diff --git a/LJ.conf b/LJ.conf index e5cf99e..95552a8 100644 --- a/LJ.conf +++ b/LJ.conf @@ -7,16 +7,16 @@ bhoroscip = 127.0.0.1 [laser0] color = -1 -ip = 127.0.0.1 +ip = 192.168.1.4 kpps = 25000 -centerx = -1610 -centery = 0 -zoomx = 26.0 -zoomy = 38.0 +centerx = -10485 +centery = -1985 +zoomx = 12.0 +zoomy = 7.0 sizex = 31450 sizey = 32000 finangle = 0.0 -swapx = 1 +swapx = -1 swapy = -1 lsteps = [ (1.0, 8),(0.25, 3), (0.75, 3), (1.0, 10)] warpdest = [[-1500., 1500.], @@ -28,10 +28,10 @@ warpdest = [[-1500., 1500.], color = -1 ip = 192.168.1.3 kpps = 25000 -centerx = 506 -centery = 413 -zoomx = 30.0 -zoomy = 30.0 +centerx = 1554 +centery = 3573 +zoomx = 42.0 +zoomy = 40.0 sizex = 32000 sizey = 32000 finangle = 0.0 @@ -45,32 +45,13 @@ warpdest = [[-1500., 1500.], [laser2] color = -1 -ip = 192.168.1.6 -kpps = 25000 -centerx = 0 -centery = 0 -zoomx = 47.8 -zoomy = 39.3 -sizex = 30600 -sizey = 32000 -finangle = -0.008 -swapx = 1 -swapy = 1 -lsteps = [(1.0, 8),(0.25, 3), (0.75, 3), (1.0, 10)] -warpdest = [[-1500., 1500.], - [ 1500., 1500.], - [ 1500.,-1500.], - [-1500.,-1500.]] - -[laser3] -color = -1 ip = 192.168.1.5 kpps = 25000 -centerx = -12 -centery = 0 -zoomx = 38.0 -zoomy = 26.0 -sizex = 32000 +centerx = -31084 +centery = -4837 +zoomx = 37.8 +zoomy = 13.3 +sizex = 30600 sizey = 32000 finangle = 0.0 swapx = -1 @@ -81,17 +62,42 @@ warpdest = [[-1500., 1500.], [ 1500.,-1500.], [-1500.,-1500.]] +[laser3] +color = -1 +ip = 192.168.1.6 +kpps = 25000 +centerx = -15930 +centery = -4434 +zoomx = 36.0 +zoomy = 54.0 +sizex = 32000 +sizey = 32000 +finangle = 0.0 +swapx = 1 +swapy = 1 +lsteps = [(1.0, 8),(0.25, 3), (0.75, 3), (1.0, 10)] +warpdest = [[-1500., 1500.], + [ 1500., 1500.], + [ 1500.,-1500.], + [-1500.,-1500.]] + [plugins] plugins = { - "nozoid": {"OSC": 8003, "command": ""}, + "nozoid": {"OSC": 8003, "command": "python3 plugins/audio/nozoids3.py"}, "glyph": {"OSC": 8004, "command": "python3 plugins/laserglyph.py"}, "planet": {"OSC": 8005, "command": "python3 plugins/planetarium/main.py"}, - "words": {"OSC": 8006, "command": "python3 plugins/livewords.py"}, + "words": {"OSC": 8006, "command": "python3 plugins/livewords3.py"}, "cycl": {"OSC": 8007, "command": "python3 plugins/textcycl.py"}, "simu": {"OSC": 8008, "command": "python plugins/pysimu.py"}, + "artnet": {"OSC": 8009, "command": "python3 libs/artnet.py"}, "bank0": {"OSC": 8010, "command": "python3 plugins/VJing/bank0.py"}, - "pose": {"OSC": 8011, "command": "python3 plugins/VJing/idiotia.py"}, + "pose": {"OSC": 8011, "command": "python plugins/VJing/idiotia.py"}, + "maxw": {"OSC": 8012, "command": "python3 plugins/maxwell.py"}, + "square": {"OSC": 8013, "command": "python3 plugins/square.py"}, "ljpong": {"OSC": 8020, "command": "python plugins/games/ljpong/main.py"}, - "ljwars": {"OSC": 8021, "command": "python plugins/games/ljsw/main.py"} + "ljwars": {"OSC": 8021, "command": "python plugins/games/ljsw/main.py"}, + "audiogen": {"OSC": 8030, "command": "python3 plugins/audio/audiogen.py"}, + "midigen": {"OSC": 8031, "command": "python3 plugins/audio/midigen.py"}, + "viewgen": {"OSC": 8032, "command": "python3 plugins/audio/viewgen.py"} } diff --git a/README.md b/README.md index 1a23d4d..3897eee 100644 --- a/README.md +++ b/README.md @@ -13,20 +13,30 @@ A software laser server with GUI for up to 4 lasers live actions. Think creative LJ has 5 main components : +- "Plugins" are points generators (to one or more lasers). Lot examples comes with LJ : planetarium, 3D anaglyph animations,... See plugins directory. - A "tracer" per etherdream/laser that take its given point list, correct geometry, recompute in laser controller coordinates, send it to its controller and report its status to the "manager". -- A "manager" that talk to all tracers (which client number point lists to draw, new geometry correction,...), handle the webui functions, OSC commands,... -- To share the lasers between people/computers, LJ accept up to 4 virtual "clients" that can simultaneously send one point list per laser. You select in WebUI wich "client" should be used by tracers. +- A "manager" that talk to all tracers (which client number point lists to draw, new geometry correction,...), handle IOs (webui functions, OSC commands,...) and plugins. - A web GUI in html, css, and vanilla js. No html server or js framework here : it's complex enough. This GUI has a (currently slow) simulator, but one can used a builtin python simulator (pysimu) or an etherdream/laser emulator (from nannou) to work without physical lasers !! -- A network available database (redis). "Clients" send directly their pointlists to redis and each "tracer" is instructed to get a given pointlist in redis. +- A network available database (redis). "Plugins" send directly their pointlists to redis and each "tracer" is instructed to get one of the avalaible pointlist in redis. See details right below... -4 clients can send 4 pointlists so up to 16 pointlists can be accessed at anytime from anywhere in the network. The server/network/webUI idea allows to share cpu intensive tasks and especially give tracers enough cpu to draw smoothly. Of course all this can happen in one computer. There is no real limits : 4 everything is tested and works smoothly if *you have enough cpu/computers/network ressources*. +More details : -It's obviously overkill for one laser in a garage, but for several laserS game events, laserS art, laserS competition, laserS planetarium,... LJ will handle the complexity. Content providers like artists, demomakers,... just need to send points. +LJ server accept up to 4 groups = virtual "scenes" of 4 "pointlists" so up to 16 pointlists can be sent to redis at anytime from anywhere in the network. You select in WebUI (Simu/plugins matrix) wich "scene" should be used by tracers/lasers. The all point is to easily share actual lasers. Imagine in demo party : -Needs at least : an etherdream DAC connected to an ILDA laser, RJ 45 IP network (gigabits only !! no wifi, 100 mpbs doesn't work well with several lasers) +Erica needs 4 lasers, that's the 4 pointlists of a "scene". +Paula and Jennifer use only 2 lasers each, so they can share a "scene". +And so on.. -LJ is tested with Firefox, supports Linux and OS X. Windows is unkown but welcome, if someone want to jump in and care about it. +The server/network/webUI idea allows to spread cpu intensive tasks on different cpu cores and especially give tracers enough cpu to feed etherdreams DACs smoothly. Of course all this can happen in one computer. There is no real limits : 4 everything is tested and works smoothly if *you have enough cpu/computers/network ressources*. + +It's obviously overkill for one laser in a garage, but for several laserS games events, laserS art, laserS competition, laserS planetarium,... LJ will handle the complexity. Content providers like artists, demomakers,... just need create plugin in whatever langage, send the points to redis. Then use the webui to select the "scene". + +Registering the plugin in LJ.conf is absolutely not mandatory. + +Needs at least : an etherdream DAC connected to an ILDA laser, RJ 45 IP network (gigabits only !! wifi and wired 100 mpbs doesn't work well with several lasers). Seriously : if you experience frame drops you need to upgrade your network. + +LJ is tested with Firefox, supports Linux and OS X. Windows is unkown but welcome, if someone want to jump in. LJ is in dev : versions in this repository will always be core functionnal : accept and draw pointlists. New features can be not fully implemented, wait for the next commit. Any feedback is welcome at any time. @@ -38,14 +48,19 @@ LJ is in dev : versions in this repository will always be core functionnal : acc (Doc in progress) -- Laser alignment like in laser mapping. +- Laser alignment like in videomapping. - OSC and websocket commands. Very cool : LJ can script or be scripted. - Web ui : In your browser open webui/index.html. Javascript is needed. By default it connect to localhost. If you want to control a remote server, you need to change the uri line in LJ.js. - Status update every 0.5 seconds : every etherdream DAC state, number of buffer points sent,... - "Optimisation" points automatically added, can be changed live for glitch art. Search "resampler" commands. - A compiled version for os x and linux of nannou.org etherdream+laser emulator is included. For more informations, like license see https://github.com/nannou-org/ether-dream - Some fancy examples are available : 3D anaglyph, Laser Pong,... - +- Midi and audio reactive, look midigen.py and fft3.py +- Openpose skeletons animations laser player. +- Resolume OSC client. +- Another project (bhorpad) has been merged in LJ : so if you a led matrix, like Launchpad mini or bhoreal, plug it you may define, launch macros as pushing on one led or use them to display informations. +- Artnet plugin. +- Maxwell laser synth emulation plugin. @@ -82,18 +97,18 @@ There is a nice websocket debug tool : websocat. Correct launch order is : -- Dac/Laser (emulator or IRL) -- Redis server once. +- Switch on Dac/Laser (emulator or IRL) +- Redis server once : i.e redis-server & - This server. see below. - Load/reload webUI page from disk in a browser (webui/index.html). Javascript must be enabled. -- Run a plugin, see in clients/plugins folder for examples. +- Run a builtin plugin or your generator, to send pointlists in redis. See in clients/plugins folder for examples. -A typical server start is python main.py -L numberoflasers. +A typical LJ server start is python main.py -L numberoflasers. Use also -h to display all possible arguments, like -v for debug informations. -CASE 1 : the laser server computer is the same that the computer running a client : +CASE 1 : the laser server computer is the same that the computer running a generator/plugin : 1/ Run LJ @@ -119,11 +134,13 @@ edit /etc/redis/redis.conf 2/ Launch LJ with -r argument : python main.py -r 192.168.1.13 -L 1 -3/ run a client/plugin on client computer, like : +3/ If the webUI is launched on "client" computer, update uri line in LJ.js + +4/ run a client/plugin on client computer, like : node nodeclient.js -4/ to monitor redis server use redis-manager/medis/... or : +5/ to monitor redis server use redis-manager/medis/... or : redis-cli -h redisserverIP monitor @@ -140,21 +157,86 @@ A "plugin" is a software that send any number of pointlist(s). LJ comes with dif - Planetarium : A 4 lasers planetarium. - pySimu : A full speed laser simulator (pygame, python 2.7) - LaserPong : Our laser Pong is back ! (python 2.7) - +- Pose : Display json openpose skeleton animations ans starfields +- fft3 : Example how to make LJ audio reactive +- maxwell : A laser synth inspired by bluefang great work. # -# Program your own "plugin" +# Client Side : Program your own "plugin" # -The server approach is based on redis, so you write and run your laser client software in any redis capable programming langage (50+ : https://redis.io/clients). If you want some interaction with GUI, like in text status area, you also need OSC. +The server approach is based on redis, so you write and run your laser client software in any redis capable programming langage (50+ : https://redis.io/clients). An external program that just send pointlists is a "client". If you want some interactions from the webUI, like text status area support, crash detection, launch,... it's a "plugin" and some default code is needed see square.py as example and : - Read all this readme ;-) -- There is a client and plugin folders with examples in different languages. If you want to do game especially with pygame, see ljpong in plugins/games directory. +- There is a client and plugin folders with examples in different languages. If you want to do game, especially with pygame, see ljpong in plugins/games directory. - Generate at least one point list array (say a square) with *enough points*, one point is likely to fail for buffering reason. -- Feed your point list array in string format to redis server. i.e use "/pl/0/1" redis key to feed client 0, pointlist 1. +- Feed your point list array in string format to redis server. i.e use "/pl/0/1" redis key to feed scene 0, pointlist 1. - Tell LJ.conf your plugin configuration : OSC port and command line to start it. -- lj3.py (python 3) and lj.py (python 2.7) have many very useful functions to not reinvent the wheel. Maybe it's better to symlink them in your directory than having a separated copy, to get future enhancements transparently. + +Currently the WebUI (webui/index.html) is static so it has to be modified 'manually' and build : run python build.py in webui directory. + + +# +# Client side dope mode : How to use lj23 +# + +lj23 have many very useful functions to not reinvent the wheel for advanced point generation "client" side : layers, built in rotations,.. + + +4 Great TRICKS with lj23. +First open square.py and learn how to declare different objects. square.py is a 2D shape example in 3D rotation (red/green anaglyph rendering) that use 2 layers : one left eye and one for right eye. + + +1/ How to have another laser drawing the same thing ? + +That's a destination problem : just add another destination ! + +Dest1 = lj.DestObject('1', 1, True, 0 , 1, 1) # Dest1 will also send layer 0 points to scene 1, laser 1 + + + +2/ Different layers to different lasers ? + +Say because of too much points you want Left element drawn by scene 0, laser 0 and right element by scene 0, laser 1 + +First define a different object/layer for each drawn element : + +Leftsquare = lj.FixedObject('Leftsquare', True, 255, [], red, 255, 0, 0, 0 , True) # Left goes to layer 0 +Rightsquare = lj.FixedObject('Rightsquare', True, 255, [], green, 0, 255, 0, 1 , True) # Right goes to layer 1 + +Define 2 destinations : + +Dest0 = lj.DestObject('0', 0, True, 0 , 0, 0) # Dest0 will send layer 0 points to scene 0, laser 0 +Dest1 = lj.DestObject('1', 1, True, 1 , 0, 1) # Dest1 will send layer 1 points to scene 0, laser 1 + + + +3/ Different layers to one laser ? + +You should consider adding all your points to one layer, but same as 1/ it's a destination problem, just add another destination with the same scene/laser for this layer + +Dest1 = lj.DestObject('1', 1, True, 1 , 0, 0) # Dest1 will also send layer 1 points to scene 0, laser 0 + + + +4/ I want to animate/modify anything on the fly : I'm doing a game and suddenly my hero change color. + +It's a declared object problem : say Hero is a Fixed Object, you can directly change value of + +Hero.name, Hero.active, Hero.intensity, Hero.xy, Hero.color, Hero.red, Hero.green, Hero.blue, Hero.layer, Hero.closed + +For a character vanishing in one point use a relativeObject so you can decrease resize parameter,... +PNC.name, PNC.active, PNC.intensity, PNC.xy, PNC.color, PNC.red, PNC.green, PNC.blue, PNC.layer, PNC.closed, PNC.xpos, PNC.ypos, PNC.resize, PNC.rotx, PNC.roty, PNC.rotz + +Remember RelativeObject points 'objectname.xy' has to be around 0,0,0. The other properties let you change the actual position (xpos, ypos), resize,.. + + +Same for Dest0 if it's a destObject, properties are : +Dest0.name, Dest0.number, Dest0.active, Dest0.layer, Dest0.scene, Dest0.laser + + +DrawDests() will take care of all your declared drawn elements/"objects" and Destinations to generate a point list per scene/laser sent to redis. In client point of view a "pointlist" is the sum of all its declared "layers". # @@ -202,7 +284,7 @@ By default LJ uses on 127.0.0.1 (localhost) : You need to update LJ.conf to your network/etherdreams IPs and be sure to check command arguments : python main.py --help -The need for a dedicated computer to act as "laser server" usually depends on how many lasers you want to control and your main computer load. If you seen flickering with small point lists, try the dedicated computer option and/or stop process interfering like redis monitoring,... +The need for a dedicated computer to act as "laser server" usually depends on how many lasers you want to control and your main computer load. If you seen flickering with small point lists, try the dedicated computer idea and/or stop process interfering like redis monitoring,... diff --git a/clients/lj.py b/clients/lj.py index d8505d4..57db353 100644 --- a/clients/lj.py +++ b/clients/lj.py @@ -1,17 +1,21 @@ # coding=UTF-8 ''' -LJ v0.8.1 -Some LJ functions useful for python clients (was framy.py) +LJ v0.8.2 +Some LJ functions useful for python 2.7 clients (was framy.py) +Functions and documentation here is low priority as python 2 support will stop soon. +Better code your plugin with python 3 and lj3.py. -Config(redisIP, client number) + +Config PolyLineOneColor rPolyLineOneColor - -Text(word, color, PL, xpos, ypos, resize, rotx, roty, rotz) : Display a word -Send(adress,message) : remote control. See commands.py -WebStatus(message) : display message on webui -DrawPL(point list number) : once you stacked all wanted elements, like 2 polylines, send them to lasers. +Text +SendLJ : remote control +LjClient : +LjPl : +DrawPL +WebStatus LICENCE : CC Sam Neurohack @@ -20,10 +24,11 @@ Sam Neurohack import math import redis -#from OSC import OSCServer, OSCClient, OSCMessage +from OSC import OSCServer, OSCClient, OSCMessage -redisIP = '127.0.0.1' -r = redis.StrictRedis(host=redisIP, port=6379, db=0) +print "Importing lj..." +#redisIP = '127.0.0.1' +#r = redis.StrictRedis(host=redisIP, port=6379, db=0) ClientNumber = 0 @@ -31,22 +36,14 @@ point_list = [] pl = [[],[],[],[]] -''' -LJIP = "127.0.0.1" -osclientlj = OSCClient() -oscmsg = OSCMessage() -osclientlj.connect((redisIP, 8002)) -''' - -''' -def Send(oscaddress,oscargs=''): +def SendLJ(oscaddress,oscargs=''): oscmsg = OSCMessage() oscmsg.setAddress(oscaddress) oscmsg.append(oscargs) - #print ("sending to bhorosc : ",oscmsg) + print ("sending OSC message : ",oscmsg) try: osclientlj.sendto(oscmsg, (redisIP, 8002)) oscmsg.clearData() @@ -54,11 +51,9 @@ def Send(oscaddress,oscargs=''): print ('Connection to LJ refused : died ?') pass #time.sleep(0.001 -''' def WebStatus(message): - Send("/status",message) - + SendLJ("/status", message) ASCII_GRAPHICS = [ @@ -162,6 +157,47 @@ def Config(redisIP,client): r = redis.StrictRedis(host=redisIP, port=6379, db=0) ClientNumber = client + #print "client configured",ClientNumber + + +def LjClient(client): + global ClientNumber + + ClientNumber = client + +def LjPl(pl): + global PL + + PL = pl + +# Properly close the system. Todo +def OSCstop(): + osc_terminate() + + +# Answer to LJ pings with /pong value +def OSCping(name): + SendLJ("/pong",name) + + + +# Closing plugin messages to LJ +def ClosePlugin(name): + WebStatus(name+" Exit") + SendLJ("/"+name+"/start",0) + + + + +# /quit +def OSCquit(name): + + WebStatus(name + " quit.") + print("Stopping OSC...") + + OSCstop() + sys.exit() + def LineTo(xy, c, PL): @@ -199,7 +235,7 @@ def Pointransf(xy, xpos = 0, ypos =0, resize =1, rotx =0, roty =0 , rotz=0): y = xy[1] * resize z = 0 - rad = rotx * math.pi / 180 + rad = math.radians(rotx) cosaX = math.cos(rad) sinaX = math.sin(rad) @@ -207,7 +243,7 @@ def Pointransf(xy, xpos = 0, ypos =0, resize =1, rotx =0, roty =0 , rotz=0): y = y2 * cosaX - z * sinaX z = y2 * sinaX + z * cosaX - rad = roty * math.pi / 180 + rad = math.radians(roty) cosaY = math.cos(rad) sinaY = math.sin(rad) @@ -215,7 +251,7 @@ def Pointransf(xy, xpos = 0, ypos =0, resize =1, rotx =0, roty =0 , rotz=0): z = z2 * cosaY - x * sinaY x = z2 * sinaY + x * cosaY - rad = rotz * math.pi / 180 + rad = math.radians(rotz) cosZ = math.cos(rad) sinZ = math.sin(rad) @@ -250,12 +286,14 @@ def rPolyLineOneColor(xy_list, c, PL , closed, xpos = 0, ypos =0, resize =0.7, r def LinesPL(PL): - print ("Stupido !! your code is to old : use DrawPL() instead of LinesPL()") + print "Stupido !! your code is to old : use DrawPL() instead of LinesPL()" DrawPL(PL) + def DrawPL(PL): #print '/pl/0/'+str(PL), str(pl[PL]) if r.set('/pl/'+str(ClientNumber)+'/'+str(PL), str(pl[PL])) == True: + #print '/pl/'+str(ClientNumber)+'/'+str(PL), str(pl[PL]) pl[PL] = [] return True else: @@ -294,12 +332,11 @@ def Text(message,c, PL, xpos, ypos, resize, rotx, roty, rotz): #print "" # texte centre en x automatiquement selon le nombre de lettres l x_offset = 26 * (- (0.9*l) + 3*i) - #print i,x_offset + # Digits if ord(ch)<58: char_pl_list = ASCII_GRAPHICS[ord(ch) - 48] else: char_pl_list = ASCII_GRAPHICS[ord(ch) - 46] - char_draw = [] #dots.append((char_pl_list[0][0] + x_offset,char_pl_list[0][1],0)) @@ -312,4 +349,4 @@ def Text(message,c, PL, xpos, ypos, resize, rotx, roty, rotz): - + \ No newline at end of file diff --git a/clients/ljremote.py b/clients/ljremote.py deleted file mode 100644 index 6cf0905..0000000 --- a/clients/ljremote.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/python2.7 -# -*- coding: utf-8 -*- -# -*- mode: Python -*- -''' - -LJ Remote control via OSC in python - -This is different than sending pointlist to draw. See pyclient or laserglyph for that. -Say your client want to display some information on the weUI status field or remote control any other LJ functions. - -See commands.py for LJ functions list. -See Doctodo for all webUI elements. - -LJ OSC server is listening on port 8002, talk to it. - -''' -from OSC import OSCServer, OSCClient, OSCMessage - -LJIP = "127.0.0.1" - -osclientlj = OSCClient() -oscmsg = OSCMessage() -osclientlj.connect((LJIP, 8002)) - -def sendlj(oscaddress,oscargs=''): - - oscmsg = OSCMessage() - oscmsg.setAddress(oscaddress) - oscmsg.append(oscargs) - - #print ("sending to bhorosc : ",oscmsg) - try: - osclientlj.sendto(oscmsg, (LJIP, 8002)) - oscmsg.clearData() - except: - print ('Connection to LJ refused : died ?') - pass - #time.sleep(0.001) - -# display pouet in status display -sendlj("/status","pouet") - -# switch laser 0 ack led to green -sendlj("/lack/0", 1) \ No newline at end of file diff --git a/libs/.DS_Store b/libs/.DS_Store new file mode 100644 index 0000000..2a920cd Binary files /dev/null and b/libs/.DS_Store differ diff --git a/libs/LPD8.py b/libs/LPD8.py new file mode 100644 index 0000000..9256e7d --- /dev/null +++ b/libs/LPD8.py @@ -0,0 +1,289 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +LPD8 +v0.7.0 + +LPD8 Handler. +Start a dedicated thread to handle incoming events from LPD8 midi controller. + +Depending on selected destination computer (Prog Chg + Pad number) actions will be done +locally or forwarded via OSC to given computer. Remote computer must run bhorpad or +maxwellator. + +# Note + +# Program Change button selected : change destination computer + +# CC rotary -> midi CC. + + +by Sam Neurohack +from /team/laser + + +""" + + +import time +import rtmidi +from rtmidi.midiutil import open_midiinput +from threading import Thread +from rtmidi.midiconstants import (CHANNEL_PRESSURE, CONTROLLER_CHANGE, NOTE_ON, NOTE_OFF, + PITCH_BEND, POLY_PRESSURE, PROGRAM_CHANGE) + +from mido import MidiFile +import mido +import sys +import midi3, launchpad +#import midimacros, maxwellmacros +import traceback + +from queue import Queue +#from libs import macros +import json, subprocess +from OSC3 import OSCServer, OSCClient, OSCMessage +import socket + +print('LPD8 startup...') +myHostName = socket.gethostname() +print("Name of the localhost is {}".format(myHostName)) +myIP = socket.gethostbyname(myHostName) +print("IP address of the localhost is {}".format(myIP)) + +myIP = "127.0.0.1" + +print('Used IP', myIP) +OSCinPort = 8080 +maxwellatorPort = 8090 + +LPD8queue = Queue() + +mode = "maxwell" +mididest = 'Session 1' + +midichannel = 1 +CChannel = 0 +CCvalue = 0 +Here = -1 + +ModeCallback = '' +computerIP = ['127.0.0.1','192.168.2.95','192.168.2.52','127.0.0.1', + '127.0.0.1','127.0.0.1','127.0.0.1','127.0.0.1'] +computer = 0 + + +# /cc cc number value +def cc(ccnumber, value, dest=mididest): + + midi3.MidiMsg([CONTROLLER_CHANGE+midichannel-1,ccnumber,value], dest) + +def NoteOn(note,velocity, dest=mididest): + midi3.NoteOn(note,velocity, mididest) + +def NoteOff(note, dest=mididest): + midi3.NoteOn(note, mididest) + + +def ComputerUpdate(comput): + global computer + + computer = comput + + + +# Client to export buttons actions from LPD8 or bhoreal + +def SendOSC(ip,port,oscaddress,oscargs=''): + + oscmsg = OSCMessage() + oscmsg.setAddress(oscaddress) + oscmsg.append(oscargs) + + osclient = OSCClient() + osclient.connect((ip, port)) + + print("sending OSC message : ", oscmsg, "to", ip, ":", port) + + try: + osclient.sendto(oscmsg, (ip, port)) + oscmsg.clearData() + return True + except: + print ('Connection to', ip, 'refused : died ?') + return False + + +# +# Events from LPD8 buttons +# + +# Process events coming from LPD8 in a separate thread. + +def MidinProcess(LPD8queue): + global computer + + + while True: + LPD8queue_get = LPD8queue.get + msg = LPD8queue_get() + #print (msg) + + # Note + if msg[0]==NOTE_ON: + + # note mode + ModeNote(msg[1], msg[2], mididest) + + ''' + # ModeOS + if msg[2] > 0: + ModeOS(msg[0]) + ''' + + + # Program Change button selected : change destination computer + if msg[0]==PROGRAM_CHANGE: + + print("Program change : ", str(msg[1])) + # Change destination computer mode + print("Destination computer",int(msg[1])) + computer = int(msg[1]) + + + # CC rotary -> midi CC. + if msg[0] == CONTROLLER_CHANGE: + + print("CC :", msg[1], msg[2]) + + if computer == 0 or computer == 1: + cc(int(msg[1]), int(msg[2])) + + else: + SendOSC(computerIP[computer-1], maxwellatorPort, '/cc', [int(msg[1]), int(msg[2])]) + +# +# Notes = midi notes +# + +def ModeNote(note, velocity, mididest): + + + print('computer',computer) + + # todo : decide whether its 0 or 1 !!! + if computer == 0 or computer == 1: + midi3.NoteOn(arg, velocity, mididest) + + + if velocity == 127: + pass + #print ('NoteON', BhorNoteXY(x,y),notename , "velocity", velocity ) + #Disp(notename) + else: + midi3.NoteOff(arg) + #print ('NoteOFF', BhorNoteXY(x,y),notename , "velocity", velocity ) + + + +# +# Notes = OS Macros +# + +def ModeOS(arg): + + + macroname = 'n'+arg + macronumber = findMacros(macroname,'OS') + if macronumber != -1: + + eval(macros['OS'][macronumber]["code"]) + else: + print("no Code yet") + + + +LPD8queue = Queue() + + +# LPD8 Mini call back : new msg forwarded to LPD8 queue +class LPD8AddQueue(object): + def __init__(self, port): + self.port = port + #print("LPD8AddQueue", self.port) + self._wallclock = time.time() + + def __call__(self, event, data=None): + message, deltatime = event + self._wallclock += deltatime + print() + print("[%s] @%0.6f %r" % (self.port, self._wallclock, message)) + LPD8queue.put(message) + +# +# Modes : +# + +# Load Matrix only macros (for the moment) in macros.json +def LoadMacros(): + global macros + + print() + print("Loading LPD8 Macros...") + f=open("macros.json","r") + s = f.read() + macros = json.loads(s) + print(len(macros['OS']),"Macros") + print("Loaded.") + + +# return macroname number for given type 'OS', 'Maxwell' +def findMacros(macroname,macrotype): + + #print("searching", macroname,'...') + position = -1 + for counter in range(len(macros[macrotype])): + #print (counter,macros[macrotype][counter]['name'],macros[macrotype][counter]['code']) + if macroname == macros[macrotype][counter]['name']: + #print(macroname, "is ", counter) + position = counter + return position + + + +# Not assigned buttons +def DefaultMacro(arg): + + print ("DefaultMacro", arg) + + + + +# +# Default macros +# + + +LPD8macros = { + + "n1": {"command": DefaultMacro, "default": 1}, + "n2": {"command": DefaultMacro, "default": 2}, + "n3": {"command": DefaultMacro, "default": 3}, + "n4": {"command": DefaultMacro, "default": 4}, + "n5": {"command": DefaultMacro, "default": 5}, + "n6": {"command": DefaultMacro, "default": 6}, + "n7": {"command": DefaultMacro, "default": 7}, + "n8": {"command": DefaultMacro, "default": 8} + } + + +def Run(macroname, macroargs=''): + + doit = LPD8macros[macroname]["command"] + if macroargs=='': + macroargs = LPD8macros[macroname]["default"] + #print("Running", doit, "with args", macroargs ) + doit(macroargs) + +LoadMacros() diff --git a/libs/OSC3.py b/libs/OSC3.py new file mode 100755 index 0000000..e4cc3bf --- /dev/null +++ b/libs/OSC3.py @@ -0,0 +1,2873 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +March 2015: + Python 3 version tested in Blender and simpleOSC with twisted + +This module contains an OpenSoundControl implementation (in Pure Python), based +(somewhat) on the good old 'SimpleOSC' implementation by Daniel Holth & Clinton +McChesney. + +This implementation is intended to still be 'simple' to the user, but much more +complete (with OSCServer & OSCClient classes) and much more powerful (the +OSCMultiClient supports subscriptions & message-filtering, OSCMessage & +OSCBundle are now proper container-types) + +=============================================================================== +OpenSoundControl +=============================================================================== + +OpenSoundControl is a network-protocol for sending (small) packets of addressed +data over network sockets. This OSC-implementation supports the classical +UDP/IP protocol for sending and receiving packets but provides as well support +for TCP/IP streaming, whereas the message size is prepended as int32 (big +endian) before each message/packet. + +OSC-packets come in two kinds: + + - OSC-messages consist of an 'address'-string (not to be confused with a + (host:port) network-address!), followed by a string of 'typetags' + associated with the message's arguments (ie. 'payload'), and finally the + arguments themselves, encoded in an OSC-specific way. The OSCMessage class + makes it easy to create & manipulate OSC-messages of this kind in a + 'pythonesque' way (that is, OSCMessage-objects behave a lot like lists) + + - OSC-bundles are a special type of OSC-message containing only + OSC-messages as 'payload'. Recursively. (meaning; an OSC-bundle could + contain other OSC-bundles, containing OSC-bundles etc.) + +OSC-bundles start with the special keyword '#bundle' and do not have an +OSC-address (but the OSC-messages a bundle contains will have OSC-addresses!). +Also, an OSC-bundle can have a timetag, essentially telling the receiving +server to 'hold' the bundle until the specified time. The OSCBundle class +allows easy cration & manipulation of OSC-bundles. + +For further information see also http://opensoundcontrol.org/spec-1_0 + +------------------------------------------------------------------------------- + +To send OSC-messages, you need an OSCClient, and to receive OSC-messages you +need an OSCServer. + +The OSCClient uses an 'AF_INET / SOCK_DGRAM' type socket (see the 'socket' +module) to send binary representations of OSC-messages to a remote host:port +address. + +The OSCServer listens on an 'AF_INET / SOCK_DGRAM' type socket bound to a local +port, and handles incoming requests. Either one-after-the-other (OSCServer) or +in a multi-threaded / multi-process fashion (ThreadingOSCServer/ +ForkingOSCServer). If the Server has a callback-function (a.k.a. handler) +registered to 'deal with' (i.e. handle) the received message's OSC-address, +that function is called, passing it the (decoded) message. + +The different OSCServers implemented here all support the (recursive) un- +bundling of OSC-bundles, and OSC-bundle timetags. + +In fact, this implementation supports: + + - OSC-messages with 'i' (int32), 'f' (float32), 'd' (double), 's' (string) and + 'b' (blob / binary data) types + - OSC-bundles, including timetag-support + - OSC-address patterns including '*', '?', '{,}' and '[]' wildcards. + +(please *do* read the OSC-spec! http://opensoundcontrol.org/spec-1_0 it +explains what these things mean.) + +In addition, the OSCMultiClient supports: + - Sending a specific OSC-message to multiple remote servers + - Remote server subscription / unsubscription (through OSC-messages, of course) + - Message-address filtering. + +------------------------------------------------------------------------------- +SimpleOSC: + Copyright (c) Daniel Holth & Clinton McChesney. +pyOSC: + Copyright (c) 2008-2010, Artem Baguinski et al., Stock, V2_Lab, Rotterdam, Netherlands. +Streaming support (OSC over TCP): + Copyright (c) 2010 Uli Franke , Weiss Engineering, Uster, Switzerland. + +------------------------------------------------------------------------------- +Changelog: +------------------------------------------------------------------------------- +v0.3.0 - 27 Dec. 2007 + Started out to extend the 'SimpleOSC' implementation (v0.2.3) by Daniel Holth & Clinton McChesney. + Rewrote OSCMessage + Added OSCBundle + +v0.3.1 - 3 Jan. 2008 + Added OSClient + Added OSCRequestHandler, loosely based on the original CallbackManager + Added OSCServer + Removed original CallbackManager + Adapted testing-script (the 'if __name__ == "__main__":' block at the end) to use new Server & Client + +v0.3.2 - 5 Jan. 2008 + Added 'container-type emulation' methods (getitem(), setitem(), __iter__() & friends) to OSCMessage + Added ThreadingOSCServer & ForkingOSCServer + - 6 Jan. 2008 + Added OSCMultiClient + Added command-line options to testing-script (try 'python OSC.py --help') + +v0.3.3 - 9 Jan. 2008 + Added OSC-timetag support to OSCBundle & OSCRequestHandler + Added ThreadingOSCRequestHandler + +v0.3.4 - 13 Jan. 2008 + Added message-filtering to OSCMultiClient + Added subscription-handler to OSCServer + Added support fon numpy/scipy int & float types. (these get converted to 'standard' 32-bit OSC ints / floats!) + Cleaned-up and added more Docstrings + +v0.3.5 - 14 aug. 2008 + Added OSCServer.reportErr(...) method + +v0.3.6 - 19 April 2010 + Added Streaming support (OSC over TCP) + Updated documentation + Moved pattern matching stuff into separate class (OSCAddressSpace) to + facilitate implementation of different server and client architectures. + Callbacks feature now a context (object oriented) but dynamic function + inspection keeps the code backward compatible + Moved testing code into separate testbench (testbench.py) + +----------------- +Original Comments +----------------- +> Open SoundControl for Python +> Copyright (C) 2002 Daniel Holth, Clinton McChesney +> +> This library is free software; you can redistribute it and/or modify it under +> the terms of the GNU Lesser General Public License as published by the Free +> Software Foundation; either version 2.1 of the License, or (at your option) any +> later version. +> +> This library is distributed in the hope that it will be useful, but WITHOUT ANY +> WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +> PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +> details. +> +> You should have received a copy of the GNU Lesser General Public License along +> with this library; if not, write to the Free Software Foundation, Inc., 59 +> Temple Place, Suite 330, Boston, MA 02111-1307 USA +> +> For questions regarding this module contact Daniel Holth +> or visit http://www.stetson.edu/~ProctoLogic/ +> +> Changelog: +> 15 Nov. 2001: +> Removed dependency on Python 2.0 features. +> - dwh +> 13 Feb. 2002: +> Added a generic callback handler. +> - dwh +""" + +import math, re, socket, select, string, struct, sys, threading, time, types, array, errno, inspect +from socketserver import UDPServer, DatagramRequestHandler, ForkingMixIn, ThreadingMixIn, StreamRequestHandler, TCPServer +from contextlib import closing + +global version +version = ("0.3","6", "$Rev: 6382 $"[6:-2]) + +global FloatTypes +FloatTypes = [float] + +global IntTypes +IntTypes = [int] + +global NTP_epoch +from calendar import timegm +NTP_epoch = timegm((1900,1,1,0,0,0)) # NTP time started in 1 Jan 1900 +del timegm + +global NTP_units_per_second +NTP_units_per_second = 0x100000000 # about 232 picoseconds + +## +# numpy/scipy support: +## + +try: + from numpy import typeDict + + for ftype in ['float32', 'float64', 'float128']: + try: + FloatTypes.append(typeDict[ftype]) + except KeyError: + pass + + for itype in ['int8', 'int16', 'int32', 'int64']: + try: + IntTypes.append(typeDict[itype]) + IntTypes.append(typeDict['u' + itype]) + except KeyError: + pass + + # thanks for those... + del typeDict, ftype, itype + +except ImportError: + pass + +###### +# +# OSCMessage classes +# +###### + +class OSCMessage(object): + """ Builds typetagged OSC messages. + + OSCMessage objects are container objects for building OSC-messages. + On the 'front' end, they behave much like list-objects, and on the 'back' end + they generate a binary representation of the message, which can be sent over a network socket. + OSC-messages consist of an 'address'-string (not to be confused with a (host, port) IP-address!), + followed by a string of 'typetags' associated with the message's arguments (ie. 'payload'), + and finally the arguments themselves, encoded in an OSC-specific way. + + On the Python end, OSCMessage are lists of arguments, prepended by the message's address. + The message contents can be manipulated much like a list: + >>> msg = OSCMessage("/my/osc/address") + >>> msg.append('something') + >>> msg.insert(0, 'something else') + >>> msg[1] = 'entirely' + >>> msg.extend([1,2,3.]) + >>> msg += [4, 5, 6.] + >>> del msg[3:6] + >>> msg.pop(-2) + 5 + >>> print msg + /my/osc/address ['something else', 'entirely', 1, 6.0] + + OSCMessages can be concatenated with the + operator. In this case, the resulting OSCMessage + inherits its address from the left-hand operand. The right-hand operand's address is ignored. + To construct an 'OSC-bundle' from multiple OSCMessage, see OSCBundle! + + Additional methods exist for retreiving typetags or manipulating items as (typetag, value) tuples. + """ + def __init__(self, address=""): + """Instantiate a new OSCMessage. + The OSC-address can be specified with the 'address' argument + """ + self.clear(address) + + def setAddress(self, address): + """Set or change the OSC-address + """ + self.address = address + + def clear(self, address=""): + """Clear (or set a new) OSC-address and clear any arguments appended so far + """ + self.address = address + self.clearData() + + def clearData(self): + """Clear any arguments appended so far + """ + self.typetags = "," + self.message = b"" + + def append(self, argument, typehint=None): + """Appends data to the message, updating the typetags based on + the argument's type. If the argument is a blob (counted + string) pass in 'b' as typehint. + 'argument' may also be a list or tuple, in which case its elements + will get appended one-by-one, all using the provided typehint + """ + if isinstance(argument,dict): + argument = list(argument.items()) + elif isinstance(argument, OSCMessage): + raise TypeError("Can only append 'OSCMessage' to 'OSCBundle'") + + if hasattr(argument, '__iter__') and not type(argument) in (str,bytes): + for arg in argument: + self.append(arg, typehint) + + return + + if typehint == 'b': + binary = OSCBlob(argument) + tag = 'b' + elif typehint == 't': + binary = OSCTimeTag(argument) + tag = 't' + else: + tag, binary = OSCArgument(argument, typehint) + + self.typetags += tag + self.message += binary + + def getBinary(self): + """Returns the binary representation of the message + """ + binary = OSCString(self.address) + binary += OSCString(self.typetags) + binary += self.message + + return binary + + def __repr__(self): + """Returns a string containing the decode Message + """ + return str(decodeOSC(self.getBinary())) + + def __str__(self): + """Returns the Message's address and contents as a string. + """ + return "%s %s" % (self.address, str(list(self.values()))) + + def __len__(self): + """Returns the number of arguments appended so far + """ + return (len(self.typetags) - 1) + + def __eq__(self, other): + """Return True if two OSCMessages have the same address & content + """ + if not isinstance(other, self.__class__): + return False + + return (self.address == other.address) and (self.typetags == other.typetags) and (self.message == other.message) + + def __ne__(self, other): + """Return (not self.__eq__(other)) + """ + return not self.__eq__(other) + + def __add__(self, values): + """Returns a copy of self, with the contents of 'values' appended + (see the 'extend()' method, below) + """ + msg = self.copy() + msg.extend(values) + return msg + + def __iadd__(self, values): + """Appends the contents of 'values' + (equivalent to 'extend()', below) + Returns self + """ + self.extend(values) + return self + + def __radd__(self, values): + """Appends the contents of this OSCMessage to 'values' + Returns the extended 'values' (list or tuple) + """ + out = list(values) + out.extend(list(self.values())) + + if isinstance(values,tuple): + return tuple(out) + + return out + + def _reencode(self, items): + """Erase & rebuild the OSCMessage contents from the given + list of (typehint, value) tuples""" + self.clearData() + for item in items: + self.append(item[1], item[0]) + + def values(self): + """Returns a list of the arguments appended so far + """ + return decodeOSC(self.getBinary())[2:] + + def tags(self): + """Returns a list of typetags of the appended arguments + """ + return list(self.typetags.lstrip(',')) + + def items(self): + """Returns a list of (typetag, value) tuples for + the arguments appended so far + """ + out = [] + values = list(self.values()) + typetags = self.tags() + for i in range(len(values)): + out.append((typetags[i], values[i])) + + return out + + def __contains__(self, val): + """Test if the given value appears in the OSCMessage's arguments + """ + return (val in list(self.values())) + + def __getitem__(self, i): + """Returns the indicated argument (or slice) + """ + return list(self.values())[i] + + def __delitem__(self, i): + """Removes the indicated argument (or slice) + """ + items = list(self.items()) + del items[i] + + self._reencode(items) + + def _buildItemList(self, values, typehint=None): + if isinstance(values, OSCMessage): + items = list(values.items()) + elif isinstance(values,list): + items = [] + for val in values: + if isinstance(val,tuple): + items.append(val[:2]) + else: + items.append((typehint, val)) + elif isinstance(values,tuple): + items = [values[:2]] + else: + items = [(typehint, values)] + + return items + + def __setitem__(self, i, val): + """Set indicatated argument (or slice) to a new value. + 'val' can be a single int/float/string, or a (typehint, value) tuple. + Or, if 'i' is a slice, a list of these or another OSCMessage. + """ + items = list(self.items()) + + new_items = self._buildItemList(val) + + if not isinstance(i,slice): + if len(new_items) != 1: + raise TypeError("single-item assignment expects a single value or a (typetag, value) tuple") + + new_items = new_items[0] + + # finally... + items[i] = new_items + + self._reencode(items) + + def setItem(self, i, val, typehint=None): + """Set indicated argument to a new value (with typehint) + """ + items = list(self.items()) + + items[i] = (typehint, val) + + self._reencode(items) + + def copy(self): + """Returns a deep copy of this OSCMessage + """ + msg = self.__class__(self.address) + msg.typetags = self.typetags + msg.message = self.message + return msg + + def count(self, val): + """Returns the number of times the given value occurs in the OSCMessage's arguments + """ + return list(self.values()).count(val) + + def index(self, val): + """Returns the index of the first occurence of the given value in the OSCMessage's arguments. + Raises ValueError if val isn't found + """ + return list(self.values()).index(val) + + def extend(self, values): + """Append the contents of 'values' to this OSCMessage. + 'values' can be another OSCMessage, or a list/tuple of ints/floats/strings + """ + items = list(self.items()) + self._buildItemList(values) + + self._reencode(items) + + def insert(self, i, val, typehint = None): + """Insert given value (with optional typehint) into the OSCMessage + at the given index. + """ + items = list(self.items()) + + for item in reversed(self._buildItemList(val)): + items.insert(i, item) + + self._reencode(items) + + def popitem(self, i): + """Delete the indicated argument from the OSCMessage, and return it + as a (typetag, value) tuple. + """ + items = list(self.items()) + + item = items.pop(i) + + self._reencode(items) + + return item + + def pop(self, i): + """Delete the indicated argument from the OSCMessage, and return it. + """ + return self.popitem(i)[1] + + def reverse(self): + """Reverses the arguments of the OSCMessage (in place) + """ + items = list(self.items()) + + items.reverse() + + self._reencode(items) + + def remove(self, val): + """Removes the first argument with the given value from the OSCMessage. + Raises ValueError if val isn't found. + """ + items = list(self.items()) + + # this is not very efficient... + i = 0 + for (t, v) in items: + if (v == val): + break + i += 1 + else: + raise ValueError("'%s' not in OSCMessage" % str(m)) + # but more efficient than first calling self.values().index(val), + # then calling self.items(), which would in turn call self.values() again... + + del items[i] + + self._reencode(items) + + def __iter__(self): + """Returns an iterator of the OSCMessage's arguments + """ + return iter(list(self.values())) + + def __reversed__(self): + """Returns a reverse iterator of the OSCMessage's arguments + """ + return reversed(list(self.values())) + + def itervalues(self): + """Returns an iterator of the OSCMessage's arguments + """ + return iter(list(self.values())) + + def iteritems(self): + """Returns an iterator of the OSCMessage's arguments as + (typetag, value) tuples + """ + return iter(list(self.items())) + + def itertags(self): + """Returns an iterator of the OSCMessage's arguments' typetags + """ + return iter(self.tags()) + +class OSCBundle(OSCMessage): + """Builds a 'bundle' of OSC messages. + + OSCBundle objects are container objects for building OSC-bundles of OSC-messages. + An OSC-bundle is a special kind of OSC-message which contains a list of OSC-messages + (And yes, OSC-bundles may contain other OSC-bundles...) + + OSCBundle objects behave much the same as OSCMessage objects, with these exceptions: + - if an item or items to be appended or inserted are not OSCMessage objects, + OSCMessage objectss are created to encapsulate the item(s) + - an OSC-bundle does not have an address of its own, only the contained OSC-messages do. + The OSCBundle's 'address' is inherited by any OSCMessage the OSCBundle object creates. + - OSC-bundles have a timetag to tell the receiver when the bundle should be processed. + The default timetag value (0) means 'immediately' + """ + def __init__(self, address="", time=0): + """Instantiate a new OSCBundle. + The default OSC-address for newly created OSCMessages + can be specified with the 'address' argument + The bundle's timetag can be set with the 'time' argument + """ + super(OSCBundle, self).__init__(address) + self.timetag = time + + def __str__(self): + """Returns the Bundle's contents (and timetag, if nonzero) as a string. + """ + if (self.timetag > 0.): + out = "#bundle (%s) [" % self.getTimeTagStr() + else: + out = "#bundle [" + + if self.__len__(): + for val in list(self.values()): + out += "%s, " % str(val) + out = out[:-2] # strip trailing space and comma + + return out + "]" + + def setTimeTag(self, time): + """Set or change the OSCBundle's TimeTag + In 'Python Time', that's floating seconds since the Epoch + """ + if time >= 0: + self.timetag = time + + def getTimeTagStr(self): + """Return the TimeTag as a human-readable string + """ + fract, secs = math.modf(self.timetag) + out = time.ctime(secs)[11:19] + out += ("%.3f" % fract)[1:] + + return out + + def append(self, argument, typehint = None): + """Appends data to the bundle, creating an OSCMessage to encapsulate + the provided argument unless this is already an OSCMessage. + Any newly created OSCMessage inherits the OSCBundle's address at the time of creation. + If 'argument' is an iterable, its elements will be encapsuated by a single OSCMessage. + Finally, 'argument' can be (or contain) a dict, which will be 'converted' to an OSCMessage; + - if 'addr' appears in the dict, its value overrides the OSCBundle's address + - if 'args' appears in the dict, its value(s) become the OSCMessage's arguments + """ + if isinstance(argument, OSCMessage): + binary = OSCBlob(argument.getBinary()) + else: + msg = OSCMessage(self.address) + if isinstance(argument,dict): + if 'addr' in argument: + msg.setAddress(argument['addr']) + if 'args' in argument: + msg.append(argument['args'], typehint) + else: + msg.append(argument, typehint) + + binary = OSCBlob(msg.getBinary()) + + self.message += binary + self.typetags += 'b' + + def getBinary(self): + """Returns the binary representation of the message + """ + binary = OSCString("#bundle") + binary += OSCTimeTag(self.timetag) + binary += self.message + + return binary + + def _reencapsulate(self, decoded): + if decoded[0] == "#bundle": + msg = OSCBundle() + msg.setTimeTag(decoded[1]) + for submsg in decoded[2:]: + msg.append(self._reencapsulate(submsg)) + + else: + msg = OSCMessage(decoded[0]) + tags = decoded[1].lstrip(',') + for i in range(len(tags)): + msg.append(decoded[2+i], tags[i]) + + return msg + + def values(self): + """Returns a list of the OSCMessages appended so far + """ + out = [] + for decoded in decodeOSC(self.getBinary())[2:]: + out.append(self._reencapsulate(decoded)) + + return out + + def __eq__(self, other): + """Return True if two OSCBundles have the same timetag & content + """ + if not isinstance(other, self.__class__): + return False + + return (self.timetag == other.timetag) and (self.typetags == other.typetags) and (self.message == other.message) + + def copy(self): + """Returns a deep copy of this OSCBundle + """ + copy = super(OSCBundle, self).copy() + copy.timetag = self.timetag + return copy + +###### +# +# OSCMessage encoding functions +# +###### + +def OSCString(next): + """Convert a string into a zero-padded OSC String. + The length of the resulting string is always a multiple of 4 bytes. + The string ends with 1 to 4 zero-bytes ('\x00') + """ + + OSCstringLength = math.ceil((len(next)+1) / 4.0) * 4 + return struct.pack(">%ds" % (OSCstringLength), str(next).encode('latin1')) + +def OSCBlob(next): + """Convert a string into an OSC Blob. + An OSC-Blob is a binary encoded block of data, prepended by a 'size' (int32). + The size is always a mutiple of 4 bytes. + The blob ends with 0 to 3 zero-bytes ('\x00') + """ + + if isinstance(next,str): + next = next.encode('latin1') + if isinstance(next,bytes): + OSCblobLength = math.ceil((len(next)) / 4.0) * 4 + binary = struct.pack(">i%ds" % (OSCblobLength), OSCblobLength, next) + else: + binary = b'' + + return binary + +def OSCArgument(next, typehint=None): + """ Convert some Python types to their + OSC binary representations, returning a + (typetag, data) tuple. + """ + if not typehint: + if type(next) in FloatTypes: + binary = struct.pack(">f", float(next)) + tag = 'f' + elif type(next) in IntTypes: + binary = struct.pack(">i", int(next)) + tag = 'i' + else: + binary = OSCString(next) + tag = 's' + + elif typehint == 'd': + try: + binary = struct.pack(">d", float(next)) + tag = 'd' + except ValueError: + binary = OSCString(next) + tag = 's' + + elif typehint == 'f': + try: + binary = struct.pack(">f", float(next)) + tag = 'f' + except ValueError: + binary = OSCString(next) + tag = 's' + elif typehint == 'i': + try: + binary = struct.pack(">i", int(next)) + tag = 'i' + except ValueError: + binary = OSCString(next) + tag = 's' + else: + binary = OSCString(next) + tag = 's' + + return (tag, binary) + +def OSCTimeTag(time): + """Convert a time in floating seconds to its + OSC binary representation + """ + if time > 0: + fract, secs = math.modf(time) + secs = secs - NTP_epoch + binary = struct.pack('>LL', int(secs), int(fract * NTP_units_per_second)) + else: + binary = struct.pack('>LL', 0, 1) + + return binary + +###### +# +# OSCMessage decoding functions +# +###### + +def _readString(data): + """Reads the next (null-terminated) block of data + """ + length = data.find(b'\0') + nextData = int(math.ceil((length+1) / 4.0) * 4) + return (data[0:length].decode('latin1'), data[nextData:]) + +def _readBlob(data): + """Reads the next (numbered) block of data + """ + + length = struct.unpack(">i", data[0:4])[0] + nextData = int(math.ceil((length) / 4.0) * 4) + 4 + return (data[4:length+4], data[nextData:]) + +def _readInt(data): + """Tries to interpret the next 4 bytes of the data + as a 32-bit integer. """ + + if(len(data)<4): + print("Error: too few bytes for int", data, len(data)) + rest = data + integer = 0 + else: + integer = struct.unpack(">i", data[0:4])[0] + rest = data[4:] + + return (integer, rest) + +def _readLong(data): + """Tries to interpret the next 8 bytes of the data + as a 64-bit signed integer. + """ + + high, low = struct.unpack(">ll", data[0:8]) + big = (int(high) << 32) + low + rest = data[8:] + return (big, rest) + +def _readTimeTag(data): + """Tries to interpret the next 8 bytes of the data + as a TimeTag. + """ + high, low = struct.unpack(">LL", data[0:8]) + if (high == 0) and (low <= 1): + time = 0.0 + else: + time = int(NTP_epoch + high) + float(low / NTP_units_per_second) + rest = data[8:] + return (time, rest) + +def _readFloat(data): + """Tries to interpret the next 4 bytes of the data + as a 32-bit float. + """ + + if(len(data)<4): + print("Error: too few bytes for float", data, len(data)) + rest = data + float = 0 + else: + float = struct.unpack(">f", data[0:4])[0] + rest = data[4:] + + return (float, rest) + +def _readDouble(data): + """Tries to interpret the next 8 bytes of the data + as a 64-bit float. + """ + + if(len(data)<8): + print("Error: too few bytes for double", data, len(data)) + rest = data + float = 0 + else: + float = struct.unpack(">d", data[0:8])[0] + rest = data[8:] + + return (float, rest) + +def decodeOSC(data): + """Converts a binary OSC message to a Python list. + """ + table = {"i":_readInt, "f":_readFloat, "s":_readString, "b":_readBlob, "d":_readDouble, "t":_readTimeTag} + decoded = [] + address, rest = _readString(data) + if address.startswith(","): + typetags = address + address = "" + else: + typetags = "" + + if address == "#bundle": + time, rest = _readTimeTag(rest) + decoded.append(address) + decoded.append(time) + while len(rest)>0: + length, rest = _readInt(rest) + decoded.append(decodeOSC(rest[:length])) + rest = rest[length:] + + elif len(rest)>0: + if not len(typetags): + typetags, rest = _readString(rest) + decoded.append(address) + decoded.append(typetags) + if typetags.startswith(","): + for tag in typetags[1:]: + value, rest = table[tag](rest) + decoded.append(value) + else: + raise OSCError("OSCMessage's typetag-string lacks the magic ','") + + return decoded + +###### +# +# Utility functions +# +###### + +def hexDump(bytes): + """ Useful utility; prints the string in hexadecimal. + """ + print("byte 0 1 2 3 4 5 6 7 8 9 A B C D E F") + + if isinstance(bytes,str): + bytes = bytes.encode('latin1') + num = len(bytes) + for i in range(num): + if (i) % 16 == 0: + line = "%02X0 : " % (i/16) + line += "%02X " % bytes[i] + if (i+1) % 16 == 0: + print("%s: %s" % (line, repr(bytes[i-15:i+1]))) + line = "" + + bytes_left = num % 16 + if bytes_left: + print("%s: %s" % (line.ljust(54), repr(bytes[-bytes_left:]))) + +def getUrlStr(*args): + """Convert provided arguments to a string in 'host:port/prefix' format + Args can be: + - (host, port) + - (host, port), prefix + - host, port + - host, port, prefix + """ + if not len(args): + return "" + + if type(args[0]) == tuple: + host = args[0][0] + port = args[0][1] + args = args[1:] + else: + host = args[0] + port = args[1] + args = args[2:] + + if len(args): + prefix = args[0] + else: + prefix = "" + + if len(host) and (host != '0.0.0.0'): + try: + (host, _, _) = socket.gethostbyaddr(host) + except socket.error: + pass + else: + host = 'localhost' + + if isinstance(port,int): + return "%s:%d%s" % (host, port, prefix) + else: + return host + prefix + +def parseUrlStr(url): + """Convert provided string in 'host:port/prefix' format to it's components + Returns ((host, port), prefix) + """ + if not (isinstance(url,str) and len(url)): + return (None, '') + + i = url.find("://") + if i > -1: + url = url[i+3:] + + i = url.find(':') + if i > -1: + host = url[:i].strip() + tail = url[i+1:].strip() + else: + host = '' + tail = url + + for i in range(len(tail)): + if not tail[i].isdigit(): + break + else: + i += 1 + + portstr = tail[:i].strip() + tail = tail[i:].strip() + + found = len(tail) + for c in ('/', '+', '-', '*'): + i = tail.find(c) + if (i > -1) and (i < found): + found = i + + head = tail[:found].strip() + prefix = tail[found:].strip() + + prefix = prefix.strip('/') + if len(prefix) and prefix[0] not in ('+', '-', '*'): + prefix = '/' + prefix + + if len(head) and not len(host): + host = head + + if len(host): + try: + host = socket.gethostbyname(host) + except socket.error: + pass + + try: + port = int(portstr) + except ValueError: + port = None + + return ((host, port), prefix) + +###### +# +# OSCClient class +# +###### + +class OSCClient(object): + """Simple OSC Client. Handles the sending of OSC-Packets (OSCMessage or OSCBundle) via a UDP-socket + """ + # set outgoing socket buffer size + sndbuf_size = 4096 * 8 + + def __init__(self, server=None): + """Construct an OSC Client. + When the 'address' argument is given this client is connected to a specific remote server. + - address ((host, port) tuple): the address of the remote server to send all messages to + Otherwise it acts as a generic client: + If address == 'None', the client doesn't connect to a specific remote server, + and the remote address must be supplied when calling sendto() + - server: Local OSCServer-instance this client will use the socket of for transmissions. + If none is supplied, a socket will be created. + """ + self.socket = None + + if server == None: + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, self.sndbuf_size) + self._fd = self.socket.fileno() + + self.server = None + else: + self.setServer(server) + + self.client_address = None + + def setServer(self, server): + """Associate this Client with given server. + The Client will send from the Server's socket. + The Server will use this Client instance to send replies. + """ + if not isinstance(server, OSCServer): + raise ValueError("'server' argument is not a valid OSCServer object") + + if self.socket != None: + self.close() + + self.socket = server.socket.dup() + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, self.sndbuf_size) + self._fd = self.socket.fileno() + + self.server = server + + if self.server.client != None: + self.server.client.close() + + self.server.client = self + + def close(self): + """Disconnect & close the Client's socket + """ + if self.socket != None: + self.socket.close() + self.socket = None + + def __str__(self): + """Returns a string containing this Client's Class-name, software-version + and the remote-address it is connected to (if any) + """ + out = self.__class__.__name__ + out += " v%s.%s-%s" % version + addr = self.address() + if addr: + out += " connected to osc://%s" % getUrlStr(addr) + else: + out += " (unconnected)" + + return out + + def __eq__(self, other): + """Compare function. + """ + if not isinstance(other, self.__class__): + return False + + isequal = cmp(self.socket._sock, other.socket._sock) + if isequal and self.server and other.server: + return cmp(self.server, other.server) + + return isequal + + def __ne__(self, other): + """Compare function. + """ + return not self.__eq__(other) + + def address(self): + """Returns a (host,port) tuple of the remote server this client is + connected to or None if not connected to any server. + """ + try: + return self.socket.getpeername() + except socket.error: + return None + + def connect(self, address): + """Bind to a specific OSC server: + the 'address' argument is a (host, port) tuple + - host: hostname of the remote OSC server, + - port: UDP-port the remote OSC server listens to. + """ + try: + self.socket.connect(address) + self.client_address = address + except socket.error as e: + self.client_address = None + raise OSCClientError("SocketError: %s" % str(e)) + + if self.server != None: + self.server.return_port = address[1] + + def sendto(self, msg, address, timeout=None): + """Send the given OSCMessage to the specified address. + - msg: OSCMessage (or OSCBundle) to be sent + - address: (host, port) tuple specifing remote server to send the message to + - timeout: A timeout value for attempting to send. If timeout == None, + this call blocks until socket is available for writing. + Raises OSCClientError when timing out while waiting for the socket. + """ + if not isinstance(msg, OSCMessage): + raise TypeError("'msg' argument is not an OSCMessage or OSCBundle object") + + ret = select.select([],[self._fd], [], timeout) + try: + ret[1].index(self._fd) + except: + # for the very rare case this might happen + raise OSCClientError("Timed out waiting for file descriptor") + + try: + self.socket.connect(address) + self.socket.sendall(msg.getBinary()) + + if self.client_address: + self.socket.connect(self.client_address) + + except socket.error as e: + if e[0] in (7, 65): # 7 = 'no address associated with nodename', 65 = 'no route to host' + raise e + else: + raise OSCClientError("while sending to %s: %s" % (str(address), str(e))) + + def send(self, msg, timeout=None): + """Send the given OSCMessage. + The Client must be already connected. + - msg: OSCMessage (or OSCBundle) to be sent + - timeout: A timeout value for attempting to send. If timeout == None, + this call blocks until socket is available for writing. + Raises OSCClientError when timing out while waiting for the socket, + or when the Client isn't connected to a remote server. + """ + if not isinstance(msg, OSCMessage): + raise TypeError("'msg' argument is not an OSCMessage or OSCBundle object") + + ret = select.select([],[self._fd], [], timeout) + try: + ret[1].index(self._fd) + except: + # for the very rare case this might happen + raise OSCClientError("Timed out waiting for file descriptor") + + try: + self.socket.sendall(msg.getBinary()) + except socket.error as e: + if e[0] in (7, 65): # 7 = 'no address associated with nodename', 65 = 'no route to host' + raise e + else: + raise OSCClientError("while sending: %s" % str(e)) + +###### +# +# FilterString Utility functions +# +###### + +def parseFilterStr(args): + """Convert Message-Filter settings in '+ - ...' format to a dict of the form + { '':True, '':False, ... } + Returns a list: ['', filters] + """ + out = {} + + if isinstance(args,str): + args = [args] + + prefix = None + for arg in args: + head = None + for plus in arg.split('+'): + minus = plus.split('-') + plusfs = minus.pop(0).strip() + if len(plusfs): + plusfs = '/' + plusfs.strip('/') + + if (head == None) and (plusfs != "/*"): + head = plusfs + elif len(plusfs): + if plusfs == '/*': + out = { '/*':True } # reset all previous filters + else: + out[plusfs] = True + + for minusfs in minus: + minusfs = minusfs.strip() + if len(minusfs): + minusfs = '/' + minusfs.strip('/') + if minusfs == '/*': + out = { '/*':False } # reset all previous filters + else: + out[minusfs] = False + + if prefix == None: + prefix = head + + return [prefix, out] + +def getFilterStr(filters): + """Return the given 'filters' dict as a list of + '+' | '-' filter-strings + """ + if not len(filters): + return [] + + if '/*' in list(filters.keys()): + if filters['/*']: + out = ["+/*"] + else: + out = ["-/*"] + else: + if False in list(filters.values()): + out = ["+/*"] + else: + out = ["-/*"] + + for (addr, bool) in list(filters.items()): + if addr == '/*': + continue + + if bool: + out.append("+%s" % addr) + else: + out.append("-%s" % addr) + + return out + +# A translation-table for mapping OSC-address expressions to Python 're' expressions +OSCtrans = str.maketrans("{,}?","(|).") + +def getRegEx(pattern): + """Compiles and returns a 'regular expression' object for the given address-pattern. + """ + # Translate OSC-address syntax to python 're' syntax + pattern = pattern.replace(".", r"\.") # first, escape all '.'s in the pattern. + pattern = pattern.replace("(", r"\(") # escape all '('s. + pattern = pattern.replace(")", r"\)") # escape all ')'s. + pattern = pattern.replace("*", r".*") # replace a '*' by '.*' (match 0 or more characters) + pattern = pattern.translate(OSCtrans) # change '?' to '.' and '{,}' to '(|)' + + return re.compile(pattern) + +###### +# +# OSCMultiClient class +# +###### + +class OSCMultiClient(OSCClient): + """'Multiple-Unicast' OSC Client. Handles the sending of OSC-Packets (OSCMessage or OSCBundle) via a UDP-socket + This client keeps a dict of 'OSCTargets'. and sends each OSCMessage to each OSCTarget + The OSCTargets are simply (host, port) tuples, and may be associated with an OSC-address prefix. + the OSCTarget's prefix gets prepended to each OSCMessage sent to that target. + """ + def __init__(self, server=None): + """Construct a "Multi" OSC Client. + - server: Local OSCServer-instance this client will use the socket of for transmissions. + If none is supplied, a socket will be created. + """ + super(OSCMultiClient, self).__init__(server) + + self.targets = {} + + def _searchHostAddr(self, host): + """Search the subscribed OSCTargets for (the first occurence of) given host. + Returns a (host, port) tuple + """ + try: + host = socket.gethostbyname(host) + except socket.error: + pass + + for addr in list(self.targets.keys()): + if host == addr[0]: + return addr + + raise NotSubscribedError((host, None)) + + def _updateFilters(self, dst, src): + """Update a 'filters' dict with values form another 'filters' dict: + - src[a] == True and dst[a] == False: del dst[a] + - src[a] == False and dst[a] == True: del dst[a] + - a not in dst: dst[a] == src[a] + """ + if '/*' in list(src.keys()): # reset filters + dst.clear() # 'match everything' == no filters + if not src.pop('/*'): + dst['/*'] = False # 'match nothing' + + for (addr, bool) in list(src.items()): + if (addr in list(dst.keys())) and (dst[addr] != bool): + del dst[addr] + else: + dst[addr] = bool + + def _setTarget(self, address, prefix=None, filters=None): + """Add (i.e. subscribe) a new OSCTarget, or change the prefix for an existing OSCTarget. + - address ((host, port) tuple): IP-address & UDP-port + - prefix (string): The OSC-address prefix prepended to the address of each OSCMessage + sent to this OSCTarget (optional) + """ + if address not in list(self.targets.keys()): + self.targets[address] = ["",{}] + + if prefix != None: + if len(prefix): + # make sure prefix starts with ONE '/', and does not end with '/' + prefix = '/' + prefix.strip('/') + + self.targets[address][0] = prefix + + if filters != None: + if isinstance(filters,str): + (_, filters) = parseFilterStr(filters) + elif not isinstance(filters,dict): + raise TypeError("'filters' argument must be a dict with {addr:bool} entries") + + self._updateFilters(self.targets[address][1], filters) + + def setOSCTarget(self, address, prefix=None, filters=None): + """Add (i.e. subscribe) a new OSCTarget, or change the prefix for an existing OSCTarget. + the 'address' argument can be a ((host, port) tuple) : The target server address & UDP-port + or a 'host' (string) : The host will be looked-up + - prefix (string): The OSC-address prefix prepended to the address of each OSCMessage + sent to this OSCTarget (optional) + """ + if isinstance(address,str): + address = self._searchHostAddr(address) + + elif (isinstance(address,tuple)): + (host, port) = address[:2] + try: + host = socket.gethostbyname(host) + except: + pass + + address = (host, port) + else: + raise TypeError("'address' argument must be a (host, port) tuple or a 'host' string") + + self._setTarget(address, prefix, filters) + + def setOSCTargetFromStr(self, url): + """Adds or modifies a subscribed OSCTarget from the given string, which should be in the + ':[/] [+/]|[-/] ...' format. + """ + (addr, tail) = parseUrlStr(url) + (prefix, filters) = parseFilterStr(tail) + self._setTarget(addr, prefix, filters) + + def _delTarget(self, address, prefix=None): + """Delete the specified OSCTarget from the Client's dict. + the 'address' argument must be a (host, port) tuple. + If the 'prefix' argument is given, the Target is only deleted if the address and prefix match. + """ + try: + if prefix == None: + del self.targets[address] + elif prefix == self.targets[address][0]: + del self.targets[address] + except KeyError: + raise NotSubscribedError(address, prefix) + + def delOSCTarget(self, address, prefix=None): + """Delete the specified OSCTarget from the Client's dict. + the 'address' argument can be a ((host, port) tuple), or a hostname. + If the 'prefix' argument is given, the Target is only deleted if the address and prefix match. + """ + if isinstance(address,str): + address = self._searchHostAddr(address) + + if isinstance(address,tuple): + (host, port) = address[:2] + try: + host = socket.gethostbyname(host) + except socket.error: + pass + address = (host, port) + + self._delTarget(address, prefix) + + def hasOSCTarget(self, address, prefix=None): + """Return True if the given OSCTarget exists in the Client's dict. + the 'address' argument can be a ((host, port) tuple), or a hostname. + If the 'prefix' argument is given, the return-value is only True if the address and prefix match. + """ + if isinstance(address,str): + address = self._searchHostAddr(address) + + if isinstance(address,tuple): + (host, port) = address[:2] + try: + host = socket.gethostbyname(host) + except socket.error: + pass + address = (host, port) + + if address in list(self.targets.keys()): + if prefix == None: + return True + elif prefix == self.targets[address][0]: + return True + + return False + + def getOSCTargets(self): + """Returns the dict of OSCTargets: {addr:[prefix, filters], ...} + """ + out = {} + for ((host, port), pf) in list(self.targets.items()): + try: + (host, _, _) = socket.gethostbyaddr(host) + except socket.error: + pass + + out[(host, port)] = pf + + return out + + def getOSCTarget(self, address): + """Returns the OSCTarget matching the given address as a ((host, port), [prefix, filters]) tuple. + 'address' can be a (host, port) tuple, or a 'host' (string), in which case the first matching OSCTarget is returned + Returns (None, ['',{}]) if address not found. + """ + if isinstance(address,str): + address = self._searchHostAddr(address) + + if (isinstance(address,tuple)): + (host, port) = address[:2] + try: + host = socket.gethostbyname(host) + except socket.error: + pass + address = (host, port) + + if (address in list(self.targets.keys())): + try: + (host, _, _) = socket.gethostbyaddr(host) + except socket.error: + pass + + return ((host, port), self.targets[address]) + + return (None, ['',{}]) + + def clearOSCTargets(self): + """Erases all OSCTargets from the Client's dict + """ + self.targets = {} + + def updateOSCTargets(self, dict): + """Update the Client's OSCTargets dict with the contents of 'dict' + The given dict's items MUST be of the form + { (host, port):[prefix, filters], ... } + """ + for ((host, port), (prefix, filters)) in list(dict.items()): + val = [prefix, {}] + self._updateFilters(val[1], filters) + + try: + host = socket.gethostbyname(host) + except socket.error: + pass + + self.targets[(host, port)] = val + + def getOSCTargetStr(self, address): + """Returns the OSCTarget matching the given address as a ('osc://:[]', ['', ...])' tuple. + 'address' can be a (host, port) tuple, or a 'host' (string), in which case the first matching OSCTarget is returned + Returns (None, []) if address not found. + """ + (addr, (prefix, filters)) = self.getOSCTarget(address) + if addr == None: + return (None, []) + + return ("osc://%s" % getUrlStr(addr, prefix), getFilterStr(filters)) + + def getOSCTargetStrings(self): + """Returns a list of all OSCTargets as ('osc://:[]', ['', ...])' tuples. + """ + out = [] + for (addr, (prefix, filters)) in list(self.targets.items()): + out.append(("osc://%s" % getUrlStr(addr, prefix), getFilterStr(filters))) + + return out + + def connect(self, address): + """The OSCMultiClient isn't allowed to connect to any specific + address. + """ + return NotImplemented + + def sendto(self, msg, address, timeout=None): + """Send the given OSCMessage. + The specified address is ignored. Instead this method calls send() to + send the message to all subscribed clients. + - msg: OSCMessage (or OSCBundle) to be sent + - address: (host, port) tuple specifing remote server to send the message to + - timeout: A timeout value for attempting to send. If timeout == None, + this call blocks until socket is available for writing. + Raises OSCClientError when timing out while waiting for the socket. + """ + self.send(msg, timeout) + + def _filterMessage(self, filters, msg): + """Checks the given OSCMessge against the given filters. + 'filters' is a dict containing OSC-address:bool pairs. + If 'msg' is an OSCBundle, recursively filters its constituents. + Returns None if the message is to be filtered, else returns the message. + or + Returns a copy of the OSCBundle with the filtered messages removed. + """ + if isinstance(msg, OSCBundle): + out = msg.copy() + msgs = list(out.values()) + out.clearData() + for m in msgs: + m = self._filterMessage(filters, m) + if m: # this catches 'None' and empty bundles. + out.append(m) + + elif isinstance(msg, OSCMessage): + if '/*' in list(filters.keys()): + if filters['/*']: + out = msg + else: + out = None + + elif False in list(filters.values()): + out = msg + else: + out = None + + else: + raise TypeError("'msg' argument is not an OSCMessage or OSCBundle object") + + expr = getRegEx(msg.address) + + for addr in list(filters.keys()): + if addr == '/*': + continue + + match = expr.match(addr) + if match and (match.end() == len(addr)): + if filters[addr]: + out = msg + else: + out = None + break + + return out + + def _prefixAddress(self, prefix, msg): + """Makes a copy of the given OSCMessage, then prepends the given prefix to + The message's OSC-address. + If 'msg' is an OSCBundle, recursively prepends the prefix to its constituents. + """ + out = msg.copy() + + if isinstance(msg, OSCBundle): + msgs = list(out.values()) + out.clearData() + for m in msgs: + out.append(self._prefixAddress(prefix, m)) + + elif isinstance(msg, OSCMessage): + out.setAddress(prefix + out.address) + + else: + raise TypeError("'msg' argument is not an OSCMessage or OSCBundle object") + + return out + + def send(self, msg, timeout=None): + """Send the given OSCMessage to all subscribed OSCTargets + - msg: OSCMessage (or OSCBundle) to be sent + - timeout: A timeout value for attempting to send. If timeout == None, + this call blocks until socket is available for writing. + Raises OSCClientError when timing out while waiting for the socket. + """ + for (address, (prefix, filters)) in list(self.targets.items()): + if len(filters): + out = self._filterMessage(filters, msg) + if not out: # this catches 'None' and empty bundles. + continue + else: + out = msg + + if len(prefix): + out = self._prefixAddress(prefix, msg) + + binary = out.getBinary() + + ret = select.select([],[self._fd], [], timeout) + try: + ret[1].index(self._fd) + except: + # for the very rare case this might happen + raise OSCClientError("Timed out waiting for file descriptor") + + try: + while len(binary): + sent = self.socket.sendto(binary, address) + binary = binary[sent:] + + except socket.error as e: + if e[0] in (7, 65): # 7 = 'no address associated with nodename', 65 = 'no route to host' + raise e + else: + raise OSCClientError("while sending to %s: %s" % (str(address), str(e))) + +class OSCAddressSpace: + def __init__(self): + self.callbacks = {} + def addMsgHandler(self, address, callback): + """Register a handler for an OSC-address + - 'address' is the OSC address-string. + the address-string should start with '/' and may not contain '*' + - 'callback' is the function called for incoming OSCMessages that match 'address'. + The callback-function will be called with the same arguments as the 'msgPrinter_handler' below + """ + for chk in '*?,[]{}# ': + if chk in address: + raise OSCServerError("OSC-address string may not contain any characters in '*?,[]{}# '") + + if type(callback) not in (types.FunctionType, types.MethodType): + raise OSCServerError("Message callback '%s' is not callable" % repr(callback)) + + if address != 'default': + address = '/' + address.strip('/') + + self.callbacks[address] = callback + + def delMsgHandler(self, address): + """Remove the registered handler for the given OSC-address + """ + del self.callbacks[address] + + def getOSCAddressSpace(self): + """Returns a list containing all OSC-addresses registerd with this Server. + """ + return list(self.callbacks.keys()) + + def dispatchMessage(self, pattern, tags, data, client_address): + """Attmept to match the given OSC-address pattern, which may contain '*', + against all callbacks registered with the OSCServer. + Calls the matching callback and returns whatever it returns. + If no match is found, and a 'default' callback is registered, it calls that one, + or raises NoCallbackError if a 'default' callback is not registered. + + - pattern (string): The OSC-address of the receied message + - tags (string): The OSC-typetags of the receied message's arguments, without ',' + - data (list): The message arguments + """ + if len(tags) != len(data): + raise OSCServerError("Malformed OSC-message; got %d typetags [%s] vs. %d values" % (len(tags), tags, len(data))) + + expr = getRegEx(pattern) + + replies = [] + matched = 0 + for addr in list(self.callbacks.keys()): + match = expr.match(addr) + if match and (match.end() == len(addr)): + reply = self.callbacks[addr](pattern, tags, data, client_address) + matched += 1 + if isinstance(reply, OSCMessage): + replies.append(reply) + elif reply != None: + raise TypeError("Message-callback %s did not return OSCMessage or None: %s" % (self.server.callbacks[addr], type(reply))) + + if matched == 0: + if 'default' in self.callbacks: + reply = self.callbacks['default'](pattern, tags, data, client_address) + if isinstance(reply, OSCMessage): + replies.append(reply) + elif reply != None: + raise TypeError("Message-callback %s did not return OSCMessage or None: %s" % (self.server.callbacks['default'], type(reply))) + else: + raise NoCallbackError(pattern) + + return replies + +###### +# +# OSCRequestHandler classes +# +###### +class OSCRequestHandler(DatagramRequestHandler): + """RequestHandler class for the OSCServer + """ + def setup(self): + """Prepare RequestHandler. + Unpacks request as (packet, source socket address) + Creates an empty list for replies. + """ + (self.packet, self.socket) = self.request + self.replies = [] + + def _unbundle(self, decoded): + """Recursive bundle-unpacking function""" + if decoded[0] != "#bundle": + self.replies += self.server.dispatchMessage(decoded[0], decoded[1][1:], decoded[2:], self.client_address) + return + + now = time.time() + timetag = decoded[1] + if (timetag > 0.) and (timetag > now): + time.sleep(timetag - now) + + for msg in decoded[2:]: + self._unbundle(msg) + + def handle(self): + """Handle incoming OSCMessage + """ + decoded = decodeOSC(self.packet) + if not len(decoded): + return + + self._unbundle(decoded) + + def finish(self): + """Finish handling OSCMessage. + Send any reply returned by the callback(s) back to the originating client + as an OSCMessage or OSCBundle + """ + if self.server.return_port: + self.client_address = (self.client_address[0], self.server.return_port) + + if len(self.replies) > 1: + msg = OSCBundle() + for reply in self.replies: + msg.append(reply) + elif len(self.replies) == 1: + msg = self.replies[0] + else: + return + + self.server.client.sendto(msg, self.client_address) + +class ThreadingOSCRequestHandler(OSCRequestHandler): + """Multi-threaded OSCRequestHandler; + Starts a new RequestHandler thread for each unbundled OSCMessage + """ + def _unbundle(self, decoded): + """Recursive bundle-unpacking function + This version starts a new thread for each sub-Bundle found in the Bundle, + then waits for all its children to finish. + """ + if decoded[0] != "#bundle": + self.replies += self.server.dispatchMessage(decoded[0], decoded[1][1:], decoded[2:], self.client_address) + return + + now = time.time() + timetag = decoded[1] + if (timetag > 0.) and (timetag > now): + time.sleep(timetag - now) + now = time.time() + + children = [] + + for msg in decoded[2:]: + t = threading.Thread(target = self._unbundle, args = (msg,)) + t.start() + children.append(t) + + # wait for all children to terminate + for t in children: + t.join() + +###### +# +# OSCServer classes +# +###### +class OSCServer(UDPServer, OSCAddressSpace): + """A Synchronous OSCServer + Serves one request at-a-time, until the OSCServer is closed. + The OSC address-pattern is matched against a set of OSC-adresses + that have been registered to the server with a callback-function. + If the adress-pattern of the message machtes the registered address of a callback, + that function is called. + """ + + # set the RequestHandlerClass, will be overridden by ForkingOSCServer & ThreadingOSCServer + RequestHandlerClass = OSCRequestHandler + + # define a socket timeout, so the serve_forever loop can actually exit. + socket_timeout = 1 + + # DEBUG: print error-tracebacks (to stderr)? + print_tracebacks = False + + def __init__(self, server_address, client=None, return_port=0): + """Instantiate an OSCServer. + - server_address ((host, port) tuple): the local host & UDP-port + the server listens on + - client (OSCClient instance): The OSCClient used to send replies from this server. + If none is supplied (default) an OSCClient will be created. + - return_port (int): if supplied, sets the default UDP destination-port + for replies coming from this server. + """ + UDPServer.__init__(self, server_address, self.RequestHandlerClass) + OSCAddressSpace.__init__(self) + + self.setReturnPort(return_port) + self.error_prefix = "" + self.info_prefix = "/info" + + self.socket.settimeout(self.socket_timeout) + + self.running = False + self.client = None + + if client == None: + self.client = OSCClient(server=self) + else: + self.setClient(client) + + def setClient(self, client): + """Associate this Server with a new local Client instance, closing the Client this Server is currently using. + """ + if not isinstance(client, OSCClient): + raise ValueError("'client' argument is not a valid OSCClient object") + + if client.server != None: + raise OSCServerError("Provided OSCClient already has an OSCServer-instance: %s" % str(client.server)) + + # Server socket is already listening at this point, so we can't use the client's socket. + # we'll have to force our socket on the client... + client_address = client.address() # client may be already connected + client.close() # shut-down that socket + + # force our socket upon the client + client.socket = self.socket.dup() + client.socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, client.sndbuf_size) + client._fd = client.socket.fileno() + client.server = self + + if client_address: + client.connect(client_address) + if not self.return_port: + self.return_port = client_address[1] + + if self.client != None: + self.client.close() + + self.client = client + + def serve_forever(self): + """Handle one request at a time until server is closed.""" + self.running = True + while self.running: + self.handle_request() # this times-out when no data arrives. + + def close(self): + """Stops serving requests, closes server (socket), closes used client + """ + self.running = False + self.client.close() + self.server_close() + + def __str__(self): + """Returns a string containing this Server's Class-name, software-version and local bound address (if any) + """ + out = self.__class__.__name__ + out += " v%s.%s-%s" % version + addr = self.address() + if addr: + out += " listening on osc://%s" % getUrlStr(addr) + else: + out += " (unbound)" + + return out + + def __eq__(self, other): + """Compare function. + """ + if not isinstance(other, self.__class__): + return False + + return cmp(self.socket._sock, other.socket._sock) + + def __ne__(self, other): + """Compare function. + """ + return not self.__eq__(other) + + def address(self): + """Returns a (host,port) tuple of the local address this server is bound to, + or None if not bound to any address. + """ + try: + return self.socket.getsockname() + except socket.error: + return None + + def setReturnPort(self, port): + """Set the destination UDP-port for replies returning from this server to the remote client + """ + if (port > 1024) and (port < 65536): + self.return_port = port + else: + self.return_port = None + + + def setSrvInfoPrefix(self, pattern): + """Set the first part of OSC-address (pattern) this server will use to reply to server-info requests. + """ + if len(pattern): + pattern = '/' + pattern.strip('/') + + self.info_prefix = pattern + + def setSrvErrorPrefix(self, pattern=""): + """Set the OSC-address (pattern) this server will use to report errors occuring during + received message handling to the remote client. + + If pattern is empty (default), server-errors are not reported back to the client. + """ + if len(pattern): + pattern = '/' + pattern.strip('/') + + self.error_prefix = pattern + + def addDefaultHandlers(self, prefix="", info_prefix="/info", error_prefix="/error"): + """Register a default set of OSC-address handlers with this Server: + - 'default' -> noCallback_handler + the given prefix is prepended to all other callbacks registered by this method: + - ' serverInfo_handler + - ' -> msgPrinter_handler + - '/print' -> msgPrinter_handler + and, if the used Client supports it; + - '/subscribe' -> subscription_handler + - '/unsubscribe' -> subscription_handler + + Note: the given 'error_prefix' argument is also set as default 'error_prefix' for error-messages + *sent from* this server. This is ok, because error-messages generally do not elicit a reply from the receiver. + + To do this with the serverInfo-prefixes would be a bad idea, because if a request received on '/info' (for example) + would send replies to '/info', this could potentially cause a never-ending loop of messages! + Do *not* set the 'info_prefix' here (for incoming serverinfo requests) to the same value as given to + the setSrvInfoPrefix() method (for *replies* to incoming serverinfo requests). + For example, use '/info' for incoming requests, and '/inforeply' or '/serverinfo' or even just '/print' as the + info-reply prefix. + """ + self.error_prefix = error_prefix + self.addMsgHandler('default', self.noCallback_handler) + self.addMsgHandler(prefix + info_prefix, self.serverInfo_handler) + self.addMsgHandler(prefix + error_prefix, self.msgPrinter_handler) + self.addMsgHandler(prefix + '/print', self.msgPrinter_handler) + + if isinstance(self.client, OSCMultiClient): + self.addMsgHandler(prefix + '/subscribe', self.subscription_handler) + self.addMsgHandler(prefix + '/unsubscribe', self.subscription_handler) + + def printErr(self, txt): + """Writes 'OSCServer: txt' to sys.stderr + """ + sys.stderr.write("OSCServer: %s\n" % txt) + + def sendOSCerror(self, txt, client_address): + """Sends 'txt', encapsulated in an OSCMessage to the default 'error_prefix' OSC-addres. + Message is sent to the given client_address, with the default 'return_port' overriding + the client_address' port, if defined. + """ + lines = txt.split('\n') + if len(lines) == 1: + msg = OSCMessage(self.error_prefix) + msg.append(lines[0]) + elif len(lines) > 1: + msg = OSCBundle(self.error_prefix) + for line in lines: + msg.append(line) + else: + return + + if self.return_port: + client_address = (client_address[0], self.return_port) + + self.client.sendto(msg, client_address) + + def reportErr(self, txt, client_address): + """Writes 'OSCServer: txt' to sys.stderr + If self.error_prefix is defined, sends 'txt' as an OSC error-message to the client(s) + (see printErr() and sendOSCerror()) + """ + self.printErr(txt) + + if len(self.error_prefix): + self.sendOSCerror(txt, client_address) + + def sendOSCinfo(self, txt, client_address): + """Sends 'txt', encapsulated in an OSCMessage to the default 'info_prefix' OSC-addres. + Message is sent to the given client_address, with the default 'return_port' overriding + the client_address' port, if defined. + """ + lines = txt.split('\n') + if len(lines) == 1: + msg = OSCMessage(self.info_prefix) + msg.append(lines[0]) + elif len(lines) > 1: + msg = OSCBundle(self.info_prefix) + for line in lines: + msg.append(line) + else: + return + + if self.return_port: + client_address = (client_address[0], self.return_port) + + self.client.sendto(msg, client_address) + + ### + # Message-Handler callback functions + ### + + def handle_error(self, request, client_address): + """Handle an exception in the Server's callbacks gracefully. + Writes the error to sys.stderr and, if the error_prefix (see setSrvErrorPrefix()) is set, + sends the error-message as reply to the client + """ + (e_type, e) = sys.exc_info()[:2] + self.printErr("%s on request from %s: %s" % (e_type.__name__, getUrlStr(client_address), str(e))) + + if self.print_tracebacks: + import traceback + traceback.print_exc() # XXX But this goes to stderr! + + if len(self.error_prefix): + self.sendOSCerror("%s: %s" % (e_type.__name__, str(e)), client_address) + + def noCallback_handler(self, addr, tags, data, client_address): + """Example handler for OSCMessages. + All registerd handlers must accept these three arguments: + - addr (string): The OSC-address pattern of the received Message + (the 'addr' string has already been matched against the handler's registerd OSC-address, + but may contain '*'s & such) + - tags (string): The OSC-typetags of the received message's arguments. (without the preceding comma) + - data (list): The OSCMessage's arguments + Note that len(tags) == len(data) + - client_address ((host, port) tuple): the host & port this message originated from. + + a Message-handler function may return None, but it could also return an OSCMessage (or OSCBundle), + which then gets sent back to the client. + + This handler prints a "No callback registered to handle ..." message. + Returns None + """ + self.reportErr("No callback registered to handle OSC-address '%s'" % addr, client_address) + + def msgPrinter_handler(self, addr, tags, data, client_address): + """Example handler for OSCMessages. + All registerd handlers must accept these three arguments: + - addr (string): The OSC-address pattern of the received Message + (the 'addr' string has already been matched against the handler's registerd OSC-address, + but may contain '*'s & such) + - tags (string): The OSC-typetags of the received message's arguments. (without the preceding comma) + - data (list): The OSCMessage's arguments + Note that len(tags) == len(data) + - client_address ((host, port) tuple): the host & port this message originated from. + + a Message-handler function may return None, but it could also return an OSCMessage (or OSCBundle), + which then gets sent back to the client. + + This handler prints the received message. + Returns None + """ + txt = "OSCMessage '%s' from %s: " % (addr, getUrlStr(client_address)) + txt += str(data) + + self.printErr(txt) # strip trailing comma & space + + def serverInfo_handler(self, addr, tags, data, client_address): + """Example handler for OSCMessages. + All registerd handlers must accept these three arguments: + - addr (string): The OSC-address pattern of the received Message + (the 'addr' string has already been matched against the handler's registerd OSC-address, + but may contain '*'s & such) + - tags (string): The OSC-typetags of the received message's arguments. (without the preceding comma) + - data (list): The OSCMessage's arguments + Note that len(tags) == len(data) + - client_address ((host, port) tuple): the host & port this message originated from. + + a Message-handler function may return None, but it could also return an OSCMessage (or OSCBundle), + which then gets sent back to the client. + + This handler returns a reply to the client, which can contain various bits of information + about this server, depending on the first argument of the received OSC-message: + - 'help' | 'info' : Reply contains server type & version info, plus a list of + available 'commands' understood by this handler + - 'list' | 'ls' : Reply is a bundle of 'address ' messages, listing the server's + OSC address-space. + - 'clients' | 'targets' : Reply is a bundle of 'target osc://:[] [] [...]' + messages, listing the local Client-instance's subscribed remote clients. + """ + if len(data) == 0: + return None + + cmd = data.pop(0) + + reply = None + if cmd in ('help', 'info'): + reply = OSCBundle(self.info_prefix) + reply.append(('server', str(self))) + reply.append(('info_command', "ls | list : list OSC address-space")) + reply.append(('info_command', "clients | targets : list subscribed clients")) + elif cmd in ('ls', 'list'): + reply = OSCBundle(self.info_prefix) + for addr in list(self.callbacks.keys()): + reply.append(('address', addr)) + elif cmd in ('clients', 'targets'): + if hasattr(self.client, 'getOSCTargetStrings'): + reply = OSCBundle(self.info_prefix) + for trg in self.client.getOSCTargetStrings(): + reply.append(('target',) + trg) + else: + cli_addr = self.client.address() + if cli_addr: + reply = OSCMessage(self.info_prefix) + reply.append(('target', "osc://%s/" % getUrlStr(cli_addr))) + else: + self.reportErr("unrecognized command '%s' in /info request from osc://%s. Try 'help'" % (cmd, getUrlStr(client_address)), client_address) + + return reply + + def _subscribe(self, data, client_address): + """Handle the actual subscription. the provided 'data' is concatenated together to form a + ':[] [] [...]' string, which is then passed to + parseUrlStr() & parseFilterStr() to actually retreive , , etc. + + This 'long way 'round' approach (almost) guarantees that the subscription works, + regardless of how the bits of the are encoded in 'data'. + """ + url = "" + have_port = False + for item in data: + if (isinstance(item,int)) and not have_port: + url += ":%d" % item + have_port = True + elif isinstance(item,str): + url += item + + (addr, tail) = parseUrlStr(url) + (prefix, filters) = parseFilterStr(tail) + + if addr != None: + (host, port) = addr + if not host: + host = client_address[0] + if not port: + port = client_address[1] + addr = (host, port) + else: + addr = client_address + + self.client._setTarget(addr, prefix, filters) + + trg = self.client.getOSCTargetStr(addr) + if trg[0] != None: + reply = OSCMessage(self.info_prefix) + reply.append(('target',) + trg) + return reply + + def _unsubscribe(self, data, client_address): + """Handle the actual unsubscription. the provided 'data' is concatenated together to form a + ':[]' string, which is then passed to + parseUrlStr() to actually retreive , & . + + This 'long way 'round' approach (almost) guarantees that the unsubscription works, + regardless of how the bits of the are encoded in 'data'. + """ + url = "" + have_port = False + for item in data: + if (isinstance(item,int)) and not have_port: + url += ":%d" % item + have_port = True + elif isinstance(item,str): + url += item + + (addr, _) = parseUrlStr(url) + + if addr == None: + addr = client_address + else: + (host, port) = addr + if not host: + host = client_address[0] + if not port: + try: + (host, port) = self.client._searchHostAddr(host) + except NotSubscribedError: + port = client_address[1] + + addr = (host, port) + + try: + self.client._delTarget(addr) + except NotSubscribedError as e: + txt = "%s: %s" % (e.__class__.__name__, str(e)) + self.printErr(txt) + + reply = OSCMessage(self.error_prefix) + reply.append(txt) + return reply + + def subscription_handler(self, addr, tags, data, client_address): + """Handle 'subscribe' / 'unsubscribe' requests from remote hosts, + if the local Client supports this (i.e. OSCMultiClient). + + Supported commands: + - 'help' | 'info' : Reply contains server type & version info, plus a list of + available 'commands' understood by this handler + - 'list' | 'ls' : Reply is a bundle of 'target osc://:[] [] [...]' + messages, listing the local Client-instance's subscribed remote clients. + - '[subscribe | listen | sendto | target] [ ...] : Subscribe remote client/server at , + and/or set message-filters for messages being sent to the subscribed host, with the optional + arguments. Filters are given as OSC-addresses (or '*') prefixed by a '+' (send matching messages) or + a '-' (don't send matching messages). The wildcard '*', '+*' or '+/*' means 'send all' / 'filter none', + and '-*' or '-/*' means 'send none' / 'filter all' (which is not the same as unsubscribing!) + Reply is an OSCMessage with the (new) subscription; 'target osc://:[] [] [...]' + - '[unsubscribe | silence | nosend | deltarget] : Unsubscribe remote client/server at + If the given isn't subscribed, a NotSubscribedError-message is printed (and possibly sent) + + The given to the subscribe/unsubscribe handler should be of the form: + '[osc://][][:][]', where any or all components can be omitted. + + If is not specified, the IP-address of the message's source is used. + If is not specified, the is first looked up in the list of subscribed hosts, and if found, + the associated port is used. + If is not specified and is not yet subscribed, the message's source-port is used. + If is specified on subscription, is prepended to the OSC-address of all messages + sent to the subscribed host. + If is specified on unsubscription, the subscribed host is only unsubscribed if the host, + port and prefix all match the subscription. + If is not specified on unsubscription, the subscribed host is unsubscribed if the host and port + match the subscription. + """ + if not isinstance(self.client, OSCMultiClient): + raise OSCServerError("Local %s does not support subsctiptions or message-filtering" % self.client.__class__.__name__) + + addr_cmd = addr.split('/')[-1] + + if len(data): + if data[0] in ('help', 'info'): + reply = OSCBundle(self.info_prefix) + reply.append(('server', str(self))) + reply.append(('subscribe_command', "ls | list : list subscribed targets")) + reply.append(('subscribe_command', "[subscribe | listen | sendto | target] [ ...] : subscribe to messages, set filters")) + reply.append(('subscribe_command', "[unsubscribe | silence | nosend | deltarget] : unsubscribe from messages")) + return reply + + if data[0] in ('ls', 'list'): + reply = OSCBundle(self.info_prefix) + for trg in self.client.getOSCTargetStrings(): + reply.append(('target',) + trg) + return reply + + if data[0] in ('subscribe', 'listen', 'sendto', 'target'): + return self._subscribe(data[1:], client_address) + + if data[0] in ('unsubscribe', 'silence', 'nosend', 'deltarget'): + return self._unsubscribe(data[1:], client_address) + + if addr_cmd in ('subscribe', 'listen', 'sendto', 'target'): + return self._subscribe(data, client_address) + + if addr_cmd in ('unsubscribe', 'silence', 'nosend', 'deltarget'): + return self._unsubscribe(data, client_address) + +class ForkingOSCServer(ForkingMixIn, OSCServer): + """An Asynchronous OSCServer. + This server forks a new process to handle each incoming request. + """ + # set the RequestHandlerClass, will be overridden by ForkingOSCServer & ThreadingOSCServer + RequestHandlerClass = ThreadingOSCRequestHandler + +class ThreadingOSCServer(ThreadingMixIn, OSCServer): + """An Asynchronous OSCServer. + This server starts a new thread to handle each incoming request. + """ + # set the RequestHandlerClass, will be overridden by ForkingOSCServer & ThreadingOSCServer + RequestHandlerClass = ThreadingOSCRequestHandler + +###### +# +# OSCError classes +# +###### + +class OSCError(Exception): + """Base Class for all OSC-related errors + """ + def __init__(self, message): + self.message = message + + def __str__(self): + return self.message + +class OSCClientError(OSCError): + """Class for all OSCClient errors + """ + pass + +class OSCServerError(OSCError): + """Class for all OSCServer errors + """ + pass + +class NoCallbackError(OSCServerError): + """This error is raised (by an OSCServer) when an OSCMessage with an 'unmatched' address-pattern + is received, and no 'default' handler is registered. + """ + def __init__(self, pattern): + """The specified 'pattern' should be the OSC-address of the 'unmatched' message causing the error to be raised. + """ + self.message = "No callback registered to handle OSC-address '%s'" % pattern + +class NotSubscribedError(OSCClientError): + """This error is raised (by an OSCMultiClient) when an attempt is made to unsubscribe a host + that isn't subscribed. + """ + def __init__(self, addr, prefix=None): + if prefix: + url = getUrlStr(addr, prefix) + else: + url = getUrlStr(addr, '') + + self.message = "Target osc://%s is not subscribed" % url + +###### +# +# OSC over streaming transport layers (usually TCP) +# +# Note from the OSC 1.0 specifications about streaming protocols: +# +# The underlying network that delivers an OSC packet is responsible for +# delivering both the contents and the size to the OSC application. An OSC +# packet can be naturally represented by a datagram by a network protocol such +# as UDP. In a stream-based protocol such as TCP, the stream should begin with +# an int32 giving the size of the first packet, followed by the contents of the +# first packet, followed by the size of the second packet, etc. +# +# The contents of an OSC packet must be either an OSC Message or an OSC Bundle. +# The first byte of the packet's contents unambiguously distinguishes between +# these two alternatives. +# +###### + +class OSCStreamRequestHandler(StreamRequestHandler, OSCAddressSpace): + """ This is the central class of a streaming OSC server. If a client + connects to the server, the server instantiates a OSCStreamRequestHandler + for each new connection. This is fundamentally different to a packet + oriented server which has a single address space for all connections. + This connection based (streaming) OSC server maintains an address space + for each single connection, because usually tcp server spawn a new thread + or process for each new connection. This would generate severe + multithreading synchronization problems when each thread would operate on + the same address space object. Therefore: To implement a streaming/TCP OSC + server a custom handler must be implemented which implements the + setupAddressSpace member in which it creates its own address space for this + very connection. This has been done within the testbench and can serve as + inspiration. + """ + def __init__(self, request, client_address, server): + """ Initialize all base classes. The address space must be initialized + before the stream request handler because the initialization function + of the stream request handler calls the setup member which again + requires an already initialized address space. + """ + self._txMutex = threading.Lock() + OSCAddressSpace.__init__(self) + StreamRequestHandler.__init__(self, request, client_address, server) + + def _unbundle(self, decoded): + """Recursive bundle-unpacking function""" + if decoded[0] != "#bundle": + self.replies += self.dispatchMessage(decoded[0], decoded[1][1:], decoded[2:], self.client_address) + return + + now = time.time() + timetag = decoded[1] + if (timetag > 0.) and (timetag > now): + time.sleep(timetag - now) + + for msg in decoded[2:]: + self._unbundle(msg) + + def setup(self): + StreamRequestHandler.setup(self) + print("SERVER: New client connection.") + self.setupAddressSpace() + self.server._clientRegister(self) + + def setupAddressSpace(self): + """ Override this function to customize your address space. """ + pass + + def finish(self): + StreamRequestHandler.finish(self) + self.server._clientUnregister(self) + print("SERVER: Client connection handled.") + def _transmit(self, data): + sent = 0 + while sent < len(data): + tmp = self.connection.send(data[sent:]) + if tmp == 0: + return False + sent += tmp + return True + def _transmitMsg(self, msg): + """Send an OSC message over a streaming socket. Raises exception if it + should fail. If everything is transmitted properly, True is returned. If + socket has been closed, False. + """ + if not isinstance(msg, OSCMessage): + raise TypeError("'msg' argument is not an OSCMessage or OSCBundle object") + + try: + binary = msg.getBinary() + length = len(binary) + # prepend length of packet before the actual message (big endian) + len_big_endian = array.array('c', '\0' * 4) + struct.pack_into(">L", len_big_endian, 0, length) + len_big_endian = len_big_endian.tostring() + if self._transmit(len_big_endian) and self._transmit(binary): + return True + return False + except socket.error as e: + if e[0] == errno.EPIPE: # broken pipe + return False + raise e + + def _receive(self, count): + """ Receive a certain amount of data from the socket and return it. If the + remote end should be closed in the meanwhile None is returned. + """ + chunk = self.connection.recv(count) + if not chunk or len(chunk) == 0: + return None + while len(chunk) < count: + tmp = self.connection.recv(count - len(chunk)) + if not tmp or len(tmp) == 0: + return None + chunk = chunk + tmp + return chunk + + def _receiveMsg(self): + """ Receive OSC message from a socket and decode. + If an error occurs, None is returned, else the message. + """ + # get OSC packet size from stream which is prepended each transmission + chunk = self._receive(4) + if chunk == None: + print("SERVER: Socket has been closed.") + return None + # extract message length from big endian unsigned long (32 bit) + slen = struct.unpack(">L", chunk)[0] + # receive the actual message + chunk = self._receive(slen) + if chunk == None: + print("SERVER: Socket has been closed.") + return None + # decode OSC data and dispatch + msg = decodeOSC(chunk) + if msg == None: + raise OSCError("SERVER: Message decoding failed.") + return msg + + def handle(self): + """ + Handle a connection. + """ + # set socket blocking to avoid "resource currently not available" + # exceptions, because the connection socket inherits the settings + # from the listening socket and this times out from time to time + # in order to provide a way to shut the server down. But we want + # clean and blocking behaviour here + self.connection.settimeout(None) + + print("SERVER: Entered server loop") + try: + while True: + decoded = self._receiveMsg() + if decoded == None: + return + elif len(decoded) <= 0: + # if message decoding fails we try to stay in sync but print a message + print("OSC stream server: Spurious message received.") + continue + + self.replies = [] + self._unbundle(decoded) + + if len(self.replies) > 1: + msg = OSCBundle() + for reply in self.replies: + msg.append(reply) + elif len(self.replies) == 1: + msg = self.replies[0] + else: + # no replies, continue receiving + continue + self._txMutex.acquire() + txOk = self._transmitMsg(msg) + self._txMutex.release() + if not txOk: + break + + except socket.error as e: + if e[0] == errno.ECONNRESET: + # if connection has been reset by client, we do not care much + # about it, we just assume our duty fullfilled + print("SERVER: Connection has been reset by peer.") + else: + raise e + + def sendOSC(self, oscData): + """ This member can be used to transmit OSC messages or OSC bundles + over the client/server connection. It is thread save. + """ + self._txMutex.acquire() + result = self._transmitMsg(oscData) + self._txMutex.release() + return result + +""" TODO Note on threaded unbundling for streaming (connection oriented) +transport: + +Threaded unbundling as implemented in ThreadingOSCServer must be implemented in +a different way for the streaming variant, because contrary to the datagram +version the streaming handler is instantiated only once per connection. This +leads to the problem (if threaded unbundling is implemented as in OSCServer) +that all further message reception is blocked until all (previously received) +pending messages are processed. + +Each StreamRequestHandler should provide a so called processing queue in which +all pending messages or subbundles are inserted to be processed in the future). +When a subbundle or message gets queued, a mechanism must be provided that +those messages get invoked when time asks for them. There are the following +opportunities: + - a timer is started which checks at regular intervals for messages in the + queue (polling - requires CPU resources) + - a dedicated timer is started for each message (requires timer resources) +""" + +class OSCStreamingServer(TCPServer): + """ A connection oriented (TCP/IP) OSC server. + """ + + # define a socket timeout, so the serve_forever loop can actually exit. + # with 2.6 and server.shutdown this wouldn't be necessary + socket_timeout = 1 + + # this is the class which handles a new connection. Override this for a + # useful customized server. See the testbench for an example + RequestHandlerClass = OSCStreamRequestHandler + + def __init__(self, address): + """Instantiate an OSCStreamingServer. + - server_address ((host, port) tuple): the local host & UDP-port + the server listens for new connections. + """ + self._clientList = [] + self._clientListMutex = threading.Lock() + TCPServer.__init__(self, address, self.RequestHandlerClass) + self.socket.settimeout(self.socket_timeout) + + def serve_forever(self): + """Handle one request at a time until server is closed. + Had to add this since 2.5 does not support server.shutdown() + """ + self.running = True + while self.running: + self.handle_request() # this times-out when no data arrives. + + def start(self): + """ Start the server thread. """ + self._server_thread = threading.Thread(target=self.serve_forever) + self._server_thread.setDaemon(True) + self._server_thread.start() + + def stop(self): + """ Stop the server thread and close the socket. """ + self.running = False + self._server_thread.join() + self.server_close() + # 2.6 only + #self.shutdown() + + def _clientRegister(self, client): + """ Gets called by each request/connection handler when connection is + established to add itself to the client list + """ + self._clientListMutex.acquire() + self._clientList.append(client) + self._clientListMutex.release() + + def _clientUnregister(self, client): + """ Gets called by each request/connection handler when connection is + lost to remove itself from the client list + """ + self._clientListMutex.acquire() + self._clientList.remove(client) + self._clientListMutex.release() + + def broadcastToClients(self, oscData): + """ Send OSC message or bundle to all connected clients. """ + result = True + for client in self._clientList: + result = result and client.sendOSC(oscData) + return result + +class OSCStreamingServerThreading(ThreadingMixIn, OSCStreamingServer): + pass + """ Implements a server which spawns a separate thread for each incoming + connection. Care must be taken since the OSC address space is for all + the same. + """ + +class OSCStreamingClient(OSCAddressSpace): + """ OSC streaming client. + A streaming client establishes a connection to a streaming server but must + be able to handle replies by the server as well. To accomplish this the + receiving takes place in a secondary thread, because no one knows if we + have to expect a reply or not, i.e. synchronous architecture doesn't make + much sense. + Replies will be matched against the local address space. If message + handlers access code of the main thread (where the client messages are sent + to the server) care must be taken e.g. by installing sychronization + mechanisms or by using an event dispatcher which can handle events + originating from other threads. + """ + # set outgoing socket buffer size + sndbuf_size = 4096 * 8 + rcvbuf_size = 4096 * 8 + + def __init__(self): + self._txMutex = threading.Lock() + OSCAddressSpace.__init__(self) + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, self.sndbuf_size) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, self.rcvbuf_size) + self.socket.settimeout(1.0) + self._running = False + + def _receiveWithTimeout(self, count): + chunk = str() + while len(chunk) < count: + try: + tmp = self.socket.recv(count - len(chunk)) + except socket.timeout: + if not self._running: + print("CLIENT: Socket timed out and termination requested.") + return None + else: + continue + except socket.error as e: + if e[0] == errno.ECONNRESET: + print("CLIENT: Connection reset by peer.") + return None + else: + raise e + if not tmp or len(tmp) == 0: + print("CLIENT: Socket has been closed.") + return None + chunk = chunk + tmp + return chunk + def _receiveMsgWithTimeout(self): + """ Receive OSC message from a socket and decode. + If an error occurs, None is returned, else the message. + """ + # get OSC packet size from stream which is prepended each transmission + chunk = self._receiveWithTimeout(4) + if not chunk: + return None + # extract message length from big endian unsigned long (32 bit) + slen = struct.unpack(">L", chunk)[0] + # receive the actual message + chunk = self._receiveWithTimeout(slen) + if not chunk: + return None + # decode OSC content + msg = decodeOSC(chunk) + if msg == None: + raise OSCError("CLIENT: Message decoding failed.") + return msg + + def _receiving_thread_entry(self): + print("CLIENT: Entered receiving thread.") + self._running = True + while self._running: + decoded = self._receiveMsgWithTimeout() + if not decoded: + break + elif len(decoded) <= 0: + continue + + self.replies = [] + self._unbundle(decoded) + if len(self.replies) > 1: + msg = OSCBundle() + for reply in self.replies: + msg.append(reply) + elif len(self.replies) == 1: + msg = self.replies[0] + else: + continue + self._txMutex.acquire() + txOk = self._transmitMsgWithTimeout(msg) + self._txMutex.release() + if not txOk: + break + print("CLIENT: Receiving thread terminated.") + + def _unbundle(self, decoded): + if decoded[0] != "#bundle": + self.replies += self.dispatchMessage(decoded[0], decoded[1][1:], decoded[2:], self.socket.getpeername()) + return + + now = time.time() + timetag = decoded[1] + if (timetag > 0.) and (timetag > now): + time.sleep(timetag - now) + + for msg in decoded[2:]: + self._unbundle(msg) + + def connect(self, address): + self.socket.connect(address) + self.receiving_thread = threading.Thread(target=self._receiving_thread_entry) + self.receiving_thread.start() + + def close(self): + # let socket time out + self._running = False + self.receiving_thread.join() + self.socket.close() + + def _transmitWithTimeout(self, data): + sent = 0 + while sent < len(data): + try: + tmp = self.socket.send(data[sent:]) + except socket.timeout: + if not self._running: + print("CLIENT: Socket timed out and termination requested.") + return False + else: + continue + except socket.error as e: + if e[0] == errno.ECONNRESET: + print("CLIENT: Connection reset by peer.") + return False + else: + raise e + if tmp == 0: + return False + sent += tmp + return True + + def _transmitMsgWithTimeout(self, msg): + if not isinstance(msg, OSCMessage): + raise TypeError("'msg' argument is not an OSCMessage or OSCBundle object") + binary = msg.getBinary() + length = len(binary) + # prepend length of packet before the actual message (big endian) + len_big_endian = array.array('c', '\0' * 4) + struct.pack_into(">L", len_big_endian, 0, length) + len_big_endian = len_big_endian.tostring() + if self._transmitWithTimeout(len_big_endian) and self._transmitWithTimeout(binary): + return True + else: + return False + + def sendOSC(self, msg): + """Send an OSC message or bundle to the server. Returns True on success. + """ + self._txMutex.acquire() + txOk = self._transmitMsgWithTimeout(msg) + self._txMutex.release() + return txOk + + def __str__(self): + """Returns a string containing this Client's Class-name, software-version + and the remote-address it is connected to (if any) + """ + out = self.__class__.__name__ + out += " v%s.%s-%s" % version + addr = self.socket.getpeername() + if addr: + out += " connected to osc://%s" % getUrlStr(addr) + else: + out += " (unconnected)" + + return out + + def __eq__(self, other): + """Compare function. + """ + if not isinstance(other, self.__class__): + return False + + isequal = cmp(self.socket._sock, other.socket._sock) + if isequal and self.server and other.server: + return cmp(self.server, other.server) + + return isequal + + def __ne__(self, other): + """Compare function. + """ + return not self.__eq__(other) diff --git a/libs/__init__.py b/libs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/__init__.pyc b/libs/__init__.pyc new file mode 100644 index 0000000..81e9352 Binary files /dev/null and b/libs/__init__.pyc differ diff --git a/libs/artnet.py b/libs/artnet.py new file mode 100644 index 0000000..aafd77f --- /dev/null +++ b/libs/artnet.py @@ -0,0 +1,364 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + + +""" + +ArtNet/DMX Handler : +v0.7.0 + + +by Sam Neurohack +from /team/laser + +Artnet receving code from https://github.com/kongr45gpen/simply-artnet +Laser selection +one universe / laser + +Plugin selection +banck change/scene/ + +""" + +import random +import pysimpledmx +from serial.tools import list_ports +import serial,time +from threading import Thread +import socket +import struct +import types +from sys import platform, version +import sys +import argparse, traceback +import os + +is_py2 = version[0] == '2' +if is_py2: + from OSC import OSCServer, OSCClient, OSCMessage +else: + from OSC3 import OSCServer, OSCClient, OSCMessage + +ljpath = r'%s' % os.getcwd().replace('\\','/') + +# import from shell + +#sys.path.append('../../libs') + +#import from LJ +sys.path.append(ljpath +'/libs/') + +import lj23 as lj + + +# +# Init +# + +OSCinPort = 8032 + + +sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) +sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) +sock.bind(('',6454)) + +dmxeq = {} +dmxstates = [] +dmxinit = False +universe = [] + + +for i in range(1,514): + dmxstates.append(-1) + + +print ("") +print ("Artnet v0.1") +print ("Arguments parsing if needed...") +argsparser = argparse.ArgumentParser(description="Artnet & DMX for LJ") +argsparser.add_argument("-u","--universe",help="Universe, not implemented (0 by default)",type=int) +argsparser.add_argument("-s","--subuniverse",help="Subniverse, not implemented (0 by default)",type=int) +argsparser.add_argument("-r","--redisIP",help="IP of the Redis server used by LJ (127.0.0.1 by default) ",type=str) +argsparser.add_argument("-m","--myIP",help="Local IP (127.0.0.1 by default) ",type=str) +argsparser.add_argument("-v","--verbose",help="Verbosity level (0 by default)",type=int) + +args = argsparser.parse_args() + + +# Universe +if args.universe: + universenb = args.universe +else: + universenb = 0 + +# Universe +if args.subuniverse: + subuniversenb = args.subuniverse +else: + subuniversenb = 0 + +# Debug level +if args.verbose: + debug = args.verbose +else: + debug = 0 + +# Redis Computer IP +if args.redisIP != None: + redisIP = args.redisIP +else: + redisIP = '127.0.0.1' + +# myIP +if args.myIP != None: + myIP = args.myIP +else: + myIP = '127.0.0.1' + +r = lj.Config(redisIP, 255, "artnet") + +lj.WebStatus("Artnet init...") + +def lhex(h): + return ':'.join(x.encode('hex') for x in h) + + +def senddmx0(): + for channel in range (1,512): + senddmx(channel,0) + +def senddmx(channel, value): + + print("Setting channel %d to %d" % (i,value)) + #mydmx.setChannel((channel + 1 ), value, autorender=True) + # calling render() is better more reliable to actually sending data + # Some strange bug. Need to add one to required dmx channel is done automatically + mydmx.setChannel((channel ), value) + mydmx.render() + print("Sending DMX Channel : ", str(channel), " value : ", str(value)) + +def updateDmxValue(channel, val): + + # + if dmxstates[channel] == -1: + dmxstates[channel] = val + + # DMX UPDATE!!! WOW!!! + if dmxstates[channel] != val: + dmxstates[channel] = val + print("updating channel", channel, "with ", val ) + if mydmx != False: + senddmx(channel, ord(val)) + + +# Search for DMX devices + +#ljnozoids.WebStatus("Available serial devices") + +print("") +print("Available serial devices...") +ports = list(list_ports.comports()) + +portnumber = 0 + +# Get all serial ports names +for i, p in enumerate(ports): + + print(i, ":", p) + + if p[0]== "/dev/ttyUSB0": + portname[portnumber] = p[0] + portnumber += 1 + + + if platform == 'darwin' and p[1].find("DMX USB PRO") != -1: + portname[portnumber] = p[0] + portnumber += 1 + + +# ljnozoids.WebStatus("Found " + str(portnumber) +" Nozoids.") + +print("Found", portnumber, "DMX devices") + + +if portnumber > 0: + + print("with serial names", portname) + mydmx = pysimpledmx.DMXConnection(gstt.serdmx[0]) + senddmx0() + time.sleep(1) + + # Send a random value to channel 1 + vrand=random.randint(0,255) + senddmx(1,vrand) + +else: + mydmx = False + print("No DMX found, Art-Net receiver only.") + + +# +# OSC +# + +oscserver = OSCServer( (myIP, OSCinPort) ) +oscserver.timeout = 0 +#oscrun = True + +# this method of reporting timeouts only works by convention +# that before calling handle_request() field .timed_out is +# set to False +def handle_timeout(self): + self.timed_out = True + +# funny python's way to add a method to an instance of a class +oscserver.handle_timeout = types.MethodType(handle_timeout, oscserver) + + +# default handler +def OSChandler(path, tags, args, source): + + oscaddress = ''.join(path.split("/")) + print("Default OSC Handler : msg from Client : " + str(source[0]),) + print("OSC address", path, "with",) + if len(args) > 0: + print("args", args) + else: + print("noargs") + #oscIPout = str(source[0]) + #osclient.connect((oscIPout, oscPORTout)) + + +# RAW OSC Frame available ? +def OSCframe(): + + # clear timed_out flag + print ("oscframe") + oscserver.timed_out = False + # handle all pending requests then return + while not oscserver.timed_out: + oscserver.handle_request() + + +# Stop osc server +def OSCstop(): + + oscrun = False + oscserver.close() + + +# /sendmx channel value +def OSCsendmx(path, tags, args, source): + + channel = args[0] + val = args[1] + updateDmxValue(channel, val) + + +lj.addOSCdefaults(oscserver) +lj.SendLJ("/pong", "artnet") +lj.WebStatus("Artnet Running...") + +oscserver.addMsgHandler( "/sendmx", OSCsendmx ) + +# +# Running... +# + + +print ("Starting, use Ctrl+C to stop") +print (lj.oscrun) + +try: + + while lj.oscrun: + + data = sock.recv(10240) + if len(data) < 20: + continue + + if data[0:7] != "Art-Net" or data[7] != "\0": + print("artnet package") + #lj.WebStatus("Artnet package") + continue + OSCframe() + + if ord(data[8]) != 0x00 or ord(data[9]) != 0x50: + print("OpDmx") + continue + + print ("oscrun", lj.oscrun) + protverhi = ord(data[10]) + protverlo = ord(data[11]) + sequence = ord(data[12]) + physical = ord(data[13]) + subuni = ord(data[14]) + net = ord(data[15]) + lengthhi = ord(data[16]) + length = ord(data[17]) + dmx = data[18:] + + print (data[0:7], "version :",lhex(data[10])+lhex(data[11]), "sequence :", sequence, "physical", physical, "subuni",subuni,"net", net) + + for i in xrange(0,510): + updateDmxValue(i+1,dmx[i]) + + +except Exception: + traceback.print_exc() + +finally: + + lj.ClosePlugin() + sock.close() + OSCstop() +''' +import sys, socket, math, time +from ctypes import * + +class Artnet: + class ArtNetDMXOut(LittleEndianStructure): + PORT = 0x1936 + _fields_ = [("id", c_char * 8), + ("opcode", c_ushort), + ("protverh", c_ubyte), + ("protver", c_ubyte), + ("sequence", c_ubyte), + ("physical", c_ubyte), + ("universe", c_ushort), + ("lengthhi", c_ubyte), + ("length", c_ubyte), + ("payload", c_ubyte * 512)] + + def __init__(self): + self.id = b"Art-Net" + self.opcode = 0x5000 + self.protver = 14 + self.universe = 0 + self.lengthhi = 2 + + def __init__(self): + self.artnet = Artnet.ArtNetDMXOut() + self.S = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) + for i in range(512): + self.artnet.payload[i] = 0 + + def send(self,data,IP,port): + # send(送るデータ,IPアドレス,ポート番号) + self.artnet.universe = port + for i in range(512): + if(i < len(data)): + self.artnet.payload[i] = data[i] + else: + break + self.S.sendto(self.artnet,(IP,Artnet.ArtNetDMXOut.PORT)) + +if __name__ == '__main__': + artnet = Artnet() + data = [0] * 512 + for i in range(150): + data[i*3+0] = 0 + data[i*3+1] = 0 + data[i*3+2] = 0 +artnet.send(data,"133.15.42.111",5) +''' diff --git a/libs/audio.py b/libs/audio.py new file mode 100755 index 0000000..11126e1 --- /dev/null +++ b/libs/audio.py @@ -0,0 +1,292 @@ + +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Audio Spectrum analyser +v0.7.0 + +- summed given fft in n bands, but re normalized between 0 - 70? +- Peaks L and R +- amplitude for given target frequency and PEAK frequency +- "music note" to given frequency +- Real FFT, Imaginary FFT, Real + imaginary FFT +- threshold detection + +todo : + + +by Sam Neurohack +from /team/laser + +for python 2 & 3 + +Stereo : CHANNELS = 2 +mono : CHANNELS = 1 + +""" + + +import numpy as np +import pyaudio +from math import log, pow + +#import matplotlib.pyplot as plt + +#from scipy.interpolate import Akima1DInterpolator +#import matplotlib.pyplot as plt + +DEVICE = 3 +CHANNELS = 2 +START = 0 +RATE = 44100 # time resolution of the recording device (Hz) +CHUNK = 4096 # number of data points to read at a time. Almost 10 update/second +TARGET = 2100 # show only this one frequency + + +A4 = 440 +C0 = A4*pow(2, -4.75) +name = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] +data = [] + +p = pyaudio.PyAudio() # start the PyAudio class +stream = p.open(format = pyaudio.paInt16, channels = CHANNELS, input_device_index = DEVICE, rate=RATE, input=True, + frames_per_buffer=CHUNK) #uses default input device + + +# +# Audio devices & audiogen functions +# + +def list_devices(): + # List all audio input devices + p = pyaudio.PyAudio() + i = 0 + n = p.get_device_count() + print (n,"devices found") + while i < n: + dev = p.get_device_info_by_index(i) + if dev['maxInputChannels'] > 0: + print (str(i)+'. '+dev['name']) + i += 1 + + +def valid_input_devices(self): + """ + See which devices can be opened for microphone input. + call this when no PyAudio object is loaded. + """ + mics=[] + for device in range(self.p.get_device_count()): + if self.valid_test(device): + mics.append(device) + if len(mics)==0: + print("no microphone devices found!") + else: + print("found %d microphone devices: %s"%(len(mics),mics)) + return mics + + + +def loop(): + try: + + #plt.ion() + + #plt.axis([x[0], x[-1], -0.1, max_f]) + fftbands = [0,1,2,3,4,5,6,7,8,9] + plt.xlabel('frequencies') + plt.ylabel('amplitude') + data = audioinput() + drawfreq, fft = allfft(data) + #lines = plt.plot(drawfreq, fft) + + #plt.axis([drawfreq[0], drawfreq[-1], 0, np.max(fft)]) + #plt.plot(drawfreq, fft) + #plt.show() + #line, = plt.plot(fftbands, levels(fft,10)) + line, = plt.plot(drawfreq, fft) + #while True : + for i in range(50): + + data = audioinput() + + # smooth the FFT by windowing data + #data = data * np.hanning(len(data)) + + # conversion to -1 to +1 + # normed_samples = (data / float(np.iinfo(np.int16).max)) + + # Left is channel 0 + dataL = data[0::2] + # Right is channel 1 + dataR = data[1::2] + + # Peaks L and R + peakL = np.abs(np.max(dataL)-np.min(dataL))/CHUNK + peakR = np.abs(np.max(dataR)-np.min(dataR))/CHUNK + # print(peakL, peakR) + + drawfreq, fft = allfft(data) + #fft, fftr, ffti, fftb, drawfreq = allfft(data) + + #line.set_ydata(levels(fft,10)) + line.set_ydata(fft) + plt.pause(0.01) + #print(drawfreq) + #print(fft) + #print (levels(fft,10)) + + #line.set_ydata(fft) + #plt.pause(0.01) # pause avec duree en secondes + + # lines = plt.plot(x, y) + #lines[0].set_ydata(fft) + #plt.legend(['s=%4.2f' % s]) + #plt.draw() + #plt.show() + + + + ''' + targetpower,freqPeak = basicfft(audioinput(stream)) + print("amplitude", targetpower, "@", TARGET, "Hz") + if freqPeak > 0.0: + print("peak frequency: %d Hz"%freqPeak, pitch(freqPeak)) + ''' + plt.show() + + except KeyboardInterrupt: + stream.stop_stream() + stream.close() + p.terminate() + + print("End...") + +# Close properly +def close(): + stream.stop_stream() + stream.close() + p.terminate() + + +# Return "music note" to given frequency +def pitch(freq): + h = round(12*(log(freq/C0)/log(2))) + octave = h // 12 + n = h % 12 + return name[n] + str(octave) + + +# Return summed given fft in n bands, but re normalized 0 - 70 +def levels(fourier, bands): + + size = int(len(fourier)) + levels = [0.0] * bands + + # Add up for n bands + # remove normalizer if you want raw added data in all bands + normalizer = size/bands + #print (size,bands,size/bands) + levels = [sum(fourier[I:int(I+size/bands)])/normalizer for I in range(0, size, int(size/bands))][:bands] + + for band in range(bands): + if levels[band] == np.NINF: + levels[band] =0 + + + return levels + + +# read CHUNK size in audio buffer +def audioinput(): + + # When reading from our 16-bit stereo stream, we receive 4 characters (0-255) per + # sample. To get them in a more convenient form, numpy provides + # fromstring() which will for each 16 bits convert it into a nicer form and + # turn the string into an array. + return np.fromstring(stream.read(CHUNK),dtype=np.int16) + +# power for given TARGET frequency and PEAK frequency +# do fft first. No conversion in 'powers' +def basicfft(data): + + #data = data * np.hanning(len(data)) # smooth the FFT by windowing data + fft = abs(np.fft.fft(data).real) + #fft = 10*np.log10(fft) + fft = fft[:int(len(fft)/2)] # first half of fft + + freq = np.fft.fftfreq(CHUNK,1.0/RATE) + freq = freq[:int(len(freq)/2)] # first half of FFTfreq + + assert freq[-1]>TARGET, "ERROR: increase chunk size" + + # return power for given TARGET frequency and peak frequency + return fft[np.where(freq > TARGET)[0][0]], freq[np.where(fft == np.max(fft))[0][0]]+1 + + +# todo : Try if data = 1024 ? +# in "power' (0-70?) get Real FFT, Imaginary FFT, Real + imaginary FFT +def allfft(data): + + #print ("allfft", len(data)) + fft = np.fft.fft(data) + #print("fft",len(fft)) + fftr = 10*np.log10(abs(fft.real))[:int(len(data)/2)] + ffti = 10*np.log10(abs(fft.imag))[:int(len(data)/2)] + fftb = 10*np.log10(np.sqrt(fft.imag**2+fft.real**2))[:int(len(data)/2)] + #print("fftb",len(fftb)) + drawfreq = np.fft.fftfreq(np.arange(len(data)).shape[-1])[:int(len(data)/2)] + drawfreq = drawfreq*RATE/1000 #make the frequency scale + #return fft, fftr, ffti, fftb, drawfreq + return drawfreq, fftb + + # Draw Original datas + # X : np.arange(len(data))/float(rate)*1000 + # Y : data + + # Draw real FFT + # X : drawfreq + # Y : fftr + + # Draw imaginary + # X : drawfreq + # Y : ffti + + # Draw Real + imaginary + # X : drawfreq + # Y : fftb + + +# True if any value in the data is greater than threshold and after a certain delay +def ding(right,threshold): + if max(right) > threshold and time.time() - last_run > min_delay: + return True + else: + return False + last_run = time.time() + + +if __name__ == "__main__": + + + loop() +''' +x = np.linspace(0, 3, 100) +k = 2*np.pi +w = 2*np.pi +dt = 0.01 + +t = 0 +for i in range(50): + y = np.cos(k*x - w*t) + if i == 0: + line, = plt.plot(x, y) + else: + line.set_ydata(y) + plt.pause(0.01) # pause avec duree en secondes + t = t + dt + +plt.show() +''' + diff --git a/libs/bhoreal.py b/libs/bhoreal.py new file mode 100644 index 0000000..4051aa6 --- /dev/null +++ b/libs/bhoreal.py @@ -0,0 +1,271 @@ +# coding=UTF-8 +""" +Bhoreal +v0.7.0 +Bhoreal Led matrix Handler + +Start a dedicated thread to handle incoming events from launchpad. + +Cls() +AllColor(color) +StarttBhoreal(port) : Start animation + +Led Matrix can be access with X and Y coordinates and as midi note (0-63) + +NoteOn(note,color) +NoteOff(note) +NoteOnXY(x,y,color): +NoteOffXY(x,y): +NoteXY(x,y): + +gstt.BhorLeds[] array stores matrix current state + +by Sam Neurohack +from /team/laser + +""" + +import time +from rtmidi.midiconstants import (CHANNEL_PRESSURE, CONTROLLER_CHANGE, NOTE_ON, NOTE_OFF, + PITCH_BEND, POLY_PRESSURE, PROGRAM_CHANGE) +import gstt, midi3 +import sys + +gstt.BhorLeds = [0]*65 + + +Here = -1 +from queue import Queue + +def NoteOn(note,color): + + print ("bhoreal noteon", note, color) + msg = [NOTE_ON, note, color] + midi3.send(msg,"Bhoreal") + gstt.BhorLeds[note]=color + +def NoteOff(note): + msg = [NOTE_OFF, note, 0] + midi3.send(msg,"Bhoreal") + gstt.BhorLeds[note]=0 + + +def NoteOnXY(x,y,color): + #print x,y + msg = [NOTE_ON, NoteXY(x,y), color] + midi3.send(msg,"Bhoreal") + gstt.BhorLeds[NoteXY(x,y)]=color + +def NoteOffXY(x,y): + msg = [NOTE_OFF, NoteXY(x,y), 0] + midi3.send(msg,"Bhoreal") + gstt.BhorLeds[NoteXY(x,y)]=0 + + +# Leds position are humans numbers 1-8. So -1 for pythonic array position 0-7 +def NoteXY(x,y): + note = (x -1)+ (y-1) * 8 + return note + +def Index(note): + y=note/8 + x=note%8 + #print "Note : ",note + #print "BhorIndex : ", x+1,y+1 + return int(x+1),int(y+1) + +# +# Bhoreal Start anim +# + +# AllColor for bhoreal on given port + +def AllColor(port,color): + for led in range(0,64,1): + msg = [NOTE_ON, led, color] + midi3.send(msg,"Bhoreal") + +# Cls for bhoreal on given port + +def Cls(port): + for led in range(0,64,1): + msg = [NOTE_OFF, led, 0] + midi3.send(msg,"Bhoreal") + + + +def StartBhoreal(port): + + Cls(port) + time.sleep(0.2) + for color in range(0,126,1): + AllColor(port,color) + time.sleep(0.02) + time.sleep(0.2) + Cls(port) + + + +def UpdateLine(line,newval): + if gstt.BhorealHere != -1: + for led in range(8): + NoteOffXY(led,line) + + NoteOnXY(newval,line,64) + + + +# Update Laser +def Noteon_Update(note): + + ''' + # forward new instruction ? + if gstt.MyLaser != gstt.Laser: + doit = jumplaser.get(gstt.Laser) + doit("/noteon",note) + ''' + # + + + if note < 8: + pass + + # + if note > 7 and note < 16: + pass + + # + if note > 15 and note < 24: + pass + + # change current simulator PL + if note > 23 and note < 32: + pass + + if note == 57 or note == 58: + pass + + if note > 58: + pass + +''' +# todo 57 Color mode : Rainbow +# 58 Color mode : RGB + +# Notes for Curve : 0-7 +def UpdateCurve(): + print ("New Curve :", gstt.Curve) + if gstt.BhorealHere != -1: + for led in range(0,8): + NoteOff(led) + NoteOn(gstt.Curve,20) + +# Notes for set : 8-15 +def UpdateSet(): + print ("New Set :", gstt.Set) + if gstt.BhorealHere != -1: + for led in range(9,17): + NoteOff(led) + NoteOn(gstt.Set+8,10) + +# Note for current laser : 16-23 +def UpdateLaser(): + print ("New Laser :", gstt.Laser) + if gstt.BhorealHere != -1: + for led in range(16,24): + NoteOff(led) + NoteOn(gstt.Laser+16,30) + +# Note for PL displayed in pygame window : 24-31 +def UpdateSimu(): + print ("New simuPL :", gstt.simuPL) + if gstt.BhorealHere != -1: + for led in range(24,32): + NoteOff(led) + NoteOn(gstt.simuPL+24,40) +''' + +# +# Events from Bhoreal handling +# + +# Process events coming from Bhoreal in a separate thread. +def MidinProcess(bhorqueue): + + #print() + #print("bhoreal midi in process started with queue", bhorqueue) + #print() + bhorqueue_get = bhorqueue.get + while True: + + msg = bhorqueue_get() + + # Bhoreal Led pressed + print ("Bhoreal Matrix : ", str(msg[1]), gstt.BhorLeds[msg[1]]) + + if msg[0] == NOTE_ON and msg[2] == 64: + # led + NoteOn(msg[1],64) + + # Bhoreal Led depressed + elif msg[0] == NOTE_ON and msg[2] == 0: + NoteOn(msg[1],0) + + ''' + + print "Bhoreal Matrix : ", str(msg[1]), str(gstt.BhorLeds[msg[1]]) + + if msg[1]< 8: + gstt.Curve = msg[1] + UpdateCurve() + + if msg[1]> 7 and msg[1] < 16: + gstt.Set = msg[1]-8 + UpdateSet() + + if msg[1]> 15 and msg[1] < 24: + gstt.Laser = msg[1]-16 + UpdateLaser() + + if msg[1]> 23 and msg[1] < 31: + gstt.simuPL = msg[1]-24 + UpdateSimu() + + #Bhoreal send back note on and off to light up the led. + if msg[1]> 56: + if gstt.BhorLeds[msg[1]] < 115: + gstt.BhorLeds[msg[1]] += 10 + #midi3.NoteOn(msg[1],gstt.BhorLeds[msg[1]]) + + #time.sleep(0.1) + #midi3.NoteOff(msg[1]) + ''' +bhorqueue = Queue() + + +# New Bhoreal call back : new msg forwarded to Bhoreal queue +class AddQueue(object): + def __init__(self, portname): + self.portname = portname + self._wallclock = time.time() + + + def __call__(self, event, data=None): + message, deltatime = event + self._wallclock += deltatime + print("[%s] @%0.6f %r" % (self.portname, self._wallclock, message)) + bhorqueue.put(message) + +''' +# Old Bhoreal call back : new msg forwarded to Bhoreal queue +class AddQueue(object): + def __init__(self, port): + self.port = port + self._wallclock = time.time() + + def __call__(self, event, data=None): + message, deltatime = event + self._wallclock += deltatime + print("[%s] @%0.6f %r" % (self.port, self._wallclock, message)) + bhorqueue.put(message) +''' diff --git a/libs/bhoreal.pyc b/libs/bhoreal.pyc new file mode 100644 index 0000000..db51e95 Binary files /dev/null and b/libs/bhoreal.pyc differ diff --git a/libs/bhorunicornhat.py b/libs/bhorunicornhat.py new file mode 100644 index 0000000..c7815ef --- /dev/null +++ b/libs/bhorunicornhat.py @@ -0,0 +1,164 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +''' +Bhorunicornhat +v0.7.0 + +A library to replace unicornhat and unicorn_hat_sim to use +any unicorn hat python script with a bhoreal or a Launchpad mini. + +2 things to do : + +1/ Change import in a unicorn target python program + +import unicornhat as unicorn + +by : + +import bhorunicornhat as unicorn + +2/ Set target (bhoreal or launchpad) by calling unicornhat.dest(device,rotation) +or modify destination and rotation manually : see a few lines down. + + + +by Sam Neurohack +from /team/laser + +CC NC BY + +''' +import colorsys,time +import midi3,bhoreal,launchpad + +# For Launchpad mini +mididest = "launchpad" +rotangle = 270 + +# For Bhoreal +#mididest = "bhoreal" +#rotangle = 180 + +BhorLeds = [0] * 64 +Bhuffer = [0] * 64 +HAT = (8,8) +AUTO = (8,8) + +# 'launchpad' with rotation 270 +# 'bhoreal' with rotation 180 +def dest(dest,rot): + global mididest, rotangle + + mididest = dest + rotangle = rot + +def set_layout(x): + pass + + +def rot90(): + + for notes in range(0,64): + Bhuffer[notes] = BhorLeds[notes] + + for y in range(1,9): + for x in range(1,9): + #print x,y,9-y,x,bhoreal.NoteXY(9-y,x),bhoreal.NoteXY(x,y),BhorLeds[bhoreal.NoteXY(9-y,x)],Bhuffer[bhoreal.NoteXY(x,y)] + BhorLeds[bhoreal.NoteXY(9-y,x)]=Bhuffer[bhoreal.NoteXY(x,y)] + +def rot180(): + + for notes in range(0,64): + Bhuffer[notes] = BhorLeds[notes] + + for y in range(8,0,-1): + #print "" + for x in range(1,9): + #print x,y,9-y,bhoreal.NoteXY(x,9-y),bhoreal.NoteXY(x,y),BhorLeds[bhoreal.NoteXY(x,9-y)],Bhuffer[bhoreal.NoteXY(x,y)] + BhorLeds[bhoreal.NoteXY(x,9-y)]=Bhuffer[bhoreal.NoteXY(x,y)] + +def rotation(angle): + if angle == 90: + rot90() + if angle == 180: + rot180() + if angle == 270: + rot180() + rot90() + +def brightness(brightness): + #like 0.5 + pass + +def get_shape(): + return 8,8 + +def clear(): + off() + +def hue(r,g,b): + + h = int(127*colorsys.rgb_to_hsv(r,g,b)[0]) + v = int(127*colorsys.rgb_to_hsv(r,g,b)[2]) + if h == 0 and v != 0: + h=127 + #should be variation of grey (v,v,v) + #v = int(127*colorsys.rgb_to_hsv(r,g,b)[2]) + #print r,g,b,h + return h + +def off(): + for note in range(1,64): + BhorLeds[note] = 0 + show() + +def set_all(r,g,b): + + for led in range(0,64): + BhorLeds[led] = hue(r,g,b) + +def set_pixel(x,y,r,g,b): + + #print x,y,r,g,b,colorsys.rgb_to_hsv(r,g,b) + note = (x-1)+ (y-1) * 8 + #print int(127*colorsys.rgb_to_hsv(r,g,b)[0]) + BhorLeds[note] = hue(r,g,b) + +def set_pixels(pixels): + led = 0 + for line in pixels: + #print line + for ledline in range(0,8): + #print line[ledline] + r,g,b = line[ledline][0],line[ledline][1],line[ledline][2] + BhorLeds[led] = hue(r,g,b) + led += 1 + +def clean_shutdown(): + pass + +def show(): + + # How turn off all leds + ''' + if bhoreal.Here != -1: + bhoreal.Cls(0) + + if launchpad.Here != -1: + launchpad.Cls() + ''' + + # Check if midi3 has been previously initiated + if len(midi3.OutDevice) == 0: + midi3.OutConfig() + + + if (mididest == 'launchpad' and launchpad.Here != -1) or (mididest == 'bhoreal' and bhoreal.Here != -1): + + rotation(rotangle) + for note in range(1,65): + midi3.NoteOn(note-1,BhorLeds[note-1],mididest) + time.sleep(0.0001) + else: + print(mididest,'is connected ?') diff --git a/cli.py b/libs/cli.py similarity index 100% rename from cli.py rename to libs/cli.py diff --git a/libs/cli.pyc b/libs/cli.pyc new file mode 100644 index 0000000..5b7dfee Binary files /dev/null and b/libs/cli.pyc differ diff --git a/commands.py b/libs/commands.py similarity index 80% rename from commands.py rename to libs/commands.py index d1cba90..8053ae2 100644 --- a/commands.py +++ b/libs/commands.py @@ -46,6 +46,8 @@ lsteps is a string like "[ (1.0, 8),(0.25, 3), (0.75, 3), (1.0, 10)]" /planet will be forwarded to planetarium client. /nozoid will be forwarded to nozoid client. +/scene/scenenumber/start 0 or 1 + 0 : display user pointlist with current client key. See below for client key. 1 : pull in redis a new correction matrix (EDH) 2 : display black @@ -78,16 +80,15 @@ Bob could use /pl/2/0 and /pl/2/1 and Lisa could use /pl/2/2 and /pl/2/3. """ from __future__ import absolute_import import types, time -import gstt -import homographyp -import settings +from libs import gstt import redis -import plugins + +from libs import settings, plugins, homographyp r = redis.StrictRedis(host=gstt.LjayServerIP , port=6379, db=0) -GenericCommands = ["start","ljclient","clientnumber","noteon","ljpong","ljwars","mouse","emergency","simu","status","run","nozoid","planet","live","words","ai","bank0","pose","lj","cycl","glyph","pong"] +GenericCommands = ["start","align","ljclient","scene","addest","deldest","clientnumber","vcvrack","fft","midigen","viewgen","audiogen","noteon","cc","ljpong","ljwars","mouse","emergency","simu","status","run","nozoid","planet","live","words","ai","bank0","pose","lj","cycl","glyph","pong"] @@ -135,17 +136,33 @@ def LasClientChange(clientnumber): if r.get("/pl/"+str(clientnumber)+"/0") != None: print "Switching to laser client", clientnumber - gstt.LasClientNumber = clientnumber - plugins.sendWSall("/status Client " + str(gstt.LasClientNumber) + " laser " + str(gstt.Laser)) + gstt.SceneNumber = clientnumber + plugins.sendWSall("/status Client " + str(gstt.SceneNumber) + " laser " + str(gstt.Laser)) r.set('/clientkey', "/pl/"+str(clientnumber)+"/") print "clientkey set to", "/pl/"+str(clientnumber)+"/" for laserid in xrange(0,gstt.LaserNumber): r.set('/order/'+str(laserid), 5) else: - print "ERROR : MaxLasClient is set to ", gstt.MaxLasClient + print "ERROR : Maximum number of scenes is set to ", gstt.MaxScenes +def SceneChange(newscene): + + print "Switching to scene", newscene + gstt.SceneNumber = int(newscene) + plugins.sendWSall("/status Scene " + newscene) + + r.set('/clientkey', "/pl/"+ newscene +"/") + print "clientkey set to", "/pl/" + newscene + "/" + + for laserid in xrange(0,gstt.LaserNumber): + r.set('/order/'+str(laserid), 5) + plugins.sendWSall("/scene/" + str(laserid) + "/start 0") + + plugins.sendWSall("/scene/" + newscene + "/start 1") + + def NoteOn(note): print "NoteOn", note @@ -158,13 +175,17 @@ def NoteOn(note): if note > 23 and note < 32: if note - 24 > gstt.LaserNumber -1: print "Only",gstt.LaserNumber,"lasers asked, you dum ass !" - plugins.sendWSall("/status Not Enough Laser") + plugins.sendWSall("/status Not Enough Lasers") else: gstt.Laser = note -24 - plugins.sendWSall("/status Client " + str(gstt.LasClientNumber) + " laser " + str(gstt.Laser)) + plugins.sendWSall("/status Scene " + str(gstt.SceneNumber) + " laser " + str(gstt.Laser)) print "Current Laser switched to", gstt.Laser +def CC(number, value): + print "CC", note, value + + def Mouse(x1,y1,x2,y2): print "Mouse", x1,y1,x2,y2 @@ -173,28 +194,60 @@ def Mouse(x1,y1,x2,y2): def handler(oscpath, args): #print "" - print "OSC handler in commands.py got /"+ str(oscpath)+ " with args :",args + if gstt.debug > 0: + print "OSC handler in commands.py got /"+ str(oscpath)+ " with args :",args # 2 incoming cases : generic or specific for a given lasernumber : # Generic : Commands without a laser number if oscpath[1] in GenericCommands: - print "GenericCommand :",oscpath[1],"with args",args + if gstt.debug > 0: + print "GenericCommand :",oscpath[1],"with args",args + if oscpath[1] == "ljclient": - LasClientChange(int(args[0])) + #LasClientChange(int(args[0])) + SceneChange(args[0]) + + + #/scene/scenenumber/start 0 or 1 + if oscpath[1] == "scene": + + print oscpath[1], oscpath[2], args[0] + if args[0] == '1' and r.get("/pl/" + oscpath[2] + "/0") != None: + SceneChange(oscpath[2]) + else: + print "ERROR : Maximum number of scenes is set to ", gstt.MaxScenes elif oscpath[1] == "noteon": NoteOn(int(args[0])) + elif oscpath[1] == "CC": + CC(int(args[0]), int(args[1])) + + elif oscpath[1] == "pong": - print "LJ commands got pong from", args - plugins.sendWSall("/" + args[0] + "start 1") + #print "LJ commands got pong from", args + print("/" + args[0] + "/start 1") + plugins.sendWSall("/" + args[0] + "/start 1") + print("/status got pong from "+ args[0] +".") plugins.sendWSall("/status got pong from "+ args[0] +".") - + + + elif oscpath[1] == "vcvrack": + pass + ''' + #print "LJ commands got /vcvrack from", args + if oscpath[2] == "1" : + r.set('/vcvrack/1', args[0]) + #print('/vcvrack/1', args[0]) + if oscpath[2] == "2" : + r.set('/vcvrack/2', args[0]) + #print('/vcvrack/2', args[0]) + ''' elif oscpath[1] == "mouse": Mouse(int(args[0]),int(args[1]),int(args[2]),int(args[3])) @@ -220,9 +273,13 @@ def handler(oscpath, args): # Commands with a laser number else: pathlength = len(oscpath) + print("oscpath", oscpath) + + print("pathlength", pathlength) if pathlength == 3: laser = int(oscpath[2]) + else: laser = int(oscpath[3]) diff --git a/libs/commands.pyc b/libs/commands.pyc new file mode 100644 index 0000000..efd36c3 Binary files /dev/null and b/libs/commands.pyc differ diff --git a/font1.py b/libs/font1.py similarity index 100% rename from font1.py rename to libs/font1.py diff --git a/libs/font1.pyc b/libs/font1.pyc new file mode 100644 index 0000000..0c264d1 Binary files /dev/null and b/libs/font1.pyc differ diff --git a/gstt.py b/libs/gstt.py similarity index 98% rename from gstt.py rename to libs/gstt.py index 8874703..cc309c4 100644 --- a/gstt.py +++ b/libs/gstt.py @@ -25,8 +25,8 @@ anims= [[],[],[],[]] LaserNumber = 2 # What laser client to listen at launch -LasClientNumber = 0 -MaxLasClient = 3 +SceneNumber = 0 +MaxScenes = 3 screen_size = [400,400] xy_center = [screen_size[0]/2,screen_size[1]/2] diff --git a/libs/gstt.pyc b/libs/gstt.pyc new file mode 100644 index 0000000..3f2d562 Binary files /dev/null and b/libs/gstt.pyc differ diff --git a/homographyp.py b/libs/homographyp.py similarity index 100% rename from homographyp.py rename to libs/homographyp.py diff --git a/libs/homographyp.pyc b/libs/homographyp.pyc new file mode 100644 index 0000000..462120c Binary files /dev/null and b/libs/homographyp.pyc differ diff --git a/libs/launchpad.py b/libs/launchpad.py new file mode 100644 index 0000000..6bcb15a --- /dev/null +++ b/libs/launchpad.py @@ -0,0 +1,883 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Launchpad +v0.7.0 + +Maunchpad mini Handler. +Start a dedicated thread to handle incoming events from launchpad. + +Cls() +AllColorPad(color) +StartLaunchPad(port) : Start animation + +Led Matrix can be access with X and Y coordinates and as midi note (0-63) + +PadNoteOn(note,color) +PadNoteOff(note) +PadNoteOnXY(x,y,color): +PadNoteOffXY(x,y): +PadNoteXY(x,y): + +PadLeds[], PadTops[] and PadRights arrays stores matrix current state + + +Top raw and right column leds are numbered humanly 1-8. So -1 is for pythonic arrays position 0-7 + +PadTopOn(number,color) +PadTopOff(number) +PadRightOn(number) +PadRightOff(number): + +by Sam Neurohack +from /team/laser + +for python 2 & 3 + +""" + + +import time +import rtmidi +from rtmidi.midiutil import open_midiinput +from threading import Thread +from rtmidi.midiconstants import (CHANNEL_PRESSURE, CONTROLLER_CHANGE, NOTE_ON, NOTE_OFF, + PITCH_BEND, POLY_PRESSURE, PROGRAM_CHANGE) + +from mido import MidiFile +import mido +import sys +import midi3 +#import midimacros, maxwellmacros +import traceback + +from queue import Queue +import scrolldisp +#from libs import macros +import json, subprocess +from OSC3 import OSCServer, OSCClient, OSCMessage +import socket + + +print('Launchpad Startup..') +myHostName = socket.gethostname() +print("Name of the localhost is {}".format(myHostName)) +myIP = socket.gethostbyname(myHostName) +print("IP address of the localhost is {}".format(myIP)) + +myIP = "127.0.0.1" + +print('Used IP', myIP) +OSCinPort = 8080 +monomePort = 8000 +maxwellatorPort = 8090 + +launchqueue = Queue() + +mode = "maxwell" + +mididest = 'Session 1' +midichannel = 1 +CChannel = 0 +CCvalue = 0 + +PadLeds = [0] * 64 +PadTops= [0] * 8 +PadRights= [0] * 8 + +Here = -1 + +ModeCallback = '' +# midi notes +LaunchLedMatrix = [(0,1,2,3,4,5,6,7),(16,17,18,19,20,21,22,23),(32,33,34,35,36,37,38,39),(48,49,50,51,52,53,54,55),(64,65,66,67,68,69,70,71),(80,81,82,83,84,85,86,87),(96,97,98,99,100,101,102,103),(112,113,114,115,116,117,118,119)] +# Notes +LaunchRight = (8,24,40,56,72,88,104,120) +# CC +LaunchTop = (104,105,106,107,108,109,110,111) +PadTop = [0,0,0,0,0,0,0,0] +PadRight = [0,0,0,0,0,0,0,0] +PadMatrix = [0] * 64 +TopSelection = [0] *8 +computerIP = ['127.0.0.1','192.168.2.95','192.168.2.52','127.0.0.1', + '127.0.0.1','127.0.0.1','127.0.0.1','127.0.0.1'] +computer = 0 + + + +# /cc cc number value +def cc(ccnumber, value, dest=mididest): + + midi3.MidiMsg([CONTROLLER_CHANGE+midichannel-1,ccnumber,value], dest) + + + +def Disp(text,device = 'Launchpad Mini'): + + print(device,midi3.FindInDevice(device)) + + if (device == "Launchpad Mini" or device =='launchpad') and midi3.FindInDevice(device) != -1: + scrolldisp.Display(text, color=(255,255,255), delay=0.2, mididest = 'launchpad') + + if device == 'bhoreal' and midi3.FindInDevice('Bhoreal'): + scrolldisp.Display(text, color=(255,255,255), delay=0.2, mididest = device) + + +def PadNoteOn(note,color): + (x,y) = BhorIndex(note) + #print(note,x,y) + PadNoteOnXY(x,y,color) + + +def PadNoteOff(note): + (x,y) = BhorIndex(note) + PadNoteOffXY(x,y) + +def PadNoteOnXY(x,y,color): + msg= [NOTE_ON, PadNoteXY(x,y), color] + #print msg + midi3.send(msg,"Launchpad") + PadLeds[BhorNoteXY(x,y)]=color + + +def PadNoteOffXY(x,y): + msg= [NOTE_OFF, PadNoteXY(x,y), 0] + midi3.send(msg,"Launchpad") + PadLeds[BhorNoteXY(x,y)]=0 + +def PadNoteXY(x,y): + note = LaunchLedMatrix[int(y-1)][int(x-1)] + return note + +def PadIndex(note): + y=note/16 + x=note%16 + return int(x+1),int(y+1) + +def BhorIndex(note): + y=note/8 + x=note%8 + #print "Note : ",note + #print "BhorIndex : ", x+1,y+1 + return int(x+1),int(y+1) + +def BhorNoteXY(x,y): + note = (x -1)+ (y-1) * 8 + return note + +# top raw and right column leds are numbered humanly 1-8. So -1 is for pythonic arrays position 0-7 +def PadTopOn(number,color): + msg= [CONTROLLER_CHANGE, LaunchTop[number-1], color] + midi3.send(msg,"Launchpad") + PadTops[number-1]=color + +def PadTopOff(number): + msg= [CONTROLLER_CHANGE, LaunchTop[number-1], 0] + midi3.send(msg,"Launchpad") + PadTops[number-1]=0 + +def PadRightOn(number,color): + msg= [NOTE_ON, LaunchRight[number-1], color] + midi3.send(msg,"Launchpad") + PadRights[number-1]=color + +def PadRightOff(number): + msg= [NOTE_OFF, LaunchRight[number-1], 0] + midi3.send(msg,"Launchpad") + PadRights[number-1]=0 + +def TopUpdate(button,color): + #print(PadTop) + PadTop = [0,0,0,0,0,0,0,0] + PadTop[button] = color + for pad in range(7): + PadTopOn(pad+1,PadTop[pad]) + +def RightUpdate(): + for pad in range(9): + PadRightOn(pad,PadRight[pad]) + +def MatrixUpdate(): + for pad in range(64): + PadNoteOn(pad,PadMatrix[pad]) + +def MatrixSelect(): + MatrixUpdate() + return + +def ComputerUpdate(comput): + global computer + + computer = comput + PadRightOn(computer+1,127) + + + +# Client to export buttons actions from Launchpad or bhoreal + +def SendOSC(ip,port,oscaddress,oscargs=''): + + oscmsg = OSCMessage() + oscmsg.setAddress(oscaddress) + oscmsg.append(oscargs) + + osclient = OSCClient() + osclient.connect((ip, port)) + + print("sending OSC message : ", oscmsg, "to", ip, ":", port) + + try: + osclient.sendto(oscmsg, (ip, port)) + oscmsg.clearData() + return True + except: + print ('Connection to', ip, 'refused : died ?') + return False + + +# +# LaunchPad start anim +# + +# AllColor for bhoreal on given port +def AllColorPad(color): + + for led in range(0,64,1): + PadNoteOn(led,color) + ''' + for line in LaunchLedMatrix: + for led in line: + midiport[port].send_message([NOTE_ON, led, color]) + ''' + for rightled in range(8): + PadRightOn(rightled+1,color) + for topled in range(8): + PadTopOn(topled+1,color) + #midiport[port].send_message([CONTROLLER_CHANGE, topled, color]) + +def ClsMatrix(): + for led in range(0,64,1): + PadNoteOff(led) + +def ClsTop(): + for topled in range(8): + PadTopOff(topled+1) + +def ClsRight(): + + for rightled in range(8): + PadRightOff(rightled+1) + +def Cls(): + + ClsMatrix() + ClsTop() + ClsRight() + ComputerUpdate(computer) + + ''' + for line in LaunchLedMatrix: + for led in line: + midiport[port].send_message([NOTE_OFF, led, 0]) + ''' + +def StartLaunchPad(port): + + #ClsPad(port) + #time.sleep(0.3) + AllColorPad(20) + time.sleep(0.6) + Cls() + time.sleep(0.3) + +# +# Events from Launchpad Handling +# + +# Process events coming from Launchpad in a separate thread. +def MidinProcess(launchqueue): + global computer + + while True: + launchqueue_get = launchqueue.get + msg = launchqueue_get() + #print (msg) + + if msg[0]==NOTE_ON: + + (x,y) = PadIndex(msg[1]) + + # MATRIX = macros, notes, channels,... + if x < 9: + msg[1]= BhorNoteXY(x,y) + macroname = "m"+str(y)+str(x) + # Run Macro with matrix location and velocity + Run(macroname, macroargs = int(msg[2])) + + # RIGHT = computer, this host or other computer + if x == 9: + print("Right Button : ", y) + macroname = "r"+str(y) + print(macroname) + ClsRight() + PadRightOn(y,127) + print("Destination computer",y) + computer = y + #time.sleep(0.1) + #PadRightOff(y) + + # TOP = Mode Note, CC, Os, Monome,.. + if msg[0]==CONTROLLER_CHANGE: + print("Pad Top Button : ", str(msg[1]-103), "value",msg[2]) + TopUpdate(msg[1]-104,20) + macroname = "t"+str(msg[1]-103) + #print(macroname) + Run(macroname, macroargs = (msg[1]-103,msg[2])) + + +launchqueue = Queue() +ModeCallback = "ModeNo" + + + +# LaunchPad Mini call back : new msg forwarded to Launchpad queue +class LaunchAddQueue(object): + def __init__(self, port): + self.port = port + #print("LaunchAddQueue", self.port) + self._wallclock = time.time() + + def __call__(self, event, data=None): + message, deltatime = event + self._wallclock += deltatime + print() + print("[%s] @%0.6f %r" % (self.port, self._wallclock, message)) + launchqueue.put(message) + +# +# Modes : Top lines functions +# + +# Load Matrix only macros (for the moment) in macros.json +def LoadMacros(): + global macros + + print() + print("Loading Launchpad Macros...") + f=open("macros.json","r") + s = f.read() + macros = json.loads(s) + print(len(macros['OS']),"Macros") + print("Loaded.") + + +# return macroname number for given type 'OS', 'Maxwell' +def findMacros(macroname,macrotype): + + #print("searching", macroname,'...') + position = -1 + for counter in range(len(macros[macrotype])): + #print (counter,macros[macrotype][counter]['name'],macros[macrotype][counter]['code']) + if macroname == macros[macrotype][counter]['name']: + #print(macroname, "is ", counter) + position = counter + return position + + +# Default top buttons : maxwell macros +def TopMacro(arg): + + topbutton, value = arg + print ("topmacro", topbutton, "value", value) + if value == 127: + TopUpdate(topbutton-1,20) + Disp("Ma") + Disp('cr', 'bhoreal') + ModeCallback = TopCallback + +def TopCallback(arg): + + ClsMatrix() + x,y,velocity = arg + PadNoteOnXY(x,y,20) + #print ('Macros OS', BhorNoteXY(x,y), "velocity", velocity ) + macroname = 'm'+str(y)+str(x) + macronumber = findMacros(macroname,'Maxwell') + if macronumber != -1: + #print("code : ",macros['OS'][macronumber]["code"]) + eval(macros['Maxwell'][macronumber]["code"]) + else: + print("no Code yet") + +# +# Notes Macros +# + +def ModeNote(arg): + global ModeCallback + + + topbutton, value = arg + if value == 127: + TopUpdate(topbutton-1,20) + Disp("No") + Disp('te', 'bhoreal') + print("ModeNote") + + else: + ClsMatrix() + + ModeCallback = "NoteCallback" + + +def NoteCallback(arg): + + #ClsMatrix() + x,y,velocity = arg + notename = midi3.midi2note(BhorNoteXY(x,y)) + + print('computer',computer) + + # todo : decide whether its 0 or 1 !!! + if computer == 0 or computer == 1: + midi3.NoteOn(BhorNoteXY(x,y),velocity,'AutoTonic MIDI In') + else: + SendOSC(computerIP[computer-1],maxwellatorPort,'/note',[BhorNoteXY(x,y),velocity]) + + if velocity == 127: + PadNoteOnXY(x,y,20) + #print ('NoteON', BhorNoteXY(x,y),notename , "velocity", velocity ) + #Disp(notename) + else: + PadNoteOnXY(x,y,0) + #print ('NoteOFF', BhorNoteXY(x,y),notename , "velocity", velocity ) + +# +# CC Macros +# + +def ModeCC(arg): + global ModeCallback + + topbutton, value = arg + if value == 127: + TopUpdate(topbutton-1,20) + Disp('CC') + Disp(' ', 'bhoreal') + print("Mode CC") + ModeCallback = "CCSelect" + print("Please enter CC Channel") + #ClsMatrix() + Disp('Ch') + +def CCSelect(arg): + global ModeCallback, CChannel + + x,y, velocity = arg + PadNoteOnXY(x,y,20) + #print ('in CC channel callback x',x,'y',y) + if velocity == 127: + + CChannel = BhorNoteXY(x,y) + print("CC Channel", CChannel) + print("Please enter CC Value") + ModeCallback = "CCValue" + Disp('Va') + +def CCValue(arg): + #ClsMatrix() + x,y, velocity = arg + PadNoteOnXY(x,y,20) + #print ('in CC value callback x',x,'y',y) + + if velocity == 127: + CCvalue = BhorNoteXY(x,y) * 2 + print("CC Channel", CChannel,"CC Value", CCvalue) + + +# +# OS Macros +# + +def ModeOS(arg): + global ModeCallback + + topbutton, value = arg + if value == 127: + Disp('Os') + Disp('Ma', 'bhoreal') + TopUpdate(topbutton-1,20) + ModeCallback = "OSCallback" + else: + ClsMatrix() + +def OSCallback(arg): + + ClsMatrix() + x,y,velocity = arg + PadNoteOnXY(x,y,20) + #print ('Macros OS', BhorNoteXY(x,y), "velocity", velocity ) + macroname = 'm'+str(y)+str(x) + macronumber = findMacros(macroname,'OS') + if macronumber != -1: + #print("code : ",macros['OS'][macronumber]["code"]) + eval(macros['OS'][macronumber]["code"]) + else: + print("no Code yet") + + +# +# Monome emulation +# + +prefix = '/box' + +def ModeMonome(arg): + global ModeCallback + + topbutton, value = arg + if value == 127: + TopUpdate(topbutton-1,20) + Disp('Mo') + Disp('me', 'bhoreal') + ModeCallback = "MonomeCallback" + + else: + ClsMatrix() + + +def MonomeCallback(arg): + + ClsMatrix() + x,y,velocity = arg + #PadNoteOnXY(x,y,20) + + SendOSC('127.0.0.1', monomePort, prefix+'/press', (x,y,1)) + SendOSC('127.0.0.1', monomePort, prefix+'/grid/key', (x,y,1)) + + +# +# StartMode +# + +def ModeNo(arg): + x,y,velocity = arg + PadNoteOnXY(x,y,20) + print ('Mode No x',x,'y',y,"note", PadNoteXY(x,y)) + +''' +def Mode(mode): + global macros + + + if mode == "maxwell": + print("Launchpad in Maxwell mode") + macros = maxwellmacros.buttons + + if mode == "generic": + print("Launchpad in generic mode") + macros = generic +''' + + +# +# Right column functions +# + +def RightMacro(number): + + print ("rightmacro",number) + +# +# Default Pad macros +# + + +launchmacros = { + + "t": {"command": TopMacro, "default": -1}, + "t1": {"command": ModeNote, "default": ''}, + "t2": {"command": ModeCC, "default": ''}, + "t3": {"command": ModeOS, "default": ''}, + "t4": {"command": ModeMonome, "default": ''}, + "t5": {"command": TopMacro, "default": 5}, + "t6": {"command": TopMacro, "default": 6}, + "t7": {"command": TopMacro, "default": 7}, + "t8": {"command": TopMacro, "default": 8}, + + "r1": {"command": RightMacro, "default": 1}, + "r2": {"command": RightMacro, "default": 2}, + "r3": {"command": RightMacro, "default": 3}, + "r4": {"command": RightMacro, "default": 4}, + "r5": {"command": RightMacro, "default": 5}, + "r6": {"command": RightMacro, "default": 6}, + "r7": {"command": RightMacro, "default": 7}, + "r8": {"command": RightMacro, "default": 8} + } + +#Mode("generic") + + +def Run(macroname, macroargs=''): + + #print ("macroargs", macroargs) + + # Matrix button -> parameters sent to current Function in ModeCallback + if macroname.find("m") == 0: + doit = eval(ModeCallback) + doit((int(macroname[2]),int(macroname[1]), macroargs)) + #eval(ModeCallback)((int(macroname[2]),int(macroname[1]), macroargs),) + + + # Otherwise do the macro + else: + + doit = launchmacros[macroname]["command"] + if macroargs=='': + macroargs = launchmacros[macroname]["default"] + #print("Running", doit, "with args", macroargs ) + doit(macroargs) + +#ComputerUpdate(computer) +LoadMacros() + + +''' + +Docs Community About +monome +osc : opensound control / serialosc protocol + +what is serialosc? how does it work? +discovering and connecting to serialosc devices + +serialosc server listens on port 12002. + +when devices are connected, serialosc spawns new ports for each device. querying the server allows you to discover the port number for each device. (this supersedes the zeroconf method, which is still in place for legacy compatibility). +messages sent to serialosc server + +/serialosc/list si + +request a list of the currently connected devices, sent to host:port + +/serialosc/notify si + +request that next device change (connect/disconnect) is sent to host:port. to keep receiving the notifications, send another message to /serialosc/notify from the notify handler. +messages received from serialosc server + +/serialosc/device ssi + +currently connected device id and type, at this port + +/serialosc/add s + +device added + +/serialosc/remove s + +device removed +to serialosc device +sys + +these messages can be sent to a serialosc device to change settings. + +/sys/port i + +change computer port + +/sys/host s + +change computer host + +/sys/prefix s + +change message prefix (filtering) + +/sys/rotation i + +rotate the monome by degrees, where degrees is one of 0, 90, 180, 270. this replaces /cable + +/sys/info si + +/sys/info i + +/sys/info + +info + +request information (settings) about this device + +/info can take the following arguments: + +/info si (send /sys/info messages to host:port) + +/info i (send to localhost:port) + +/info (send to current computer application's host:port) + +example: + +to serialosc: + /sys/info localhost 9999 +from serialosc to localhost:9999: + /sys/id m0000045 + /sys/size 8 16 + /sys/host localhost + /sys/port 23849 + /sys/prefix /nubs + /sys/rotation 270 + +from serialosc + +these messages are sent from serialosc to the computer port. + +the messages below are sent after a /sys/info request is received. +sys + +/sys/port i report computer port + +/sys/host s report computer host + +/sys/id s report device id + +/sys/prefix s report prefix + +/sys/rotation i report grid device rotation + +/sys/size ii report grid device size + +to device +grid + +/grid/led/set x y s + +set led at (x,y) to state s (0 or 1). + +/grid/led/all s + +set all leds to state s (0 or 1). + +/grid/led/map x_offset y_offset s[8] + +Set a quad (8×8, 64 buttons) in a single message. + +Each number in the list is a bitmask of the buttons in a row, one number in the list for each row. The message will fail if the list doesn’t have 8 entries plus offsets. + +taken apart: + +(/grid/led/map) <- the message/route + (8 8) <- the offsets + (1 2 4 8 16 32 64 128) <- the bitmasks for each row + +examples + +/grid/led/map 0 0 4 4 4 4 8 8 8 8 +/grid/led/map 0 0 254 253 125 247 239 36 191 4 + +Offsets must be mutliples of 8. + +/grid/led/row x_offset y s[..] + +Set a row in a quad in a single message. + +Each number in the list is a bitmask of the buttons in a row, one number in the list for each row being updated. + +examples (for 256) + +/grid/led/row 0 0 255 255 +/grid/led/row 8 5 255 + +examples (for 64) + +/grid/led/row 0 0 232 +/grid/led/row 0 3 129 + +Offsets must be mutliples of 8. Offsets for monome64 should always be zero. + +/grid/led/col x y_offset s[..] + +Set a column in a quad in a single message. + +Each number in the list is a bitmask of the buttons in a column, one number in the list for each row being updated. + +examples (for 256) + +/grid/led/col 0 0 255 255 (updates quads 1 and 3) +/grid/led/col 13 8 255 (updates quad 4 due to offset.) + +examples (for 64) + +/grid/led/col 0 0 232 +/grid/led/col 6 0 155 + +Offsets must be mutliples of 8. Offsets for monome64 should always be zero. + +/grid/led/intensity i + +variable brightness: + +Valid values for ‘l’ below are in the range [0, 15]. + +January 2011 devices only support four intensity levels (off + 3 brightness levels). The value passed in /level/ messages will be “rounded down” to the lowest available intensity as below: + + [0, 3] - off + [4, 7] - low intensity + [8, 11] - medium intensity + [12, 15] - high intensity + +June 2012 devices allow the full 16 intensity levels. + +/grid/led/level/set x y l +/grid/led/level/all l +/grid/led/level/map x_off y_off l[64] +/grid/led/level/row x_off y l[..] +/grid/led/level/col x y_off l[..] + +tilt + +/tilt/set n s + +set active state of tilt sensor n to s (0 or 1, 1 = active, 0 = inactive). +arc + +led 0 is north. clockwise increases led number. These can be viewed and tested in the browser at http://nomeist.com/osc/arc/ + +/ring/set n x l + +set led x (0-63) on encoder n (0-1 or 0-3) to level l (0-15) + +/ring/all n l + +set all leds on encoder n (0-1 or 0-3) to level l (0-15) + +/ring/map n l[64] + +set all leds on encoder n (0-1 or 0-3) to 64 member array l[64] + +/ring/range n x1 x2 l + +set leds on encoder n (0-1 or 0-3) between (inclusive) x1 and x3 to level l (0-15). direction of set is always clockwise, with wrapping. +from device +grid + +/grid/key x y s + +key state change at (x,y) to s (0 or 1, 1 = key down, 0 = key up). +tilt + +/tilt n x y z + +position change on tilt sensor n, integer (8-bit) values (x, y, z) +arc + +/enc/delta n d + +position change on encoder n by value d (signed). clockwise is positive. + +/enc/key n s + +key state change on encoder n to s (0 or 1, 1 = key down, 0 = key up) + + Info@monome.org + +''' \ No newline at end of file diff --git a/libs/launchpad.pyc b/libs/launchpad.pyc new file mode 100644 index 0000000..2954e3f Binary files /dev/null and b/libs/launchpad.pyc differ diff --git a/plugins/lj.py b/libs/lj.py similarity index 90% rename from plugins/lj.py rename to libs/lj.py index 5244032..022815d 100644 --- a/plugins/lj.py +++ b/libs/lj.py @@ -1,8 +1,10 @@ # coding=UTF-8 ''' -LJ v0.8.1 -Some LJ functions useful for python 2.7 clients (was framy.py) + +lj v0.7.5 for LJ v0.8+ + +Some LJ functions useful for python 2.7 clients Functions and documentation here is low priority as python 2 support will stop soon. Better code your plugin with python 3 and lj3.py. @@ -26,11 +28,13 @@ import math import redis from OSC import OSCServer, OSCClient, OSCMessage -redisIP = '127.0.0.1' -r = redis.StrictRedis(host=redisIP, port=6379, db=0) +print "Importing lj from libs..." +#redisIP = '127.0.0.1' +#r = redis.StrictRedis(host=redisIP, port=6379, db=0) ClientNumber = 0 - +name = "noname" +oscrun = True point_list = [] pl = [[],[],[],[]] @@ -42,7 +46,10 @@ def SendLJ(oscaddress,oscargs=''): oscmsg.setAddress(oscaddress) oscmsg.append(oscargs) - print ("sending OSC message : ",oscmsg) + osclientlj = OSCClient() + osclientlj.connect((redisIP, 8002)) + + print "lj is sending OSC message : ",oscmsg, "to",redisIP,":8002" try: osclientlj.sendto(oscmsg, (redisIP, 8002)) oscmsg.clearData() @@ -151,12 +158,15 @@ ASCII_GRAPHICS = [ [(-2,15), (2,15)] # Point a la place de { ] -def Config(redisIP,client): - global ClientNumber +def Config(redIP,client,myname): + global ClientNumber, name, redisIP + redisIP = redIP r = redis.StrictRedis(host=redisIP, port=6379, db=0) ClientNumber = client #print "client configured",ClientNumber + name = myname + print "Plugin declare its name",name def LjClient(client): @@ -168,7 +178,34 @@ def LjPl(pl): global PL PL = pl - + + + +# Answer to LJ pings with /pong value +def OSCping(path, tags, args, source): + print name,"got /ping from LJ -> reply /pong", name + SendLJ("/pong",name) + + + +# Closing plugin messages to LJ +def ClosePlugin(): + WebStatus(name+" Exiting") + SendLJ("/"+name+"/start",0) + + +# /quit +def OSCquit(path, tags, args, source): + global oscrun + + oscrun = False + print('lj got /quit for',name) + #WebStatus(name + " quit.") + #SendLJ("/"+name+"/start",0) + #print("Stopping OSC...") + #OSCstop() + #sys.exit() + def LineTo(xy, c, PL): diff --git a/libs/lj23.py b/libs/lj23.py new file mode 100644 index 0000000..f0ee337 --- /dev/null +++ b/libs/lj23.py @@ -0,0 +1,892 @@ +# coding=UTF-8 +''' + +lj23 v0.7.6 for LJ v0.8+ + +Some LJ functions useful for python clients + + + +Class management : + +https://stackoverflow.com/questions/739882/iterating-over-object-instances-of-a-given-class-in-python +https://stackoverflow.com/questions/8628123/counting-instances-of-a-class +http://effbot.org/pyfaq/how-do-i-get-a-list-of-all-instances-of-a-given-class.htm + + + + +Config(redisIP, client number,name) + +Basic Draw : + +- PolyLineOneColor, rPolyLineOneColor, LineTo, Line +- PolyLineRGB, rPolyLineRGB, LineRGBTo, LineRGB +- rgb2int(r,g,b) +- DrawPL(point list number) : once you stacked all wanted elements, like 2 polylines, send them to lasers. +- DrawDests(): Draw all requested destinations for each PL. + +High level draw : + +- Text(word, integercolor, PL, xpos, ypos, resize, rotx, roty, rotz) : Display a word +- TextRGB(word, red, green, blue, ...) +- Embeded font1 + + +Laser objects (name and convenient group of parameters for one or several point lists) + +- RelativeObject +- FixedObject + +PL "Destinations" : tells Live what PL to draw and to what scene/Laser ("destination") to send it. + + +OSC and plugins functions : + +SendLJ(adress,message) : LJ remote control. See commands.py +SendResol(address,message): Send OSC message to Resolume. +WebStatus(message) : display message on webui + +LjClient(client): Change Client number in redis keys +LjPl(pl): Change pl number in redis keys = laser target. +ClosePlugin(name): Send UI closing info of given plugin + +OSCstart(): Start the OSC system. +OSCframe(): Handle incoming OSC message. Calling the right callback +OSCstop(): Properly close the OSC system +OSCping(): /ping Answer to LJ pings by sending /pong name +OSCquit(): /quit Exit calling script using name in terminal +OSCadddest(): PL, scene, laser Add a destination +OSCdeldest(): PL, scene, lasers delete a destination +OSCobj(): /name/obj objectname attribute value for automation +OSCvar(): /name/var variablename value for automation + +setup_controls(joystick) + +XboxController : getLeftHori, getLeftVert, getRightHori, getRightVert, getLeftTrigger, getRightTrigger +Ps3Controller : getLeftHori, getLeftVert, getRightHori, getRightVert, getLeftTrigger, getRightTrigger, getUp, getDown, getLeft, getRight, getFire1, getFire2(self): +MySaitekController : getLeftHori,getLeftVert, getRightHori,getRightVert, getLeftTrigger,getRightTrigger +MyThrustController : getLeftHori, getLeftVert, getRightHori, getRightVert, getLeftTrigger, getRightTrigger +CSLController : getLeftHori,getLeftVert,getRightHori, getRightVert,getLeftTrigger,getRightTrigger,getFire1,getFire2 +my USB Joystick : getUp,getDown,getLeft,getRight,etLeftTrigger, getRightTrigger,getFire1, getFire2 + + +LICENCE : CC +Sam Neurohack + +''' + +import math +import redis +import sys +import weakref +import struct +import numpy as np +from multiprocessing import Process, Queue, TimeoutError + +is_py2 = sys.version[0] == '2' +if is_py2: + from OSC import OSCServer, OSCClient, OSCMessage + #print ("Importing lj23 and OSC from libs...") +else: + from OSC3 import OSCServer, OSCClient, OSCMessage + #print ("Importing lj23 and OSC3 from libs...") + + +#redisIP = '127.0.0.1' +#r = redis.StrictRedis(host=redisIP, port=6379, db=0) + +ClientNumber = 0 +name = "noname" +oscrun = True +point_list = [] +pl = [[],[],[],[]] + +fft3Groups = [-1,-1,-1,-1] + +Dests = dict() + +oscIPresol = "127.0.0.1" +oscPORTresol = 7000 + + +''' + + Laser "objects" + + + set a name and convenient group of parameters for one or several point lists + + RelativeObject is for point lists around 0,0 with builtin move/rotation. + + How to init with object color, xpos,... : + osciObj = lj.RelativeObject('osciObj', True, 255, [], white, red, green,blue,0 , False, centerX , centerY , 1 , Xrot , Yrot , Zrot) + How to use in drawing functions : you're free to use 0, some or all of any laserobject attributes + - draw one or several pointlists with 'A' laserobject color and 'B' laserobject xpos ypos ? + - Change color of 'main' object and all other objects using it will change also + how to change attribute : + osciObj.resize = 2 or /pluginame/change 'OsciObj' 'resize' 2 + +''' + +class RelativeObject: + + kind = 'relative' + counter = 0 + + def __init__(self, name, active, intensity, xy, color, red, green, blue, PL , closed, xpos , ypos , resize , rotx , roty , rotz): + self.name = name + self.active = active # True/False + self.intensity = intensity + self.xy = [] # Dots list + self.color = color # RGB color in int + self.red = red + self.green = green + self.blue = blue + self.PL = PL + self.closed = closed + self.xpos = xpos + self.ypos = ypos + self.resize = resize + self.rotx = rotx + self.roty = roty + self.rotz = rotz + + RelativeObject.counter += 1 + #type(self).counter += 1 + + def __del__(self): + RelativeObject.counter -= 1 + + +# Fixed Laser object : point list in 'pygame' space (top left = 0,0 / bottom right) +class FixedObject: + + kind = 'fixed' + counter = 0 + + def __init__(self, name, intensity, active, xy, color, red, green, blue, PL , closed): + self.name = name + self.active = active # True/False + self.intensity = intensity + self.xy = [] + self.color = color + self.red = red + self.green = green + self.blue = blue + self.PL = PL + self.closed = closed + + FixedObject.counter += 1 + + def __del__(self): + FixedObject.counter -= 1 + +''' + +class IterDest(type): + def __new__ (cls, name, bases, dct): + dct['_instances'] = [] + return super().__new__(cls, name, bases, dct) + + def __call__(cls, *args, **kwargs): + instance = super().__call__(*args, **kwargs) + cls._instances.append(instance) + return instance + + def __iter__(cls): + return iter(cls._instances) + +class DestObject(): + +# class Destinations(metaclass=IterDest): + __metaclass__ = IterDest + counter = 0 + def __init__(self, name, number, active, PL , scene, laser): + self.name = name + self.number = number + self.active = active + self.PL = PL + self.scene = scene + self.laser = laser + + DestObject.counter += 1 + + def __del__(self): + DestObject.counter -= 1 +''' +class DestObject(): + +# class Destinations(metaclass=IterDest): + _instances = set() + counter = 0 + + def __init__(self, name, number, active, PL , scene, laser): + self.name = name + self.number = number + self.active = active + self.PL = PL + self.scene = scene + self.laser = laser + self._instances.add(weakref.ref(self)) + DestObject.counter += 1 + + @classmethod + def getinstances(cls): + dead = set() + for ref in cls._instances: + obj = ref() + if obj is not None: + yield obj + else: + dead.add(ref) + cls._instances -= dead + + + def __del__(self): + DestObject.counter -= 1 + + + +def Config(redIP,client,myname): + global ClientNumber, name, redisIP, r + + redisIP = redIP + r = redis.StrictRedis(host=redisIP, port=6379, db=0) + + # ClientNumber 255 are not drawing anything like artnet + ClientNumber = client + #print ("client configured",ClientNumber) + name = myname + print ("Plugin declare its name",name) + #print pl + return r + + +def LjClient(client): + global ClientNumber + + ClientNumber = client + + + +def LjPl(pl): + global PL + + PL = pl + + +def fromRedis(n): + + encoded = r.get(n) + #print("") + #print('fromredis key',n,":",encoded) + h, w = struct.unpack('>II',encoded[:8]) + #print("fromredis array size",n,":",h,w) + a = np.frombuffer(encoded, dtype=np.int16, offset=8).reshape(h,w) + #print("fromredis array",n,":",a) + return a + +# Store Numpy array 'a' in Redis key 'n' +# Write also in redis key 'a' numpy array, its 2 dimensions size : h time w values +def toRedis(n,a): + + #print("array.shape", a.shape, len(a.shape) ) + if len(a.shape) == 1: + h = a.shape[0] + w = 1 + else: + h,w = a.shape + #print("toredis", n,"h",h,"w",w,"a",a) + shape = struct.pack('>II',h,w) + + #shape = struct.pack('>II',len(a),1) + #print("toredis",n,a) + encoded = shape + a.tobytes() + + # Store encoded data in Redis + return r.set(n,encoded) + + +# +# OSC functions +# + +# OSC clients + +def SendLJ(oscaddress,oscargs=''): + + oscmsg = OSCMessage() + oscmsg.setAddress(oscaddress) + oscmsg.append(oscargs) + + osclientlj = OSCClient() + osclientlj.connect((redisIP, 8002)) + + print("lj23 in",name," sending OSC message : ", oscmsg, "to", redisIP, ":8002") + try: + osclientlj.sendto(oscmsg, (redisIP, 8002)) + oscmsg.clearData() + except: + print ('Connection to LJ refused : died ?') + pass + #time.sleep(0.001 + + + +# Resolume OSC Arena client. +# sendresol(oscaddress, [arg1, arg2,...]) +# example : sendresol("/noteon",note) + +def SendResol(oscaddress,oscargs): + + oscmsg = OSCMessage() + oscmsg.setAddress(oscaddress) + oscmsg.append(oscargs) + + osclientresol = OSCClient() + osclientresol.connect((oscIPresol, oscPORTresol)) + + print("lj sending OSC message : ", oscmsg, "to Resolume", oscIPresol, ":", oscPORTresol) + try: + osclientresol.sendto(oscmsg, (oscIPresol, oscPORTresol)) + oscmsg.clearData() + except: + print ('Connection to Resolume refused : died ?') + pass + + + + +def WebStatus(message): + SendLJ("/status", message) + + +# Closing plugin messages to LJ +def ClosePlugin(): + WebStatus(name+" Exiting") + SendLJ("/"+name+"/start",0) + + + + +# RAW OSC Frame available ? +def OSCframe(): + # clear timed_out flag + #print "oscframe" + oscserver.timed_out = False + # handle all pending requests then return + while not oscserver.timed_out: + oscserver.handle_request() + +# Answer to LJ pings with /pong value +def OSCping(path, tags, args, source): +#def OSCping(): + print(name, "got /ping from LJ -> reply /pong", name) + SendLJ("/pong",name) + +# Properly close the system. Todo +def OSCstop(): + oscserver.close() + + +# /quit +def OSCquit(path, tags, args, source): + global oscrun + + oscrun = False + print('lj23 got /quit for',name) + #WebStatus(name + " quit.") + #SendLJ("/"+name+"/start",0) + #print("Stopping OSC...") + #OSCstop() + #sys.exit() + + +# default handler +def OSChandler(path, tags, args, source): + + oscaddress = ''.join(path.split("/")) + print("Default OSC Handler in",name,": msg from Client : " + str(source[0]),) + print("OSC address", path) + if len(args) > 0: + print("with args", args) + + #oscIPout = str(source[0]) + #osclient.connect((oscIPout, oscPORTout)) + + +# for any laser object : /pluginame/obj objectname attribute value +# like : /pluginname/obj 'fft' 'xpos' 100 +# attributes for all lj Objects: name, xy_list, c, PL +# + for RelativeObjects : closed, xpos , ypos , resize , rotx , roty , rotz +def OSCobj(path, tags, args, source): + + obj = eval(args[0]+"."+ args[1]) + obj = args[2] + + +def OSCvar(path, tags, args, source): + + obj = eval(args[0]) + obj = args[1] + + +def addOSCdefaults(server): + global oscserver + + oscserver = server + oscserver.addMsgHandler( "default", OSChandler ) + oscserver.addMsgHandler( "/ping", OSCping) + oscserver.addMsgHandler( "/quit", OSCquit) + oscserver.addMsgHandler( "/"+ name + "/adddest", OSCadddest) + oscserver.addMsgHandler( "/"+ name + "/deldest", OSCdeldest) + oscserver.addMsgHandler( "/"+ name + "/obj", OSCobj) + oscserver.addMsgHandler( "/"+ name + "/var", OSCvar) + + + +# +# Drawing basic functions +# + +def rgb2int(r,g,b): + return int('0x%02x%02x%02x' % (r,g,b),0) + + +def LineTo(xy, c, PL): + + pl[PL].append((xy + (c,))) + +def rLineTo(xy, c, PL, xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0): + + pl[PL].append((Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz) + (c,))) + + +def Line(xy1, xy2, c, PL): + LineTo(xy1, 0, PL) + LineTo(xy2, c , PL) + +def rLine(xy1, xy2, c, PL, xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0): + rLineTo(xy1, 0, PL) + rLineTo(xy2, c , PL) + + +def PolyLineOneColor(xy_list, c, PL , closed ): + #print "--" + #print "c",c + #print "xy_list",xy_list + #print "--" + xy0 = None + for xy in xy_list: + if xy0 is None: + xy0 = xy + #print "xy0:",xy0 + LineTo(xy0,0, PL) + LineTo(xy0,c, PL) + else: + #print "xy:",xy + LineTo(xy,c, PL) + if closed: + LineTo(xy0,c, PL) + + + +# Computing points coordinates for rPolyline function from 3D and around 0,0 to pygame coordinates +def Pointransf(xy, xpos = 0, ypos =0, resize =1, rotx =0, roty =0 , rotz=0): + + x = xy[0] * resize + y = xy[1] * resize + z = 0 + + rad = math.radians(rotx) + cosaX = math.cos(rad) + sinaX = math.sin(rad) + + y2 = y + y = y2 * cosaX - z * sinaX + z = y2 * sinaX + z * cosaX + + rad = math.radians(roty) + cosaY = math.cos(rad) + sinaY = math.sin(rad) + + z2 = z + z = z2 * cosaY - x * sinaY + x = z2 * sinaY + x * cosaY + + rad = math.radians(rotz) + cosZ = math.cos(rad) + sinZ = math.sin(rad) + + x2 = x + x = x2 * cosZ - y * sinZ + y = x2 * sinZ + y * cosZ + + #print xy, (x + xpos,y+ ypos) + return (x + xpos,y+ ypos) + ''' + to understand why it get negative Y + + # 3D to 2D projection + factor = 4 * gstt.cc[22] / ((gstt.cc[21] * 8) + z) + print xy, (x * factor + xpos, - y * factor + ypos ) + return (x * factor + xpos, - y * factor + ypos ) + ''' + +def rLineTo(xy, c, PL, xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0): + + pl[PL].append((Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz) + (c,))) + + +def rLine(xy1, xy2, c, PL, xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0): + + LineTo(Pointransf(xy1, xpos, ypos, resize, rotx, roty, rotz),0, PL) + LineTo(Pointransf(xy2, xpos, ypos, resize, rotx, roty, rotz),c, PL) + + + +# Send 2D point list around 0,0 with 3D rotation resizing and reposition around xpos ypos +#def rPolyLineOneColor(self, xy_list, c, PL , closed, xpos = 0, ypos =0, resize =1, rotx =0, roty =0 , rotz=0): +def rPolyLineOneColor(xy_list, c, PL , closed, xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0): + xy0 = None + for xy in xy_list: + if xy0 is None: + xy0 = xy + LineTo(Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz),0, PL) + LineTo(Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz),c, PL) + else: + LineTo(Pointransf(xy, xpos, ypos, resize, rotx, roty, rotz),c, PL) + if closed: + LineTo(Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz),c, PL) + + +def LineRGBTo(xy, red, green, blue, PL): + + LineTo(xy, int('0x%02x%02x%02x' % (red,green,blue),0), PL) + +def LineRGB(xy1, xy2, red,green,blue, PL): + + LineTo(xy1, 0, PL) + LineTo(xy2, int('0x%02x%02x%02x' % (red,green,blue),0) , PL) + + +def PolyLineRGB(xy_list, red, green, blue, PL , closed ): + + PolyLineOneColor(xy_list, int('0x%02x%02x%02x' % (red,green,blue),0), PL , closed ) + +def rPolyLineRGB(xy_list, red, green, blue, PL , closed, xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0): + + rPolyLineOneColor(xy_list, int('0x%02x%02x%02x' % (red,green,blue),0), PL , closed, xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0) + + + +def LinesPL(PL): + print("Stupido !! your code is to old : use DrawPL() instead of LinesPL()") + DrawPL(PL) + + +def DrawPL(PL): + #print '/pl/0/'+str(PL), str(pl[PL]) + if r.set('/pl/'+str(ClientNumber)+'/'+str(PL), str(pl[PL])) == True: + #print '/pl/'+str(ClientNumber)+'/'+str(PL), str(pl[PL]) + pl[PL] = [] + return True + else: + return False + +def ResetPL(self, PL): + pl[PL] = [] + + +# +# "Destinations" management for PLs +# + + +# Add a destination for a given PL +def Addest(PL, scene, laser): + + print (name,'adding',PL,scene,laser,'?') + if Findest(PL, scene, laser) == -1: + newdest = DestsObjects.counter + 1 + Dest0 = lj.DestObject(str(newdest), newdest, True, PL , scene, laser) + print("New destination added with number", newdest) + else: + print("Destination already existed") + + +# OSC add a destination for a given PL +# /pluginame/dest PL, scene, laser +def OSCadddest(path, tags, args, source): + + Addests(int(args[0]),int(args[1]),int(args[2])) + + +# Find PL destination with its parameters in destinations dictionnary +def Findest(PL, scene, laser): + + print(name, 'searching PL,scene,laser',PL,scene,laser) + for item in DestObjects.getinstances(): + #print(item) + if item.PL == PL and item.scene == scene and item.laser == laser: + #Dests.append(item[0]) + print('found number',item.number) + return item.number + else: + print('no destination found') + return -1 + ''' + #Dests = list() + allDests = Dests.items() + for item in allDests: + print(item) + if item[1] == PL and item[2] == scene and item[3] == laser: + #Dests.append(item[0]) + return Dests[item[0]] + else: + return -1 + ''' + +# Find and remove a PL destination with its parameters in destinations dictionnary +def Deldest(PL, scene, laser): + + Destnumber = Findest(PL, scene, laser) + print(name,'deleting Destination PL, scene, laser', PL,scene, laser) + + if Destnumber != -1: + print('found DestObject', Destnumber) + delattr(DestObjects, str(Destnumber)) + print("Destination", Destnumber,"was removed") + else: + print("Destination was not found") + + +# OSC Delete a destination for a given PL +# /pluginame/deldests PL, scene, laser +def OSCdeldest(path, tags, args, source): + + Deldests(args[0],args[1],args[2]) + + +# Replace DrawPL if Destinations paradigm is implemented in plugin code +def DrawDests(): + + # Objects style + + #print("DrawDest") + + for destination in DestObject.getinstances(): + #print (destination.name, destination.number, destination.active, destination.PL, destination.scene, destination.laser, pl[destination.PL] ) + + #print(Dests[str(destination)]) + #print('/pl/'+str(Dests[str(destination)]["scene"])+'/'+str(Dests[str(destination)]["laser"]), ":", str(pl[Dests[str(destination)]["PL"]])) + #print(len(pl[destination.PL])) + if destination.active == True: + if r.set('/pl/'+str(destination.scene)+'/'+str(destination.laser), str(pl[destination.PL])) == True: + #print ('pl', destination.PL, '/pl/'+str(destination.scene)+'/'+str(destination.laser), str(pl[destination.PL])) + pass + else: + print('Redis key modification failed') + + # Maybe one PL can be sent to multiple destination so they are all reset *after* all sending. + for pls in range(4): + + pl[pls] = [] + + ''' + # Dictionnary style + + #print(Dests) + for destination in range(len(Dests)): + #print(Dests[str(destination)]) + #print('/pl/'+str(Dests[str(destination)]["scene"])+'/'+str(Dests[str(destination)]["laser"]), ":", str(pl[Dests[str(destination)]["PL"]])) + if r.set('/pl/'+str(Dests[str(destination)]["scene"])+'/'+str(Dests[str(destination)]["laser"]), str(pl[Dests[str(destination)]["PL"]])) == True: + #print '/pl/'+str(ClientNumber)+'/'+str(PL), str(pl[PL]) + pass + else: + print('Redis key modification failed') + + # Maybe one PL can be sent to multiple destination so they are all reset *after* all sending. + for destination in range(len(Dests)): + + pl[Dests[str(destination)]["PL"]] = [] + ''' +''' +scenes = 4 + +def DrawDestsPL(PL): + + for scene in range(scenes): + + if Dests[laser]["scene"] != -1: + if r.set('/pl/'+str(Dests[laser]["scene"])+'/'+str(Dests[laser]["laser"]), str(pl[Dests[laser]["laser"]])) == True: + if r.set('/pl/'+str(ClientNumber)+'/'+str(PL), str(pl[PL])) == True: + #print '/pl/'+str(ClientNumber)+'/'+str(PL), str(pl[PL]) + pl[Dests[laser]["laser"]] = [] + return True + else: + return False + +''' + +# +# High level drawing functions +# + + +# Font1 + + +ASCII_GRAPHICS = [ + +#implementé + + [(-50,30), (-30,-30), (30,-30), (10,30), (-50,30)], # 0 + [(-20,30), (0,-30), (-20,30)], # 1 + [(-30,-10), (0,-30), (30,-10), (30,0), (-30,30), (30,30)], # 2 + [(-30,-30), (0,-30), (30,-10), (0,0), (30,10), (0,30), (-30,30)], # 3 + [(30,10), (-30,10), (0,-30), (0,30)], # 4 + [(30,-30), (-30,-30), (-30,0), (0,0), (30,10), (0,30), (-30,30)], # 5 + [(30,-30), (0,-30), (-30,-10), (-30,30), (0,30), (30,10), (30,0), (-30,0)], # 6 + [(-30,-30), (30,-30), (-30,30)], # 7 + [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30), (-30,0), (30,0)], # 8 + [(30,0), (-30,0), (-30,-10), (0,-30), (30,-30), (30,10), (0,30), (-30,30)], # 9 + +# A implementer + [(-30,10), (30,-10), (30,10), (0,30), (-30,10), (-30,-10), (0,-30), (30,-10)], #: + [(-30,-10), (0,-30), (0,30)], [(-30,30), (30,30)], # ; + [(-30,-10), (0,-30), (30,-10), (30,0), (-30,30), (30,30)], # < + [(-30,-30), (0,-30), (30,-10), (0,0), (30,10), (0,30), (-30,30)], # = + [(30,10), (-30,10), (0,-30), (0,30)], # > + [(30,-30), (-30,-30), (-30,0), (0,0), (30,10), (0,30), (-30,30)], # ? + [(30,-30), (0,-30), (-30,-10), (-30,30), (0,30), (30,10), (30,0), (-30,0)], # @ + +# Implementé + + + [(-30,30), (-30,-30), (30,-30), (30,30), (30,0), (-30,0)], # A + [(-30,30), (-30,-30), (30,-30), (30,30), (30,0), (-30,0)], # A + [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30), (-30,0), (30,0)], # B + [(30,30), (-30,30), (-30,-30), (30,-30)], # C + [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30)], # D + [(30,30), (-30,30), (-30,-0), (30,0), (-30,0), (-30,-30), (30,-30)], # E + [(-30,30), (-30,-0), (30,0), (-30,0), (-30,-30), (30,-30)], # F + [(0,0), (30,0), (30,30), (-30,30), (-30,-30),(30,-30)], # G + [(-30,-30), (-30,30), (-30,0), (30,0), (30,30), (30,-30)], # H + [(0,30), (0,-30)], # I + [(-30,30), (0,-30), (0,-30), (-30,-30), (30,-30)], # J + [(-30,-30), (-30,30), (-30,0), (30,-30), (-30,0), (30,30)], # K + [(30,30), (-30,30), (-30,-30)], # L + [(-30,30), (-30,-30), (0,0), (30,-30), (30,30)], # M + [(-30,30), (-30,-30), (30,30), (30,-30)], # N + [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30)], # O + [(-30,0), (30,0), (30,-30), (-30,-30), (-30,30)], # P + [(30,30), (30,-30), (-30,-30), (-30,30), (30,30),(35,35)], # Q + [(-30,30), (-30,-30), (30,-30), (30,0), (-30,0), (30,30)], # R + [(30,-30), (-30,-30), (-30,0), (30,0), (30,30), (-30,30)], # S + [(0,30), (0,-30), (-30,-30), (30,-30)], # T + [(-30,-30), (-30,30), (30,30), (30,-30)], # U + [(-30,-30), (0,30), (30,-30)], # V + [(-30,-30), (-30,30), (0,0), (30,30), (30,-30)], # W + [(-30,30), (30,-30), (-30,-30), (30,30)], # X + [(0,30), (0,0), (30,-30), (0,0), (-30,-30)], # Y + [(30,30), (-30,30), (30,-30), (-30,-30)], # Z + + # A implementer + + [(-30,-10), (0,-30), (0,30)], [(-30,30), (30,30)], # [ + [(-30,-10), (0,-30), (30,-10), (30,0), (-30,30), (30,30)], # \ + [(-30,-30), (0,-30), (30,-10), (0,0), (30,10), (0,30), (-30,30)], # ] + [(30,10), (-30,10), (0,-30), (0,30)], # ^ + [(30,-30), (-30,-30), (-30,0), (0,0), (30,10), (0,30), (-30,30)], # _ + [(30,-30), (0,-30), (-30,-10), (-30,30), (0,30), (30,10), (30,0), (-30,0)], # ` + + # Implementé + + [(-20,20), (-20,-20), (20,-20), (20,20), (20,0), (-20,0)], # a + [(-20,20), (-20,-20), (20,-20), (20,20), (-20,20), (-20,0), (20,0)], # b + [(20,20), (-20,20), (-20,-20), (20,-20)], # c + [(-20,20), (-20,-20), (20,-20), (20,20), (-20,20)], # d + [(20,20), (-20,20), (-20,-0), (20,0), (-20,0), (-20,-20), (20,-20)], # e + [(-20,20), (-20,-0), (20,0), (-20,0), (-20,-20), (20,-20)], # f + [(0,0), (20,0), (20,20), (-20,20), (-20,-20),(20,-20)], # g + [(-20,-20), (-20,20), (-20,0), (20,0), (20,20), (20,-20)], # h + [(0,20), (0,-20)], # i + [(-20,20), (0,-20), (0,-20), (-20,-20), (20,-20)], # j + [(-20,-20), (-20,20), (-20,0), (20,-20), (-20,0), (20,20)], # k + [(20,20), (-20,20), (-20,-20)], # l + [(-20,20), (-20,-20), (0,0), (20,-20), (20,20)], # m + [(-20,20), (-20,-20), (20,20), (20,-20)], # n + [(-20,20), (-20,-20), (20,-20), (20,20), (-20,20)], # o + [(-20,0), (20,0), (20,-20), (-20,-20), (-20,20)], # p + [(20,20), (20,-20), (-20,-20), (-20,20), (20,20),(25,25)], # q + [(-20,20), (-20,-20), (20,-20), (20,0), (-20,0), (20,20)], # r + [(20,-20), (-20,-20), (-20,0), (20,0), (20,20), (-20,20)], # s + [(0,20), (0,-20), (-20,-20), (20,-20)], # t + [(-20,-20), (-20,20), (20,20), (20,-20)], # u + [(-20,-20), (0,20), (20,-20)], # v + [(-20,-20), (-20,20), (0,0), (20,20), (20,-20)], # w + [(-20,20), (20,-20)], [(-20,-20), (20,20)], # x + [(0,20), (0,0), (20,-20), (0,0), (-20,-20)], # y + [(20,20), (-20,20), (20,-20), (-20,-20)], # z + + [(-2,15), (2,15)] # Point a la place de { +] + + +def DigitsDots(number,color): + dots =[] + for dot in ASCII_GRAPHICS[number]: + #print dot + dots.append((gstt.xy_center[0]+dot[0],gstt.xy_center[1]+dot[1],color)) + #self.point_list.append((xy + (c,))) + return dots + +def CharDots(char,color): + + dots =[] + for dot in ASCII_GRAPHICS[ord(char)-46]: + dots.append((dot[0],dot[1],color)) + return dots + +def Text(message,c, PL, xpos, ypos, resize, rotx, roty, rotz): + + dots =[] + + l = len(message) + i= 0 + #print() + # print (message) + + for ch in message: + + #print "" + # texte centre en x automatiquement selon le nombre de lettres l + x_offset = 26 * (- (0.9*l) + 3*i) + # Digits + if ord(ch)<58: + char_pl_list = ASCII_GRAPHICS[ord(ch) - 48] + else: + char_pl_list = ASCII_GRAPHICS[ord(ch) - 46] + + char_draw = [] + #dots.append((char_pl_list[0][0] + x_offset,char_pl_list[0][1],0)) + + for xy in char_pl_list: + char_draw.append((xy[0] + x_offset,xy[1],c)) + i +=1 + #print ch,char_pl_list,char_draw + rPolyLineOneColor(char_draw, c, PL , False, xpos, ypos, resize, rotx, roty, rotz) + #dots.append(char_draw) + +def TextRGB(message,c, PL, xpos, ypos, resize, rotx, roty, rotz): + + Text(message,int('0x%02x%02x%02x' % (red,green,blue),0), PL, xpos, ypos, resize, rotx, roty, rotz) + + + + + + + \ No newline at end of file diff --git a/libs/lj23layers.py b/libs/lj23layers.py new file mode 100644 index 0000000..a443ba9 --- /dev/null +++ b/libs/lj23layers.py @@ -0,0 +1,890 @@ +# coding=UTF-8 +''' + +lj23layers v0.7.6 for LJ v0.8+ + +Some LJ functions useful for python clients + +"layers" version : "PL" has been replaced by layer + + +Class management : + +https://stackoverflow.com/questions/739882/iterating-over-object-instances-of-a-given-class-in-python +https://stackoverflow.com/questions/8628123/counting-instances-of-a-class +http://effbot.org/pyfaq/how-do-i-get-a-list-of-all-instances-of-a-given-class.htm + +Config(redisIP, client number,name) + +Basic Draw : + +- PolyLineOneColor, rPolyLineOneColor, LineTo, Line +- PolyLineRGB, rPolyLineRGB, LineRGBTo, LineRGB +- rgb2int(r,g,b) +- Drawlayer (point list number) : once you stacked all wanted elements, like 2 polylines, send them to lasers. +- DrawDests(): Draw all requested destinations for each layer . + +High level draw : + +- Text(word, integercolor, layer , xpos, ypos, resize, rotx, roty, rotz) : Display a word +- TextRGB(word, red, green, blue, ...) +- Embeded font1 + + +Laser objects (name and convenient group of parameters for one or several point lists) + +- RelativeObject +- FixedObject + +layer "Destinations" : tells Live what layer to draw and to what scene/Laser ("destination") to send it. + + +OSC and plugins functions : + +SendLJ(adress,message) : LJ remote control. See commands.py +SendResol(address,message): Send OSC message to Resolume. +WebStatus(message) : display message on webui + +Ljscene(client): Change scene number in redis keys +Ljlayer(layer): Change layer number in redis keys = laser target. +ClosePlugin(name): Send UI closing info of given plugin + +OSCstart(): Start the OSC system. +OSCframe(): Handle incoming OSC message. Calling the right callback +OSCstop(): Properly close the OSC system +OSCping(): /ping Answer to LJ pings by sending /pong name +OSCquit(): /quit Exit calling script using name in terminal +OSCadddest(): layer , scene, laser Add a destination +OSCdeldest(): layer , scene, lasers delete a destination +OSCobj(): /name/obj objectname attribute value for automation +OSCvar(): /name/var variablename value for automation + +setup_controls(joystick) + +XboxController : getLeftHori, getLeftVert, getRightHori, getRightVert, getLeftTrigger, getRightTrigger +Ps3Controller : getLeftHori, getLeftVert, getRightHori, getRightVert, getLeftTrigger, getRightTrigger, getUp, getDown, getLeft, getRight, getFire1, getFire2(self): +MySaitekController : getLeftHori,getLeftVert, getRightHori,getRightVert, getLeftTrigger,getRightTrigger +MyThrustController : getLeftHori, getLeftVert, getRightHori, getRightVert, getLeftTrigger, getRightTrigger +CSLController : getLeftHori,getLeftVert,getRightHori, getRightVert,getLeftTrigger,getRightTrigger,getFire1,getFire2 +my USB Joystick : getUp,getDown,getLeft,getRight,etLeftTrigger, getRightTrigger,getFire1, getFire2 + + +LICENCE : CC +Sam Neurohack + +''' + +import math +import redis +import sys +import weakref +import struct +import numpy as np +from multiprocessing import Process, Queue, TimeoutError + +is_py2 = sys.version[0] == '2' +if is_py2: + from OSC import OSCServer, OSCClient, OSCMessage + #print ("Importing lj23 and OSC from libs...") +else: + from OSC3 import OSCServer, OSCClient, OSCMessage + #print ("Importing lj23 and OSC3 from libs...") + + +#redisIP = '127.0.0.1' +#r = redis.StrictRedis(host=redisIP, port=6379, db=0) + +ClientNumber = 0 +name = "noname" +oscrun = True +point_list = [] +layers = [[],[],[],[]] + +fft3Groups = [-1,-1,-1,-1] + +Dests = dict() + +oscIPresol = "127.0.0.1" +oscPORTresol = 7000 + + +''' + + Laser "objects" + + + set a name and convenient group of parameters for one or several point lists + + RelativeObject is for point lists around 0,0 with builtin move/rotation. + + How to init with object color, xpos,... : + osciObj = lj.RelativeObject('osciObj', True, 255, [], white, red, green,blue,0 , False, centerX , centerY , 1 , Xrot , Yrot , Zrot) + How to use in drawing functions : you're free to use 0, some or all of any laserobject attributes + - draw one or several pointlists with 'A' laserobject color and 'B' laserobject xpos ypos ? + - Change color of 'main' object and all other objects using it will change also + how to change attribute : + osciObj.resize = 2 or /pluginame/change 'OsciObj' 'resize' 2 + +''' + +class RelativeObject: + + kind = 'relative' + counter = 0 + + def __init__(self, name, active, intensity, xy, color, red, green, blue, layer , closed, xpos , ypos , resize , rotx , roty , rotz): + self.name = name + self.active = active # True/False + self.intensity = intensity + self.xy = [] # Dots list + self.color = color # RGB color in int + self.red = red + self.green = green + self.blue = blue + self.layer = layer + self.closed = closed + self.xpos = xpos + self.ypos = ypos + self.resize = resize + self.rotx = rotx + self.roty = roty + self.rotz = rotz + + RelativeObject.counter += 1 + #type(self).counter += 1 + + def __del__(self): + RelativeObject.counter -= 1 + + +# Fixed Laser object : point list in 'pygame' space (top left = 0,0 / bottom right) +class FixedObject: + + kind = 'fixed' + counter = 0 + + def __init__(self, name, intensity, active, xy, color, red, green, blue, layer , closed): + self.name = name + self.active = active # True/False + self.intensity = intensity + self.xy = [] + self.color = color + self.red = red + self.green = green + self.blue = blue + self.layer = layer + self.closed = closed + + FixedObject.counter += 1 + + def __del__(self): + FixedObject.counter -= 1 + +''' + +class IterDest(type): + def __new__ (cls, name, bases, dct): + dct['_instances'] = [] + return super().__new__(cls, name, bases, dct) + + def __call__(cls, *args, **kwargs): + instance = super().__call__(*args, **kwargs) + cls._instances.append(instance) + return instance + + def __iter__(cls): + return iter(cls._instances) + +class DestObject(): + +# class Destinations(metaclass=IterDest): + __metaclass__ = IterDest + counter = 0 + def __init__(self, name, number, active, layer , scene, laser): + self.name = name + self.number = number + self.active = active + self.layer = layer + self.scene = scene + self.laser = laser + + DestObject.counter += 1 + + def __del__(self): + DestObject.counter -= 1 +''' +class DestObject(): + +# class Destinations(metaclass=IterDest): + _instances = set() + counter = 0 + + def __init__(self, name, number, active, layer , scene, laser): + self.name = name + self.number = number + self.active = active + self.layer = layer + self.scene = scene + self.laser = laser + self._instances.add(weakref.ref(self)) + DestObject.counter += 1 + + @classmethod + def getinstances(cls): + dead = set() + for ref in cls._instances: + obj = ref() + if obj is not None: + yield obj + else: + dead.add(ref) + cls._instances -= dead + + + def __del__(self): + DestObject.counter -= 1 + + + +def Config(redIP,client,myname): + global ClientNumber, name, redisIP, r + + redisIP = redIP + r = redis.StrictRedis(host=redisIP, port=6379, db=0) + + # ClientNumber 255 are not drawing anything like artnet + ClientNumber = client + #print ("client configured",ClientNumber) + name = myname + print ("Plugin declare its name",name) + #print layer + return r + + +def LjClient(client): + global ClientNumber + + ClientNumber = client + + + +def Ljlayer(somelayer): + global layer + + layer = somelayer + + +def fromRedis(n): + + encoded = r.get(n) + #print("") + #print('fromredis key',n,":",encoded) + h, w = struct.unpack('>II',encoded[:8]) + #print("fromredis array size",n,":",h,w) + a = np.frombuffer(encoded, dtype=np.int16, offset=8).reshape(h,w) + #print("fromredis array",n,":",a) + return a + +# Store Numpy array 'a' in Redis key 'n' +# Write also in redis key 'a' numpy array, its 2 dimensions size : h time w values +def toRedis(n,a): + + #print("array.shape", a.shape, len(a.shape) ) + if len(a.shape) == 1: + h = a.shape[0] + w = 1 + else: + h,w = a.shape + #print("toredis", n,"h",h,"w",w,"a",a) + shape = struct.pack('>II',h,w) + + #shape = struct.pack('>II',len(a),1) + #print("toredis",n,a) + encoded = shape + a.tobytes() + + # Store encoded data in Redis + return r.set(n,encoded) + + +# +# OSC functions +# + +# OSC clients + +def SendLJ(oscaddress,oscargs=''): + + oscmsg = OSCMessage() + oscmsg.setAddress(oscaddress) + oscmsg.append(oscargs) + + osclientlj = OSCClient() + osclientlj.connect((redisIP, 8002)) + + print("lj23 in",name," sending OSC message : ", oscmsg, "to", redisIP, ":8002") + try: + osclientlj.sendto(oscmsg, (redisIP, 8002)) + oscmsg.clearData() + except: + print ('Connection to LJ refused : died ?') + pass + #time.sleep(0.001 + + + +# Resolume OSC Arena client. +# sendresol(oscaddress, [arg1, arg2,...]) +# example : sendresol("/noteon",note) + +def SendResol(oscaddress,oscargs): + + oscmsg = OSCMessage() + oscmsg.setAddress(oscaddress) + oscmsg.append(oscargs) + + osclientresol = OSCClient() + osclientresol.connect((oscIPresol, oscPORTresol)) + + print("lj sending OSC message : ", oscmsg, "to Resolume", oscIPresol, ":", oscPORTresol) + try: + osclientresol.sendto(oscmsg, (oscIPresol, oscPORTresol)) + oscmsg.clearData() + except: + print ('Connection to Resolume refused : died ?') + pass + + + + +def WebStatus(message): + SendLJ("/status", message) + + +# Closing plugin messages to LJ +def ClosePlugin(): + WebStatus(name+" Exiting") + SendLJ("/"+name+"/start",0) + + + + +# RAW OSC Frame available ? +def OSCframe(): + # clear timed_out flag + #print "oscframe" + oscserver.timed_out = False + # handle all pending requests then return + while not oscserver.timed_out: + oscserver.handle_request() + +# Answer to LJ pings with /pong value +def OSCping(path, tags, args, source): +#def OSCping(): + print(name, "got /ping from LJ -> reply /pong", name) + SendLJ("/pong",name) + +# Properly close the system. Todo +def OSCstop(): + oscserver.close() + + +# /quit +def OSCquit(path, tags, args, source): + global oscrun + + oscrun = False + print('lj23 got /quit for',name) + #WebStatus(name + " quit.") + #SendLJ("/"+name+"/start",0) + #print("Stopping OSC...") + #OSCstop() + #sys.exit() + + +# default handler +def OSChandler(path, tags, args, source): + + oscaddress = ''.join(path.split("/")) + print("Default OSC Handler in",name,": msg from Client : " + str(source[0]),) + print("OSC address", path) + if len(args) > 0: + print("with args", args) + + #oscIPout = str(source[0]) + #osclient.connect((oscIPout, oscPORTout)) + + +# for any laser object : /pluginame/obj objectname attribute value +# like : /pluginname/obj 'fft' 'xpos' 100 +# attributes for all lj Objects: name, xy_list, c, layer +# + for RelativeObjects : closed, xpos , ypos , resize , rotx , roty , rotz +def OSCobj(path, tags, args, source): + + obj = eval(args[0]+"."+ args[1]) + obj = args[2] + + +def OSCvar(path, tags, args, source): + + obj = eval(args[0]) + obj = args[1] + + +def addOSCdefaults(server): + global oscserver + + oscserver = server + oscserver.addMsgHandler( "default", OSChandler ) + oscserver.addMsgHandler( "/ping", OSCping) + oscserver.addMsgHandler( "/quit", OSCquit) + oscserver.addMsgHandler( "/"+ name + "/adddest", OSCadddest) + oscserver.addMsgHandler( "/"+ name + "/deldest", OSCdeldest) + oscserver.addMsgHandler( "/"+ name + "/obj", OSCobj) + oscserver.addMsgHandler( "/"+ name + "/var", OSCvar) + + + +# +# Drawing basic functions +# + +def rgb2int(r,g,b): + return int('0x%02x%02x%02x' % (r,g,b),0) + + +def LineTo(xy, c, layer ): + + layers[layer].append((xy + (c,))) + +def rLineTo(xy, c, layer , xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0): + + layers[layer ].append((Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz) + (c,))) + + +def Line(xy1, xy2, c, layer ): + LineTo(xy1, 0, layer ) + LineTo(xy2, c , layer ) + +def rLine(xy1, xy2, c, layer , xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0): + rLineTo(xy1, 0, layer ) + rLineTo(xy2, c , layer ) + + +def PolyLineOneColor(xy_list, c, layer , closed ): + #print "--" + #print "c",c + #print "xy_list",xy_list + #print "--" + xy0 = None + for xy in xy_list: + if xy0 is None: + xy0 = xy + #print "xy0:",xy0 + LineTo(xy0,0, layer ) + LineTo(xy0,c, layer ) + else: + #print "xy:",xy + LineTo(xy,c, layer ) + if closed: + LineTo(xy0,c, layer ) + + + +# Computing points coordinates for rPolyline function from 3D and around 0,0 to pygame coordinates +def Pointransf(xy, xpos = 0, ypos =0, resize =1, rotx =0, roty =0 , rotz=0): + + x = xy[0] * resize + y = xy[1] * resize + z = 0 + + rad = math.radians(rotx) + cosaX = math.cos(rad) + sinaX = math.sin(rad) + + y2 = y + y = y2 * cosaX - z * sinaX + z = y2 * sinaX + z * cosaX + + rad = math.radians(roty) + cosaY = math.cos(rad) + sinaY = math.sin(rad) + + z2 = z + z = z2 * cosaY - x * sinaY + x = z2 * sinaY + x * cosaY + + rad = math.radians(rotz) + cosZ = math.cos(rad) + sinZ = math.sin(rad) + + x2 = x + x = x2 * cosZ - y * sinZ + y = x2 * sinZ + y * cosZ + + #print xy, (x + xpos,y+ ypos) + return (x + xpos,y+ ypos) + ''' + to understand why it get negative Y + + # 3D to 2D projection + factor = 4 * gstt.cc[22] / ((gstt.cc[21] * 8) + z) + print xy, (x * factor + xpos, - y * factor + ypos ) + return (x * factor + xpos, - y * factor + ypos ) + ''' + +def rLineTo(xy, c, layer , xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0): + + layers[layer ].append((Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz) + (c,))) + + +def rLine(xy1, xy2, c, layer , xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0): + + LineTo(Pointransf(xy1, xpos, ypos, resize, rotx, roty, rotz),0, layer ) + LineTo(Pointransf(xy2, xpos, ypos, resize, rotx, roty, rotz),c, layer ) + + + +# Send 2D point list around 0,0 with 3D rotation resizing and reposition around xpos ypos +#def rPolyLineOneColor(self, xy_list, c, layer , closed, xpos = 0, ypos =0, resize =1, rotx =0, roty =0 , rotz=0): +def rPolyLineOneColor(xy_list, c, layer , closed, xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0): + xy0 = None + for xy in xy_list: + if xy0 is None: + xy0 = xy + LineTo(Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz),0, layer ) + LineTo(Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz),c, layer ) + else: + LineTo(Pointransf(xy, xpos, ypos, resize, rotx, roty, rotz),c, layer ) + if closed: + LineTo(Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz),c, layer ) + + +def LineRGBTo(xy, red, green, blue, layer ): + + LineTo(xy, int('0x%02x%02x%02x' % (red,green,blue),0), layer ) + +def LineRGB(xy1, xy2, red,green,blue, layer ): + + LineTo(xy1, 0, layer ) + LineTo(xy2, int('0x%02x%02x%02x' % (red,green,blue),0) , layer ) + + +def PolyLineRGB(xy_list, red, green, blue, layer , closed ): + + PolyLineOneColor(xy_list, int('0x%02x%02x%02x' % (red,green,blue),0), layer , closed ) + +def rPolyLineRGB(xy_list, red, green, blue, layer , closed, xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0): + + rPolyLineOneColor(xy_list, int('0x%02x%02x%02x' % (red,green,blue),0), layer , closed, xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0) + + + +def Lineslayer(layer): + print("Stupido !! your code is to old : use Drawlayer() instead of LinesPL()") + Drawlayer(layer ) + + +def Draw(layer ): + #print '/pl/0/'+str(layer), str(layers[layer]) + if r.set('/pl/'+str(ClientNumber)+'/'+str(layer), str(layers[layer])) == True: + #print '/pl/'+str(ClientNumber)+'/'+str(layer), str(layers[layer]) + layers[layer] = [] + return True + else: + return False + +def Resetlayer(self, layer): + layers[layer] = [] + + +# +# "Destinations" management for layers +# + + +# Add a destination for a given layer +def Addest(layer, scene, laser): + + print (name,'adding',layer,scene,laser,'?') + if Findest(layer, scene, laser) == -1: + newdest = DestsObjects.counter + 1 + Dest0 = lj.DestObject(str(newdest), newdest, True, layer , scene, laser) + print("New destination added with number", newdest) + else: + print("Destination already existed") + + +# OSC add a destination for a given layer +# /pluginame/dest layer, scene, laser +def OSCadddest(path, tags, args, source): + + Addests(int(args[0]),int(args[1]),int(args[2])) + + +# Find layer destination with its parameters in destinations dictionnary +def Findest(layer, scene, laser): + + print(name, 'searching layer,scene,laser',layer,scene,laser) + for item in DestObjects.getinstances(): + #print(item) + if item.layer == layer and item.scene == scene and item.laser == laser: + #Dests.append(item[0]) + print('found number',item.number) + return item.number + else: + print('no destination found') + return -1 + ''' + #Dests = list() + allDests = Dests.items() + for item in allDests: + print(item) + if item[1] == layer and item[2] == scene and item[3] == laser: + #Dests.append(item[0]) + return Dests[item[0]] + else: + return -1 + ''' + +# Find and remove a layer destination with its parameters in destinations dictionnary +def Deldest(layer, scene, laser): + + Destnumber = Findest(layer, scene, laser) + print(name,'deleting Destination layer, scene, laser', layer,scene, laser) + + if Destnumber != -1: + print('found DestObject', Destnumber) + delattr(DestObjects, str(Destnumber)) + print("Destination", Destnumber,"was removed") + else: + print("Destination was not found") + + +# OSC Delete a destination for a given layer +# /pluginame/deldests layer, scene, laser +def OSCdeldest(path, tags, args, source): + + Deldests(args[0],args[1],args[2]) + + +# Replace Drawlayer if Destinations paradigm is implemented in plugin code +def DrawDests(): + + # Objects style + + #print("DrawDest") + + for destination in DestObject.getinstances(): + #print (destination.name, destination.number, destination.active, destination.layer, destination.scene, destination.laser, layers[destination.layer] ) + + #print(Dests[str(destination)]) + #print('/pl/'+str(Dests[str(destination)]["scene"])+'/'+str(Dests[str(destination)]["laser"]), ":", str(layers[Dests[str(destination)]["PL"]])) + #print(len(layers[destination.layer])) + if destination.active == True: + if r.set('/pl/'+str(destination.scene)+'/'+str(destination.laser), str(layers[destination.layer])) == True: + #print ('layer', destination.layer, '/pl/'+str(destination.scene)+'/'+str(destination.laser), str(layers[destination.layer])) + pass + else: + print('Redis key modification failed') + + # Maybe one layer can be sent to multiple destination so they are all reset *after* all sending. + for layerss in range(4): + + layers[layerss] = [] + + ''' + # Dictionnary style + + #print(Dests) + for destination in range(len(Dests)): + #print(Dests[str(destination)]) + #print('/pl/'+str(Dests[str(destination)]["scene"])+'/'+str(Dests[str(destination)]["laser"]), ":", str(layers[Dests[str(destination)]["layer"]])) + if r.set('/pl/'+str(Dests[str(destination)]["scene"])+'/'+str(Dests[str(destination)]["laser"]), str(layers[Dests[str(destination)]["layer"]])) == True: + #print '/pl/'+str(ClientNumber)+'/'+str(layer), str(layers[layer]) + pass + else: + print('Redis key modification failed') + + # Maybe one layer can be sent to multiple destination so they are all reset *after* all sending. + for destination in range(len(Dests)): + + layers[Dests[str(destination)]["layer"]] = [] + ''' +''' +scenes = 4 + +def DrawDestslayer(layer): + + for scene in range(scenes): + + if Dests[laser]["scene"] != -1: + if r.set('/pl/'+str(Dests[laser]["scene"])+'/'+str(Dests[laser]["laser"]), str(layers[Dests[laser]["laser"]])) == True: + if r.set('/pl/'+str(ClientNumber)+'/'+str(layer), str(layers[layer])) == True: + #print '/pl/'+str(ClientNumber)+'/'+str(layer), str(layers[layer]) + layers[Dests[laser]["laser"]] = [] + return True + else: + return False + +''' + +# +# High level drawing functions +# + + +# Font1 + + +ASCII_GRAPHICS = [ + +#implementé + + [(-50,30), (-30,-30), (30,-30), (10,30), (-50,30)], # 0 + [(-20,30), (0,-30), (-20,30)], # 1 + [(-30,-10), (0,-30), (30,-10), (30,0), (-30,30), (30,30)], # 2 + [(-30,-30), (0,-30), (30,-10), (0,0), (30,10), (0,30), (-30,30)], # 3 + [(30,10), (-30,10), (0,-30), (0,30)], # 4 + [(30,-30), (-30,-30), (-30,0), (0,0), (30,10), (0,30), (-30,30)], # 5 + [(30,-30), (0,-30), (-30,-10), (-30,30), (0,30), (30,10), (30,0), (-30,0)], # 6 + [(-30,-30), (30,-30), (-30,30)], # 7 + [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30), (-30,0), (30,0)], # 8 + [(30,0), (-30,0), (-30,-10), (0,-30), (30,-30), (30,10), (0,30), (-30,30)], # 9 + +# A implementer + [(-30,10), (30,-10), (30,10), (0,30), (-30,10), (-30,-10), (0,-30), (30,-10)], #: + [(-30,-10), (0,-30), (0,30)], [(-30,30), (30,30)], # ; + [(-30,-10), (0,-30), (30,-10), (30,0), (-30,30), (30,30)], # < + [(-30,-30), (0,-30), (30,-10), (0,0), (30,10), (0,30), (-30,30)], # = + [(30,10), (-30,10), (0,-30), (0,30)], # > + [(30,-30), (-30,-30), (-30,0), (0,0), (30,10), (0,30), (-30,30)], # ? + [(30,-30), (0,-30), (-30,-10), (-30,30), (0,30), (30,10), (30,0), (-30,0)], # @ + +# Implementé + + + [(-30,30), (-30,-30), (30,-30), (30,30), (30,0), (-30,0)], # A + [(-30,30), (-30,-30), (30,-30), (30,30), (30,0), (-30,0)], # A + [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30), (-30,0), (30,0)], # B + [(30,30), (-30,30), (-30,-30), (30,-30)], # C + [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30)], # D + [(30,30), (-30,30), (-30,-0), (30,0), (-30,0), (-30,-30), (30,-30)], # E + [(-30,30), (-30,-0), (30,0), (-30,0), (-30,-30), (30,-30)], # F + [(0,0), (30,0), (30,30), (-30,30), (-30,-30),(30,-30)], # G + [(-30,-30), (-30,30), (-30,0), (30,0), (30,30), (30,-30)], # H + [(0,30), (0,-30)], # I + [(-30,30), (0,-30), (0,-30), (-30,-30), (30,-30)], # J + [(-30,-30), (-30,30), (-30,0), (30,-30), (-30,0), (30,30)], # K + [(30,30), (-30,30), (-30,-30)], # L + [(-30,30), (-30,-30), (0,0), (30,-30), (30,30)], # M + [(-30,30), (-30,-30), (30,30), (30,-30)], # N + [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30)], # O + [(-30,0), (30,0), (30,-30), (-30,-30), (-30,30)], # P + [(30,30), (30,-30), (-30,-30), (-30,30), (30,30),(35,35)], # Q + [(-30,30), (-30,-30), (30,-30), (30,0), (-30,0), (30,30)], # R + [(30,-30), (-30,-30), (-30,0), (30,0), (30,30), (-30,30)], # S + [(0,30), (0,-30), (-30,-30), (30,-30)], # T + [(-30,-30), (-30,30), (30,30), (30,-30)], # U + [(-30,-30), (0,30), (30,-30)], # V + [(-30,-30), (-30,30), (0,0), (30,30), (30,-30)], # W + [(-30,30), (30,-30), (-30,-30), (30,30)], # X + [(0,30), (0,0), (30,-30), (0,0), (-30,-30)], # Y + [(30,30), (-30,30), (30,-30), (-30,-30)], # Z + + # A implementer + + [(-30,-10), (0,-30), (0,30)], [(-30,30), (30,30)], # [ + [(-30,-10), (0,-30), (30,-10), (30,0), (-30,30), (30,30)], # \ + [(-30,-30), (0,-30), (30,-10), (0,0), (30,10), (0,30), (-30,30)], # ] + [(30,10), (-30,10), (0,-30), (0,30)], # ^ + [(30,-30), (-30,-30), (-30,0), (0,0), (30,10), (0,30), (-30,30)], # _ + [(30,-30), (0,-30), (-30,-10), (-30,30), (0,30), (30,10), (30,0), (-30,0)], # ` + + # Implementé + + [(-20,20), (-20,-20), (20,-20), (20,20), (20,0), (-20,0)], # a + [(-20,20), (-20,-20), (20,-20), (20,20), (-20,20), (-20,0), (20,0)], # b + [(20,20), (-20,20), (-20,-20), (20,-20)], # c + [(-20,20), (-20,-20), (20,-20), (20,20), (-20,20)], # d + [(20,20), (-20,20), (-20,-0), (20,0), (-20,0), (-20,-20), (20,-20)], # e + [(-20,20), (-20,-0), (20,0), (-20,0), (-20,-20), (20,-20)], # f + [(0,0), (20,0), (20,20), (-20,20), (-20,-20),(20,-20)], # g + [(-20,-20), (-20,20), (-20,0), (20,0), (20,20), (20,-20)], # h + [(0,20), (0,-20)], # i + [(-20,20), (0,-20), (0,-20), (-20,-20), (20,-20)], # j + [(-20,-20), (-20,20), (-20,0), (20,-20), (-20,0), (20,20)], # k + [(20,20), (-20,20), (-20,-20)], # l + [(-20,20), (-20,-20), (0,0), (20,-20), (20,20)], # m + [(-20,20), (-20,-20), (20,20), (20,-20)], # n + [(-20,20), (-20,-20), (20,-20), (20,20), (-20,20)], # o + [(-20,0), (20,0), (20,-20), (-20,-20), (-20,20)], # p + [(20,20), (20,-20), (-20,-20), (-20,20), (20,20),(25,25)], # q + [(-20,20), (-20,-20), (20,-20), (20,0), (-20,0), (20,20)], # r + [(20,-20), (-20,-20), (-20,0), (20,0), (20,20), (-20,20)], # s + [(0,20), (0,-20), (-20,-20), (20,-20)], # t + [(-20,-20), (-20,20), (20,20), (20,-20)], # u + [(-20,-20), (0,20), (20,-20)], # v + [(-20,-20), (-20,20), (0,0), (20,20), (20,-20)], # w + [(-20,20), (20,-20)], [(-20,-20), (20,20)], # x + [(0,20), (0,0), (20,-20), (0,0), (-20,-20)], # y + [(20,20), (-20,20), (20,-20), (-20,-20)], # z + + [(-2,15), (2,15)] # Point a la place de { +] + + +def DigitsDots(number,color): + dots =[] + for dot in ASCII_GRAPHICS[number]: + #print dot + dots.append((gstt.xy_center[0]+dot[0],gstt.xy_center[1]+dot[1],color)) + #self.point_list.append((xy + (c,))) + return dots + +def CharDots(char,color): + + dots =[] + for dot in ASCII_GRAPHICS[ord(char)-46]: + dots.append((dot[0],dot[1],color)) + return dots + +def Text(message,c, layer, xpos, ypos, resize, rotx, roty, rotz): + + dots =[] + + l = len(message) + i= 0 + #print() + # print (message) + + for ch in message: + + #print "" + # texte centre en x automatiquement selon le nombre de lettres l + x_offset = 26 * (- (0.9*l) + 3*i) + # Digits + if ord(ch)<58: + char_layer_list = ASCII_GRAPHICS[ord(ch) - 48] + else: + char_layer_list = ASCII_GRAPHICS[ord(ch) - 46] + + char_draw = [] + #dots.append((char_layer_list[0][0] + x_offset,char_layer_list[0][1],0)) + + for xy in char_layer_list: + char_draw.append((xy[0] + x_offset,xy[1],c)) + i +=1 + #print ch,char_layer_list,char_draw + rPolyLineOneColor(char_draw, c, layer , False, xpos, ypos, resize, rotx, roty, rotz) + #dots.append(char_draw) + +def TextRGB(message,c, layer, xpos, ypos, resize, rotx, roty, rotz): + + Text(message,int('0x%02x%02x%02x' % (red,green,blue),0), layer, xpos, ypos, resize, rotx, roty, rotz) + + + + + + + \ No newline at end of file diff --git a/plugins/VJing/lj3.py b/libs/lj3.py similarity index 94% rename from plugins/VJing/lj3.py rename to libs/lj3.py index 44e016a..30f5c1c 100644 --- a/plugins/VJing/lj3.py +++ b/libs/lj3.py @@ -1,14 +1,13 @@ -# coding=UTF-8 ''' -lj3 v0.8.1 +lj3 v0.7.5 for LJ v0.8+ -Some LJ functions useful for python clients (was framy.py) +Some LJ functions useful for python clients OSC functions commented, waiting working on OSC in python3 -Config(redisIP, client number) +Config(redisIP, client number,name) PolyLineOneColor rPolyLineOneColor @@ -19,13 +18,13 @@ DrawPL(point list number) : once you stacked all wanted elements, like 2 polylin rgb2int(r,g,b) LjClient(client): Change Client number in redis keys LjPl(pl): Change pl number in redis keys = laser target. - +ClosePlugin(name): Send UI closing info of given plugin OSCstart(): Start the OSC system. OSCframe(): Handle incoming OSC message. Calling the right callback OSCstop(): Properly close the OSC system -OSCping(value): Answer to LJ pings by sending /pong value -OSCquit(name): Exit calling script using name in terminal +OSCping(): Answer to LJ pings by sending /pong name +OSCquit(): Exit calling script using name in terminal setup_controls(joystick) @@ -55,8 +54,9 @@ from osc4py3.oscmethod import * #redisIP = '127.0.0.1' #r = redis.StrictRedis(host=redisIP, port=6379, db=0) +print('Importing lj3 from libs...') ClientNumber = 0 - +name = "noname" point_list = [] pl = [[],[],[],[]] @@ -83,33 +83,41 @@ def SendLJ(oscaddress,oscargs=''): try: msg = oscbuildparse.OSCMessage(oscaddress, None, [oscargs]) # print(msg) + print("lj3 sending OSC message to", redisIP, ":8002") osc_send(msg, "LJ 8002") OSCframe() except: - print ('Connection to LJ refused : died ?') + print (oscaddress,'Connection to LJ refused : died ?') pass def WebStatus(message): SendLJ("/status", message) -# Answer to LJ pings +# Answer to LJ /ping 1 with /pong name def OSCping(value): - # Will receive message address, and message data flattened in s, x, y - print("Got /ping with value", value) - SendLJ("/pong",value) + print(name,"got /ping 1 from LJ -> reply /pong", name) + SendLJ("/pong",name) +# Closing plugin messages to LJ +def ClosePlugin(): + WebStatus(name+" Exit") + SendLJ("/"+name+"/start",0) + print("Stopping OSC...") + OSCstop() + +''' # /quit -def OSCquit(name): +def OSCquit(): WebStatus(name + " quit.") + SendLJ("/"+name+"/start",0) print("Stopping OSC...") - OSCstop() sys.exit() - +''' ''' def handlerfunction(s, x, y): # Will receive message data unpacked in s, x, y @@ -226,9 +234,11 @@ def rgb2int(r,g,b): return int('0x%02x%02x%02x' % (r,g,b),0) -def Config(redisIP,client): - global ClientNumber, r +def Config(redisIP,client,myname): + global ClientNumber, r, name + name = myname + print ("lj3 got a name to use :", name) r = redis.StrictRedis(host=redisIP, port=6379, db=0) ClientNumber = client osc_udp_client(redisIP, 8002, "LJ 8002") diff --git a/libs/maxwell.json b/libs/maxwell.json new file mode 100644 index 0000000..41ef3d9 --- /dev/null +++ b/libs/maxwell.json @@ -0,0 +1,669 @@ +{ +"ccs": [ + { + "_comment": "Oscillator LEFT X Functions", + "Function": "/osc/left/X/curvetype", + "init": "sin" + }, + { + "Function": "/osc/left/X/freq", + "init": "1" + }, + { + "Function": "/osc/left/X/freqlimit", + "init": "127" + }, + { + "Function": "/osc/left/X/amp", + "init": "100" + }, + { + "Function": "/osc/left/X/amplimit", + "init": "constant" + }, + { + "Function": "/osc/left/X/phasemod", + "init": "64" + }, + { + "Function": "/osc/left/X/phasemodlimit", + "init": "linear" + }, + { + "Function": "/osc/left/X/phaseoffset", + "init": "64" + }, + { + "Function": "/osc/left/X/phaseoffsetlimit", + "init": "manual" + }, + { + "Function": "/osc/left/X/ampoffset", + "init": "64" + }, + { + "Function": "/osc/left/X/ampoffsetlimit", + "init": "manual" + }, + + { + "Function": "/osc/left/X/inversion", + "init": "off" + }, + + + + { + "_comment": "Oscillator LEFT Y Functions", + "Function": "/osc/left/Y/curvetype", + "init": "sin" + }, + { + "Function": "/osc/left/Y/freq", + "init": "1" + }, + { + "Function": "/osc/left/Y/freqlimit", + "init": "127" + }, + { + "Function": "/osc/left/Y/amp", + "init": "100" + }, + { + "Function": "/osc/left/Y/amplimit", + "init": "constant" + }, + { + "Function": "/osc/left/Y/phasemod", + "init": "64" + }, + { + "Function": "/osc/left/Y/phasemodlimit", + "init": "linear" + }, + { + "Function": "/osc/left/Y/phaseoffset", + "init": "64" + }, + { + "Function": "/osc/left/Y/phaseoffsetlimit", + "init": "manual" + }, + { + "Function": "/osc/left/Y/ampoffset", + "init": "64" + }, + { + "Function": "/osc/left/Y/ampoffsetlimit", + "init": "manual" + }, + + { + "Function": "/osc/left/Y/inversion", + "init": "off" + }, + + + + { + "_comment": "Oscillator LEFT Z Functions", + "Function": "/osc/left/Z/curvetype", + "init": "sin" + }, + { + "Function": "/osc/left/Z/freq", + "init": "1" + }, + { + "Function": "/osc/left/Z/freqlimit", + "init": "127" + }, + { + "Function": "/osc/left/Z/amp", + "init": "100" + }, + { + "Function": "/osc/left/Z/amplimit", + "init": "constant" + }, + { + "Function": "/osc/left/Z/phasemod", + "init": "64" + }, + { + "Function": "/osc/left/Z/phasemodlimit", + "init": "linear" + }, + { + "Function": "/osc/left/Z/phaseoffset", + "init": "64" + }, + { + "Function": "/osc/left/Z/phaseoffsetlimit", + "init": "manual" + }, + { + "Function": "/osc/left/Z/ampoffset", + "init": "64" + }, + { + "Function": "/osc/left/Z/ampoffsetlimit", + "init": "manual" + }, + + { + "Function": "/osc/left/Z/inversion", + "init": "off" + }, + + + { + "_comment": "Oscillator RIGHT X Functions", + "Function": "/osc/right/X/curvetype", + "init": "sin" + }, + { + "Function": "/osc/right/X/freq", + "init": "1" + }, + { + "Function": "/osc/right/X/freqlimit", + "init": "127" + }, + { + "Function": "/osc/right/X/amp", + "init": "100" + }, + { + "Function": "/osc/right/X/amplimit", + "init": "constant" + }, + { + "Function": "/osc/right/X/phasemod", + "init": "64" + }, + { + "Function": "/osc/right/X/phasemodlimit", + "init": "linear" + }, + { + "Function": "/osc/right/X/phaseoffset", + "init": "64" + }, + { + "Function": "/osc/right/X/phaseoffsetlimit", + "init": "manual" + }, + { + "Function": "/osc/right/X/ampoffset", + "init": "64" + }, + { + "Function": "/osc/right/X/ampoffsetlimit", + "init": "manual" + }, + + { + "Function": "/osc/right/X/inversion", + "init": "off" + }, + + + + { + "_comment": "Oscillator RIGHT Y Functions", + "Function": "/osc/right/Y/curvetype", + "init": "sin" + }, + { + "Function": "/osc/right/Y/freq", + "init": "1" + }, + { + "Function": "/osc/right/Y/freqlimit", + "init": "127" + }, + { + "Function": "/osc/right/Y/amp", + "init": "100" + }, + { + "Function": "/osc/right/Y/amplimit", + "init": "constant" + }, + { + "Function": "/osc/right/Y/phasemod", + "init": "64" + }, + { + "Function": "/osc/right/Y/phasemodlimit", + "init": "linear" + }, + { + "Function": "/osc/right/Y/phaseoffset", + "init": "64" + }, + { + "Function": "/osc/right/Y/phaseoffsetlimit", + "init": "manual" + }, + { + "Function": "/osc/right/Y/ampoffset", + "init": "64" + }, + { + "Function": "/osc/right/Y/ampoffsetlimit", + "init": "manual" + }, + + { + "Function": "/osc/right/Y/inversion", + "init": "off" + }, + + + + { + "_comment": "Oscillator RIGHT Z Functions", + "Function": "/osc/right/Z/curvetype", + "init": "sin" + }, + { + "Function": "/osc/right/Z/freq", + "init": "1" + }, + { + "Function": "/osc/right/Z/freqlimit", + "init": "127" + }, + { + "Function": "/osc/right/Z/amp", + "init": "100" + }, + { + "Function": "/osc/right/Z/amplimit", + "init": "constant" + }, + { + "Function": "/osc/right/Z/phasemod", + "init": "64" + }, + { + "Function": "/osc/right/Z/phasemodlimit", + "init": "linear" + }, + { + "Function": "/osc/right/Z/phaseoffset", + "init": "64" + }, + { + "Function": "/osc/right/Z/phaseoffsetlimit", + "init": "manual" + }, + { + "Function": "/osc/right/Z/ampoffset", + "init": "64" + }, + { + "Function": "/osc/right/Z/ampoffsetlimit", + "init": "manual" + }, + + { + "Function": "/osc/right/Z/inversion", + "init": "off" + }, + + + + + + + + { + "_comment": "LFO 1 Functions", + "Function": "/lfo/1/curvetype", + "init": "sin" + }, + { + "Function": "/lfo/1/freq", + "init": "64" + }, + { + "Function": "/lfo/1/freqlimit", + "init": "127" + }, + + { + "Function": "/lfo/1/phase", + "init": "64" + }, + + { + "Function": "/lfo/1/inversion", + "init": "off" + }, + + + + + + { + "_comment": "LFO 2 Functions", + "Function": "/lfo/2/curvetype", + "init": "sin" + }, + { + "Function": "/lfo/2/freq", + "init": "64" + }, + { + "Function": "/lfo/2/freqlimit", + "init": "127" + }, + + { + "Function": "/lfo/2/phase", + "init": "64" + }, + + { + "Function": "/lfo/2/inversion", + "init": "off" + }, + + + + + + { + "_comment": "LFO 3 Functions", + "Function": "/lfo/3/curvetype", + "init": "sin" + }, + { + "Function": "/lfo/3/freq", + "init": "64" + }, + { + "Function": "/lfo/3/freqlimit", + "init": "127" + }, + + { + "Function": "/lfo/3/phase", + "init": "64" + }, + + { + "Function": "/lfo/3/inversion", + "init": "off" + }, + + + + + + { + "_comment": "Duplicator Functions", + "Function": "/duplicator/num", + "init": "0" + }, + { + "Function": "/duplicator/offset", + "init": "0" + }, + + { + "_comment": "Mixer Functions", + "Function": "/mixer/operation", + "init": "+" + }, + { + "Function": "/mixer/value", + "init": "0" + }, + + + + { + "_comment": "Intensity Functions", + "Function": "/intensity/mod", + "init": "0" + }, + { + "Function": "/intensity/freq", + "init": "0" + }, + + + { + "_comment": "Scaler Functions", + "Function": "/scaler/curvetype", + "init": "sin" + }, + { + "Function": "/scaler/speed", + "init": "64" + }, + { + "Function": "/scaler/switch", + "init": "off" + }, + { + "Function": "/scaler/width", + "init": "64" + }, + + { + "Function": "/scaler/amt", + "init": "64" + }, + { + "Function": "/scaler/scale", + "init": "64" + }, + + + + { + "_comment": "Rotator X Functions", + "Function": "/rotator/X/curvetype", + "init": "sin" + }, + { + "Function": "/rotator/X/speed", + "init": "64" + }, + + { + "Function": "/rotator/X/lfo/switch", + "init": "off" + }, + + { + "Function": "/rotator/X/direct", + "init": "64" + }, + + + + { + "_comment": "Rotator Y Functions", + "Function": "/rotator/Y/curvetype", + "init": "sin" + }, + { + "Function": "/rotator/Y/speed", + "init": "64" + }, + + { + "Function": "/rotator/Y/lfo/switch", + "init": "off" + }, + + { + "Function": "/rotator/Y/direct", + "init": "64" + }, + + + + + { + "_comment": "Rotator Z Functions", + "Function": "/rotator/Z/curvetype", + "init": "sin" + }, + { + "Function": "/rotator/Z/speed", + "init": "64" + }, + + { + "Function": "/rotator/Z/lfo/switch", + "init": "off" + }, + + { + "Function": "/rotator/Z/direct", + "init": "64" + }, + + + + { + "_comment": "Translator X Functions", + "Function": "/translator/X/curvetype", + "init": "sin" + }, + { + "Function": "/translator/X/speed", + "init": "64" + }, + + { + "Function": "/translator/X/lfo/switch", + "init": "off" + }, + + { + "Function": "/translator/X/amt", + "init": "64" + }, + + + + + + + { + "_comment": "Translator Y Functions", + "Function": "/translator/Y/curvetype", + "init": "sin" + }, + { + "Function": "/translator/Y/speed", + "init": "64" + }, + + { + "Function": "/translator/Y/lfo/switch", + "init": "off" + }, + + { + "Function": "/translator/Y/amt", + "init": "64" + }, + + + + + + { + "_comment": "Translator Z Functions", + "Function": "/translator/Z/curvetype", + "init": "sin" + }, + { + "Function": "/translator/Z/speed", + "init": "64" + }, + + { + "Function": "/translator/Z/lfo/switch", + "init": "off" + }, + + { + "Function": "/translator/Z/amt", + "init": "64" + }, + + + + + + + { + "_comment": "Colors Functions", + "Function": "/color/colortype", + "init": "solid" + }, + { + "Function": "/color/huewidth", + "init": "0" + }, + { + "Function": "/color/hueoff", + "init": "0" + }, + { + "Function": "/color/huemod", + "init": "0" + }, + { + "Function": "/color/huerot", + "init": "0" + }, + { + "Function": "/color/intwidth", + "init": "0" + }, + { + "Function": "/color/intoff", + "init": "0" + }, + { + "Function": "/color/intmod", + "init": "0" + }, + { + "Function": "/color/intfreq", + "init": "0" + }, + + { + "Function": "/color/satwidth", + "init": "0" + }, + + { + "Function": "/color/satmod", + "init": "0" + }, + { + "Function": "/color/saturation", + "init": "127" + }, + { + "Function": "/color/modtype", + "init": "sin" + } + + +] +} diff --git a/libs/midi.py b/libs/midi.py new file mode 100644 index 0000000..85d36d5 --- /dev/null +++ b/libs/midi.py @@ -0,0 +1,452 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +""" +LJay/LJ +v0.7.0 + +Midi Handler + +Deprecated, see midi3 + +by Sam Neurohack +from /team/laser + +""" + +print "importing midi 0" +import time + +import rtmidi +from rtmidi.midiutil import open_midiinput +from threading import Thread +from rtmidi.midiconstants import (CHANNEL_PRESSURE, CONTROLLER_CHANGE, NOTE_ON, NOTE_OFF, + PITCH_BEND, POLY_PRESSURE, PROGRAM_CHANGE) +import mido +from mido import MidiFile +#import mido +import sys +from serial.tools import list_ports +import serial + +from sys import platform +import gstt + +# import bhoroscp + +import bhoreal +import launchpad +from OSC import OSCServer, OSCClient, OSCMessage +#import orbits + + +midiname = ["Name"] * 16 +midiport = [rtmidi.MidiOut() for i in range(16) ] + + + +is_py2 = sys.version[0] == '2' +if is_py2: + from Queue import Queue +else: + from queue import Queue + +# max 16 midi port array + +midinputsname = ["Name"] * 16 +midinputsqueue = [Queue() for i in range(16) ] +midinputs = [] + + +BhorealMidiName = "Bhoreal" +LaunchMidiName = "Launch" + +BhorealPort, Midi1Port, Midi2Port, VirtualPort, MPort = -1,-1,-1, -1, -1 +VirtualName = "LaunchPad Mini" +Mser = False + +# Myxolidian 3 notes chords list +Myxo = [(59,51,54),(49,52,56),(49,52,56),(51,54,57),(52,56,59),(52,56,59),(54,57,48),(57,49,52)] +MidInsNumber = 0 + +try: + input = raw_input +except NameError: + # Python 3 + StandardError = Exception + + +STATUS_MAP = { + 'noteon': NOTE_ON, + 'noteoff': NOTE_OFF, + 'programchange': PROGRAM_CHANGE, + 'controllerchange': CONTROLLER_CHANGE, + 'pitchbend': PITCH_BEND, + 'polypressure': POLY_PRESSURE, + 'channelpressure': CHANNEL_PRESSURE +} + +# OSC destination list for incoming midi +midi2OSC = { + "lj": {"oscip": "127.0.0.1", "oscport": 8002, "notes": False, "msgs": False}, + "nozoid": {"oscip": "127.0.0.1", "oscport": 8003, "notes": False, "msgs": False}, + "dump": {"oscip": "127.0.0.1", "oscport": 8040, "notes": True, "msgs": True} + } + + + +#mycontroller.midiport[LaunchHere].send_message([CONTROLLER_CHANGE, LaunchTop[number-1], color]) + +def send(device,msg): + + ''' + # if device is the midi name + if device in midiname: + deviceport = midiname.index(device) + midiport[deviceport].send_message(msg) + ''' + if device == "Launchpad": + #print LaunchHere + midiport[gstt.LaunchHere].send_message(msg) + + if device == "Bhoreal": + midiport[gstt.BhorealHere].send_message(msg) + +def NoteOn(note, color): + global MidInsNumber + + gstt.note = note + gstt.velocity = color + + for port in range(MidInsNumber): + + if midiname[port].find(LaunchMidiName) == 0: + launchpad.PadNoteOn(note%64,color) + + if midiname[port].find(BhorealMidiName) == 0: + gstt.BhorLeds[note%64]=color + midiport[port].send_message([NOTE_ON, note%64, color]) + #bhorosc.sendosc("/bhoreal", [note%64 , color]) + + if midiname[port].find(BhorealMidiName) != 0 and midiname[port].find(LaunchMidiName) != 0: + midiport[port].send_message([NOTE_ON, note, color]) + + #virtual.send_message([NOTE_ON, note, color]) + + for OSCtarget in midi2OSC: + if midi2OSC[OSCtarget]['notes']: + pass + #OSCsend(OSCtarget, "/noteon", [note, color]) + + +def NoteOff(note): + global MidInsNumber + + gstt.note = note + gstt.velocity = 0 + for port in range(MidInsNumber): + + if midiname[port].find(LaunchMidiName) == 0: + launchpad.PadNoteOff(note%64) + + if midiname[port].find(BhorealMidiName) == 0: + midiport[port].send_message([NOTE_OFF, note%64, 0]) + gstt.BhorLeds[note%64]=0 + #bhorosc.sendosc("/bhoreal", [note%64 , 0]) + + if midiname[port].find(BhorealMidiName) != 0 and midiname[port].find(LaunchMidiName) != 0: + midiport[port].send_message([NOTE_OFF, note, 0]) + #virtual.send_message([NOTE_OFF, note, 0]) + + for OSCtarget in midi2OSC: + if midi2OSC[OSCtarget]["notes"]: + pass + #OSCsend(OSCtarget, "/noteoff", note) + + + +def MidiMsg(midimsg): + + print ("MidiMsg", midimsg) + for port in range(MidInsNumber): + if midiname[port].find(BhorealMidiName) != 0: + midiport[port].send_message(midimsg) + + +def OSCsend(name, oscaddress, oscargs =''): + + + ip = midi2OSC[name]["oscip"] + port = midi2OSC[name]["oscport"] + osclient = OSCClient() + osclient.connect((ip, port)) + oscmsg = OSCMessage() + oscmsg.setAddress(oscaddress) + oscmsg.append(oscargs) + + try: + if gstt.debug > 0: + print("Midi OSCSend : sending", oscmsg, "to", name, "at", gstt.LjayServerIP, ":", PluginPort) + osclient.sendto(oscmsg, (ip, port)) + oscmsg.clearData() + #if gstt.debug >0: + # print oscaddress, oscargs, "was sent to",name + return True + + except: + if gstt.debug > 0: + print ('Midi OSCSend : Connection to IP', ip ,':', port,'refused : died ?') + #sendWSall("/status No plugin.") + #sendWSall("/status " + name + " is offline") + #sendWSall("/" + name + "/start 0") + #PluginStart(name) + return False + +def WebStatus(message): + OSCsend("lj","/status", message) + +# +# MIDI Startup and handling +# + +mqueue = Queue() +inqueue = Queue() + +# +# Events from Generic MIDI Handling +# + +def midinProcess(midiqueue): + + midiqueue_get = midiqueue.get + while True: + msg = midiqueue_get() + print ("midin ", msg) + time.sleep(0.001) + +# Event from Bhoreal or Launchpad +def MidinProcess(inqueue): + + inqueue_get = inqueue.get + while True: + time.sleep(0.001) + msg = inqueue_get() + print ("Midinproces", msg[0]) + + # Note On + if msg[0]==NOTE_ON: + NoteOn(msg[1],msg[2]) + #if bhorosc.oscdevice == 1: + WebStatus(''.join(("note ",msg[1]," to ",msg[2]))) + + # Note Off + if msg[0]==NOTE_OFF: + print ("noteoff") + NoteOff(msg[1],msg[2]) + #if bhorosc.oscdevice == 1: + WebStatus(''.join(("note ",msg[1]," to ",msg[2]))) + + # Midi CC message + if msg[0] == CONTROLLER_CHANGE: + print ("CC :", msg[1], msg[2]) + WebStatus("CC :" + str(msg[1]) + " " + str(msg[2])) + #orbits.RotX(msg[2]) + for OSCtarget in midi2OSC: + if OSCtarget["notes"]: + pass + #OSCsend(OSCtarget, "/CC", note) + + # other midi message + if msg[0] != NOTE_OFF and msg[0] != NOTE_ON: + + MidiMsg(msg[0],msg[1],msg[2]) + #if bhorosc.oscdevice == 1: + WebStatus(''.join(("msg : ",msg[0]," ",msg[1]," ",msg[2]))) + + +# Generic call back : new msg forwarded to queue +class AddQueue(object): + def __init__(self, port): + self.port = port + print ("AddQueue", port) + self._wallclock = time.time() + + def __call__(self, event, data=None): + message, deltatime = event + self._wallclock += deltatime + print("[%s] @%0.6f %r" % (self.port, self._wallclock, message)) + inqueue.put(message) + + +# +# MIDI OUT Handling +# + +def OutConfig(): + global midiout, MidInsNumber + + print("") + print("MIDIout...") + print("List and attach to available devices on host with IN port :") + + # Display list of available midi IN devices on the host, create and start an OUT instance to talk to each of these Midi IN devices + midiout = rtmidi.MidiOut() + available_ports = midiout.get_ports() + + for port, name in enumerate(available_ports): + + midiname[port]=name + midiport[port].open_port(port) + print("Will send to [%i] %s" % (port, name)) + #MidIns[port][1].open_port(port) + + # Search for a Bhoreal + if name.find(BhorealMidiName) == 0: + print("Bhoreal start animation") + gstt.BhorealHere = port + bhoreal.StartBhoreal(port) + bhoreal.UpdateCurve() + bhoreal.UpdateSet() + bhoreal.UpdateLaser() + bhoreal.UpdateSimu() + time.sleep(0.2) + + # Search for a LaunchPad + if name.find(LaunchMidiName) == 0: + print("Launchpad mini start animation") + gstt.LaunchHere = port + print(gstt.LaunchHere) + launchpad.StartLaunchPad(port) + time.sleep(0.2) + + # Search for a Guitar Wing + if name.find("Livid") == 0: + print("Livid Guitar Wing start animation") + gstt.WingHere = port + print(gstt.WingHere) + #guitarwing.StartWing(port) + time.sleep(0.2) + + print ("") + MidInsNumber = port+1 + + +# +# MIDI IN Handling +# Create processing thread and queue for each device +# +def InConfig(): + + print("") + print("MIDIin...") + print("List and attach to available devices on host with OUT port :") + if platform == 'darwin': + mido.set_backend('mido.backends.rtmidi/MACOSX_CORE') + for port, name in enumerate(mido.get_input_names()): + + #print (name) + midinputsname[port]=name + print(port,name) + + # Bhoreal found ? + if name.find(BhorealMidiName) == 0: + + # thread launch to handle all queued MIDI messages from Bhoreal device + thread = Thread(target=bhoreal.MidinProcess, args=(bhoreal.bhorqueue,)) + thread.setDaemon(True) + thread.start() + try: + bhorealin, port_name = open_midiinput(port+1) # weird rtmidi call port number is not the same in mido enumeration and here + except (EOFError, KeyboardInterrupt): + sys.exit() + + midinputs.append(bhorealin) + print("Attaching MIDI in callback handler to Bhoreal : ", name) + midinputs[port].set_callback(bhoreal.AddQueue(name)) + print("Bhor",port,port_name) + + # LaunchPad Mini Found ? + if name.find(LaunchMidiName) == 0: + + # thread launch to handle all queued MIDI messages from LauchPad device + thread = Thread(target=launchpad.LaunchMidinProcess, args=(launchpad.launchqueue,)) + thread.setDaemon(True) + thread.start() + try: + launchin, port_name = open_midiinput(port+1) # weird port number is not the same in mido enumeration and here + except (EOFError, KeyboardInterrupt): + sys.exit() + + midinputs.append(launchin) + print ("Attaching MIDI in callback handler to Launchpad : ", name) + launchin.set_callback(launchpad.LaunchAddQueue(name)) + #print "Launch",port,port_name + + # all other devices + + ''' + + + port = mido.open_ioport(name,callback=AddQueue(name)) + + This doesn't work on OS X on French system "Réseau Session" has a bug with accent. + Todo : stop using different midi framework. + + if name.find(BhorealMidiName) != 0 and name.find(LaunchMidiName) != 0: + thread = Thread(target=midinProcess, args=(midinputsqueue[port],)) + thread.setDaemon(True) + thread.start() + try: + port = mido.open_ioport(name,callback=AddQueue(name)) + #port_port, port_name = open_midiinput(port) + except (EOFError, KeyboardInterrupt): + sys.exit() + + #midinputs.append(port_port) + print "Attaching MIDI in callback handler to : ", name + #midinputs[port].set_callback(AddQueue(name)) + #MIDInport = mido.open_ioport("Laser",virtual=True,callback=MIDIn) + + ''' + + if name.find(BhorealMidiName) != 0 and name.find(LaunchMidiName) != 0: + thread = Thread(target=midinProcess, args=(midinputsqueue[port],)) + thread.setDaemon(True) + thread.start() + + + try: + port_port, port_name = open_midiinput(port) + except (EOFError, KeyboardInterrupt): + sys.exit() + + midinputs.append(port_port) + print("Attaching MIDI in callback handler to : ", name) + midinputs[port].set_callback(AddQueue(name)) + #MIDInport = mido.open_ioport("Laser",virtual=True,callback=MIDIn) + + +def End(): + global midiout + + #midiin.close_port() + midiout.close_port() + + #del virtual + if gstt.LaunchHere != -1: + del gstt.LaunchHere + if gstt.BhorealHere != -1: + del gstt.BhorealHere + + +def listdevice(number): + + return midiname[number] + +def check(): + + InConfig() + OutConfig() + #return listdevice(255) + + \ No newline at end of file diff --git a/libs/midi.pyc b/libs/midi.pyc new file mode 100644 index 0000000..b9b99b7 Binary files /dev/null and b/libs/midi.pyc differ diff --git a/libs/midi3.py b/libs/midi3.py new file mode 100644 index 0000000..d51369f --- /dev/null +++ b/libs/midi3.py @@ -0,0 +1,760 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Midi3 +v0.7.0 + +Midi Handler : + +- Hook to the MIDI host +- Enumerate connected midi devices and spawn a process/device to handle incoming events +- Provide sending functions to + - found midi devices with IN port + - OSC targets /noteon /noteoff /cc (see midi2OSC). +- Launchpad mini led matrix from/to, see launchpad.py +- Bhoreal led matrix from/to, see bhoreal.py + + +todo : + +Midi macros : plusieurs parametres evoluant les uns apres les autres ou en meme temps. +cadence + +by Sam Neurohack +from /team/laser + +for python 2 & 3 + +Laser selection +one universe / laser + +Plugin selection +bank change/scene/ + + +""" + + +import time + +import rtmidi +from rtmidi.midiutil import open_midiinput +from threading import Thread +from rtmidi.midiconstants import (CHANNEL_PRESSURE, CONTROLLER_CHANGE, NOTE_ON, NOTE_OFF, + PITCH_BEND, POLY_PRESSURE, PROGRAM_CHANGE) +import mido +from mido import MidiFile +import traceback +import weakref +import sys +from sys import platform + +print() +print('Midi startup...') + +import gstt, bhoreal, launchpad, LPD8 +from queue import Queue +#from OSC3 import OSCServer, OSCClient, OSCMessage + + +midiname = ["Name"] * 16 +midiport = [rtmidi.MidiOut() for i in range(16) ] + +OutDevice = [] +InDevice = [] + +# max 16 midi port array + +midinputsname = ["Name"] * 16 +midinputsqueue = [Queue() for i in range(16) ] +midinputs = [] + + +BhorealMidiName = "Bhoreal" +LaunchMidiName = "Launch" + +BhorealPort, Midi1Port, Midi2Port, VirtualPort, MPort = -1,-1,-1, -1, -1 +VirtualName = "LaunchPad Mini" +Mser = False + +# Myxolidian 3 notes chords list +Myxo = [(59,51,54),(49,52,56),(49,52,56),(51,54,57),(52,56,59),(52,56,59),(54,57,48),(57,49,52)] +MidInsNumber = 0 + + +clock = mido.Message(type="clock") + +start = mido.Message(type ="start") +stop = mido.Message(type ="stop") +ccontinue = mido.Message(type ="continue") +reset = mido.Message(type ="reset") +songpos = mido.Message(type ="songpos") + +mode = "maxwell" + +''' +print("clock",clock) +print("start",start) +print("continue", ccontinue) +print("reset",reset) +print("sonpos",songpos) +''' + +try: + input = raw_input +except NameError: + # Python 3 + StandardError = Exception + + +STATUS_MAP = { + 'noteon': NOTE_ON, + 'noteoff': NOTE_OFF, + 'programchange': PROGRAM_CHANGE, + 'controllerchange': CONTROLLER_CHANGE, + 'pitchbend': PITCH_BEND, + 'polypressure': POLY_PRESSURE, + 'channelpressure': CHANNEL_PRESSURE +} + +# OSC targets list +midi2OSC = { + "lj": {"oscip": "127.0.0.1", "oscport": 8002, "notes": False, "msgs": False}, + "nozoid": {"oscip": "127.0.0.1", "oscport": 8003, "notes": False, "msgs": False}, + "dump": {"oscip": "127.0.0.1", "oscport": 8040, "notes": True, "msgs": True}, + "maxwell": {"oscip": "127.0.0.1", "oscport": 8012, "notes": True, "msgs": True} + } + +notes = ["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"] +def midi2note(midinote): + + print("midinote",midinote, "note", notes[midinote%12]+str(round(midinote/12))) + return notes[midinote%12]+str(round(midinote/12)) + + +#mycontroller.midiport[LaunchHere].send_message([CONTROLLER_CHANGE, LaunchTop[number-1], color]) + +def send(msg,device): + + ''' + # if device is the midi name + if device in midiname: + deviceport = midiname.index(device) + midiport[deviceport].send_message(msg) + ''' + if device == "Launchpad": + #print LaunchHere + midiport[launchpad.Here].send_message(msg) + + if device == "Bhoreal": + midiport[bhoreal.Here].send_message(msg) + +# mididest : all, launchpad, bhoreal, specificname +def NoteOn(note,color, mididest): + global MidInsNumber + + gstt.note = note + gstt.velocity = color + + for port in range(MidInsNumber): + + # To Launchpad, if present. + if mididest == "launchpad" and midiname[port].find(LaunchMidiName) == 0: + launchpad.PadNoteOn(note%64,color) + + # To Bhoreal, if present. + elif mididest == "bhoreal" and midiname[port].find(BhorealMidiName) == 0: + gstt.BhorLeds[note%64]=color + midiport[port].send_message([NOTE_ON, note%64, color]) + #bhorosc.sendosc("/bhoreal", [note%64 , 0]) + + # To mididest + elif midiname[port].find(mididest) == 0: + midiport[port].send_message([NOTE_ON, note, color]) + + # To All + elif mididest == "all" and midiname[port].find(mididest) != 0 and midiname[port].find(BhorealMidiName) != 0 and midiname[port].find(LaunchMidiName) != 0: + midiport[port].send_message([NOTE_ON, note, color]) + + #virtual.send_message([NOTE_ON, note, color]) + + for OSCtarget in midi2OSC: + if (OSCtarget == mididest or mididest == 'all') and midi2OSC[OSCtarget]["notes"]: + OSCsend(OSCtarget, "/noteon", [note, color]) + +# mididest : all, launchpad, bhoreal, specificname +def NoteOff(note, mididest): + global MidInsNumber + + gstt.note = note + gstt.velocity = 0 + + for port in range(MidInsNumber): + + # To Launchpad, if present. + if mididest == "launchpad" and midiname[port].find(LaunchMidiName) == 0: + launchpad.PadNoteOff(note%64) + + # To Bhoreal, if present. + elif mididest == "bhoreal" and midiname[port].find(BhorealMidiName) == 0: + midiport[port].send_message([NOTE_OFF, note%64, 0]) + gstt.BhorLeds[note%64] = 0 + #bhorosc.sendosc("/bhoreal", [note%64 , 0]) + + # To mididest + elif midiname[port].find(mididest) != -1: + midiport[port].send_message([NOTE_OFF, note, 0]) + + # To All + elif mididest == "all" and midiname[port].find(mididest) == -1 and midiname[port].find(BhorealMidiName) == -1 and midiname[port].find(LaunchMidiName) == -1: + midiport[port].send_message([NOTE_OFF, note, 0]) + + #virtual.send_message([NOTE_OFF, note, 0]) + + for OSCtarget in midi2OSC: + if (OSCtarget == mididest or mididest == 'all') and midi2OSC[OSCtarget]["notes"]: + OSCsend(OSCtarget, "/noteoff", note) + + +# mididest : all or specifiname, won't be sent to launchpad or Bhoreal. +def MidiMsg(midimsg, mididest): + #print("MidiMsg", midimsg, "Dest", mididest) + + for port in range(MidInsNumber): + ##print("port",port,"midiname", midiname[port]) + + # To mididest + if midiname[port].find(mididest) != -1: + #print("sending to name", midiname[port],midimsg) + midiport[port].send_message(midimsg) + + # To All + elif mididest == "all" and midiname[port].find(mididest) == -1 and midiname[port].find(BhorealMidiName) == -1 and midiname[port].find(LaunchMidiName) == -1: + #print("all sending to port",port,"name", midiname[port]) + midiport[port].send_message(midimsg) + + for OSCtarget in midi2OSC: + if (OSCtarget == mididest or mididest == 'all') and midi2OSC[OSCtarget]["msgs"]: + OSCsend(OSCtarget, "/cc", [midimsg[1], midimsg[2]]) + + +def OSCsend(name, oscaddress, oscargs =''): + + + ip = midi2OSC[name]["oscip"] + port = midi2OSC[name]["oscport"] + osclient = OSCClient() + osclient.connect((ip, port)) + oscmsg = OSCMessage() + oscmsg.setAddress(oscaddress) + oscmsg.append(oscargs) + + try: + if gstt.debug > 0: + print("Midi OSCSend : sending", oscmsg,"to", name, "at", ip , ":", port) + + osclient.sendto(oscmsg, (ip, port)) + oscmsg.clearData() + return True + + except: + if gstt.debug > 0: + print('Midi OSCSend : Connection to IP', ip ,':', port,'refused : died ?') + #sendWSall("/status No plugin.") + #sendWSall("/status " + name + " is offline") + #sendWSall("/" + name + "/start 0") + #PluginStart(name) + return False + +def Webstatus(message): + OSCsend("lj","/status", message) + +# +# MIDI Startup and handling +# + +mqueue = Queue() +inqueue = Queue() + +# +# Events from Generic MIDI Handling +# +''' +def midinProcess(midiqueue): + + midiqueue_get = midiqueue.get + while True: + msg = midiqueue_get() + print("midin ", msg) + time.sleep(0.001) +''' +# Event from Bhoreal or Launchpad +# Or it could be the midinprocess in launchpad.py or bhoreal.py +def MidinProcess(inqueue, portname): + + inqueue_get = inqueue.get + mididest = "to Maxwell 1" + while True: + time.sleep(0.001) + msg = inqueue_get() + #print("Midinprocess", msg[0]) + + # Note On + if msg[0]==NOTE_ON: + print ("from", portname, "noteon", msg[1]) + NoteOn(msg[1],msg[2],mididest) + # Webstatus(''.join(("note ",msg[1]," to ",msg[2]))) + + # Note Off + if msg[0]==NOTE_OFF: + print("from", portname,"noteoff") + NoteOff(msg[1],msg[2], mididest) + # Webstatus(''.join(("note ",msg[1]," to ",msg[2]))) + + # Midi CC message + if msg[0] == CONTROLLER_CHANGE: + print("from", portname,"CC :", msg[1], msg[2]) + ''' + Webstatus("CC :" + str(msg[1]) + " " + str(msg[2])) + for OSCtarget in midi2OSC: + if OSCtarget["notes"]: + pass + #OSCsend(OSCtarget, "/CC", note) + ''' + # other midi message + if msg[0] != NOTE_OFF and msg[0] != NOTE_ON and msg[0] != CONTROLLER_CHANGE: + print("from", portname,"other midi message") + MidiMsg(msg[0],msg[1],msg[2],mididest) + # Webstatus(''.join(("msg : ",msg[0]," ",msg[1]," ",msg[2]))) + + +# Generic call back : new msg forwarded to queue +class AddQueue(object): + def __init__(self, portname, port): + self.portname = portname + self.port = port + #print("AddQueue", port) + self._wallclock = time.time() + + def __call__(self, event, data=None): + message, deltatime = event + self._wallclock += deltatime + #print("inqueue : [%s] @%0.6f %r" % ( self.portname, self._wallclock, message)) + midinputsqueue[self.port].put(message) + + +# +# MIDI OUT Handling +# + + +class OutObject(): + + _instances = set() + counter = 0 + + def __init__(self, name, kind, port): + + self.name = name + self.kind = kind + self.port = port + + self._instances.add(weakref.ref(self)) + OutObject.counter += 1 + + print(self.name, "kind", self.kind, "port", self.port) + + @classmethod + def getinstances(cls): + dead = set() + for ref in cls._instances: + obj = ref() + if obj is not None: + yield obj + else: + dead.add(ref) + cls._instances -= dead + + def __del__(self): + OutObject.counter -= 1 + + + +def OutConfig(): + global midiout, MidInsNumber + + # + if len(OutDevice) == 0: + print("") + print("MIDIout...") + print("List and attach to available devices on host with IN port :") + + # Display list of available midi IN devices on the host, create and start an OUT instance to talk to each of these Midi IN devices + midiout = rtmidi.MidiOut() + available_ports = midiout.get_ports() + + for port, name in enumerate(available_ports): + + midiname[port]=name + midiport[port].open_port(port) + #print("Will send to [%i] %s" % (port, name)) + #MidIns[port][1].open_port(port) + + # Search for a Bhoreal + if name.find(BhorealMidiName) == 0: + + OutDevice.append(OutObject(name, "bhoreal", port)) + print("Bhoreal start animation") + bhoreal.Here = port + bhoreal.StartBhoreal(port) + time.sleep(0.2) + + # Search for a LaunchPad + elif name.find(LaunchMidiName) == 0: + + OutDevice.append(OutObject(name, "launchpad", port)) + print("Launchpad mini start animation") + launchpad.Here = port + launchpad.StartLaunchPad(port) + time.sleep(0.2) + + # Search for a LPD8 + elif name.find('LPD8') == 0: + + OutDevice.append(OutObject(name, "LPD8", port)) + #print("LPD8 mini start animation") + LPD8.Here = port + #LPD8.StartLPD8(port) + time.sleep(0.2) + + # Search for a Guitar Wing + elif name.find("Livid") == 0: + OutDevice.append(OutObject(name, "livid", port)) + print("Livid Guitar Wing start animation") + gstt.WingHere = port + print(gstt.WingHere) + + #guitarwing.StartWing(port) + time.sleep(0.2) + else: + + OutDevice.append(OutObject(name, "generic", port)) + + #print("") + print(len(OutDevice), "Out devices") + #ListOutDevice() + MidInsNumber = len(OutDevice)+1 + +def ListOutDevice(): + + for item in OutObject.getinstances(): + + print(item.name) + +def FindOutDevice(name): + + port = -1 + for item in OutObject.getinstances(): + #print("searching", name, "in", item.name) + if name == item.name: + #print('found port',item.port) + port = item.port + return port + + +def DelOutDevice(name): + + Outnumber = Findest(name) + print('deleting OutDevice', name) + + if Outnumber != -1: + print('found OutDevice', Outnumber) + delattr(OutObject, str(name)) + print("OutDevice", Outnumber,"was removed") + else: + print("OutDevice was not found") + + + +# +# MIDI IN Handling +# Create processing thread and queue for each device +# + +class InObject(): + + _instances = set() + counter = 0 + + def __init__(self, name, kind, port, rtmidi): + + self.name = name + self.kind = kind + self.port = port + self.rtmidi = rtmidi + self.queue = Queue() + + self._instances.add(weakref.ref(self)) + InObject.counter += 1 + + #print("Adding InDevice name", self.name, "kind", self.kind, "port", self.port,"rtmidi", self.rtmidi, "Queue", self.queue) + + @classmethod + def getinstances(cls): + dead = set() + for ref in cls._instances: + obj = ref() + if obj is not None: + yield obj + else: + dead.add(ref) + cls._instances -= dead + + def __del__(self): + InObject.counter -= 1 + + +def InConfig(): + + print("") + print("MIDIin...") + print("List and attach to available devices on host with OUT port :") + + if platform == 'darwin': + mido.set_backend('mido.backends.rtmidi/MACOSX_CORE') + + genericnumber = 0 + + for port, name in enumerate(mido.get_input_names()): + + #print() + # Maxwell midi IN & OUT port names are different + + if name.find("from ") == 0: + #print ("name",name) + name = "to "+name[5:] + #print ("corrected to",name) + + outport = FindOutDevice(name) + midinputsname[port]=name + + #print("name",name, "Port",port, "Outport", outport) + + ''' + # New Bhoreal found ? + if name.find(BhorealMidiName) == 0: + + try: + bhorealin, port_name = open_midiinput(outport) # weird rtmidi call port number is not the same in mido enumeration and here + + BhoreralDevice = InObject(port_name, "bhoreal", outport, bhorealin) + print("BhorealDevice.queue",BhoreralDevice.queue ) + # thread launch to handle all queued MIDI messages from Bhoreal device + thread = Thread(target=bhoreal.MidinProcess, args=(bhoreal.bhorqueue,)) + thread.setDaemon(True) + thread.start() + print("Attaching MIDI in callback handler to Bhoreal : ", name, "port", port, "portname", port_name) + BhoreralDevice.rtmidi.set_callback(bhoreal.AddQueue(port_name)) + except Exception: + traceback.print_exc() + + ''' + # Old Bhoreal found ? + if name.find(BhorealMidiName) == 0: + + try: + bhorealin, port_name = open_midiinput(outport) # weird rtmidi call port number is not the same in mido enumeration and here + except (EOFError, KeyboardInterrupt): + sys.exit + + #midinputs.append(bhorealin) + InDevice.append(InObject(name, "bhoreal", outport, bhorealin)) + # thread launch to handle all queued MIDI messages from Bhoreal device + + thread = Thread(target=bhoreal.MidinProcess, args=(bhoreal.bhorqueue,)) + #thread = Thread(target=bhoreal.MidinProcess, args=(InDevice[port].queue,)) + thread.setDaemon(True) + thread.start() + #print("midinputs[port]", midinputs[port]) + print(name) + InDevice[port].rtmidi.set_callback(bhoreal.AddQueue(name)) + #midinputs[port].set_callback(bhoreal.AddQueue(name)) + + ''' + + # New LaunchPad Mini Found ? + if name.find(LaunchMidiName) == 0: + + + try: + launchin, port_name = open_midiinput(outport) + except (EOFError, KeyboardInterrupt): + sys.exit() + + LaunchDevice = InObject(port_name, "launchpad", outport, launchin) + thread = Thread(target=launchpad.MidinProcess, args=(launchpad.launchqueue,)) + thread.setDaemon(True) + thread.start() + print("Attaching MIDI in callback handler to Launchpad : ", name, "port", port, "portname", port_name) + LaunchDevice.rtmidi.set_callback(launchpad.LaunchAddQueue(name)) + + ''' + + # Old LaunchPad Mini Found ? + if name.find(LaunchMidiName) == 0: + + + try: + launchin, port_name = open_midiinput(outport) + except (EOFError, KeyboardInterrupt): + sys.exit() + #midinputs.append(launchin) + InDevice.append(InObject(name, "launchpad", outport, launchin)) + + thread = Thread(target=launchpad.MidinProcess, args=(launchpad.launchqueue,)) + #thread = Thread(target=launchpad.MidinProcess, args=(InDevice[port].queue,)) + thread.setDaemon(True) + thread.start() + print(name, "port", port, "portname", port_name) + InDevice[port].rtmidi.set_callback(launchpad.LaunchAddQueue(name)) + #launchin.set_callback(launchpad.LaunchAddQueue(name)) + + + # LPD8 Found ? + if name.find('LPD8') == 0: + + print() + print('LPD8 Found..') + + try: + LPD8in, port_name = open_midiinput(outport) + except (EOFError, KeyboardInterrupt): + sys.exit() + #midinputs.append(LPD8in) + InDevice.append(InObject(name, "LPD8", outport, LPD8in)) + print ("Launching LPD8 thread..") + thread = Thread(target=LPD8.MidinProcess, args=(LPD8.LPD8queue,)) + #thread = Thread(target=LPD8.MidinProcess, args=(InDevice[port].queue,)) + thread.setDaemon(True) + thread.start() + print(name, "port", port, "portname", port_name) + InDevice[port].rtmidi.set_callback(LPD8.LPD8AddQueue(name)) + + + # Everything that is not Bhoreal or Launchpad + if name.find(BhorealMidiName) != 0 and name.find(LaunchMidiName) != 0 and name.find('LPD8') != 0: + + try: + #print (name, name.find("RtMidi output")) + if name.find("RtMidi output") > -1: + print("No thread started for device", name) + else: + portin = object + port_name = "" + portin, port_name = open_midiinput(outport) + #midinputs.append(portin) + InDevice.append(InObject(name, "generic", outport, portin)) + + thread = Thread(target=MidinProcess, args=(midinputsqueue[port],port_name)) + thread.setDaemon(True) + thread.start() + + print(name, "port", port, "portname", port_name) + #midinputs[port].set_callback(AddQueue(name),midinputsqueue[port]) + #midinputs[port].set_callback(AddQueue(name)) + #genericnumber += 1 + InDevice[port].rtmidi.set_callback(AddQueue(name,port)) + + except Exception: + traceback.print_exc() + + #print("") + print(InObject.counter, "In devices") + #ListInDevice() + + +def ListInDevice(): + + for item in InObject.getinstances(): + + print(item.name) + +def FindInDevice(name): + + port = -1 + for item in InObject.getinstances(): + #print("searching", name, "in", item.name) + if name in item.name: + #print('found port',item.port) + port = item.port + return port + + +def DelInDevice(name): + + Innumber = Findest(name) + print('deleting InDevice', name) + + if Innumber != -1: + print('found InDevice', Innumber) + delattr(InObject, str(name)) + print("InDevice", Innumber,"was removed") + else: + print("InDevice was not found") + + + # all other devices + + ''' + + + port = mido.open_ioport(name,callback=AddQueue(name)) + + This doesn't work on OS X on French system "Réseau Session" has a bug with accent. + Todo : stop using different midi framework. + + if name.find(BhorealMidiName) != 0 and name.find(LaunchMidiName) != 0: + thread = Thread(target=midinProcess, args=(midinputsqueue[port],)) + thread.setDaemon(True) + thread.start() + try: + port = mido.open_ioport(name,callback=AddQueue(name)) + #port_port, port_name = open_midiinput(port) + except (EOFError, KeyboardInterrupt): + sys.exit() + + #midinputs.append(port_port) + print "Attaching MIDI in callback handler to : ", name + #midinputs[port].set_callback(AddQueue(name)) + #MIDInport = mido.open_ioport("Laser",virtual=True,callback=MIDIn) + + ''' + +def End(): + global midiout + + #midiin.close_port() + midiout.close_port() + + #del virtual + if launchpad.Here != -1: + del launchpad.Here + if bhoreal.Here != -1: + del bhoreal.Here + if LPD8.Here != -1: + del LPD8.Here + + +def listdevice(number): + + return midiname[number] + +def check(): + + OutConfig() + InConfig() + + #return listdevice(255) + + \ No newline at end of file diff --git a/plugins.py b/libs/plugins.py similarity index 77% rename from plugins.py rename to libs/plugins.py index d3841f5..5e13bbe 100644 --- a/plugins.py +++ b/libs/plugins.py @@ -45,18 +45,36 @@ def Data(name): return gstt.plugins.get(name) - +# See LJ.conf data def Start(name): # get Plugin configuration. command = Command(name) + sendWSall("/status Starting "+name+"...") + # Get LJ path + ljpath = r'%s' % os.getcwd().replace('\\','/') + + print "" + print "LJ is starting plugin :", name + + # Construct the command with absolute path. + + PluginPath = command.split(" ") + # Launch as a subprocess + PluginProcess = subprocess.Popen([PluginPath[0], ljpath + "/" + PluginPath[1]]) + + if gstt.debug >0: + print "LJ path :", ljpath + print "New process pid for ", name, ":", PluginProcess.pid + + ''' # Maybe it's not fully started data = Data(name) if command != "" and "pid" not in data : - sendWSall("/status Starting...") + sendWSall("/status Starting "+name+"...") # Get LJ path ljpath = r'%s' % os.getcwd().replace('\\','/') @@ -80,6 +98,7 @@ def Start(name): # Process can be terminated with : # PluginProcess.terminate() + ''' def OSCsend(name, oscaddress, oscargs =''): @@ -95,12 +114,12 @@ def OSCsend(name, oscaddress, oscargs =''): try: if gstt.debug > 0: - print "OSCSend : sending", oscmsg,"to plugin", name, "at", gstt.LjayServerIP, ":", PluginPort + print "Plugins manager : OSCsending", oscmsg,"to plugin", name, "at", gstt.LjayServerIP, ":", PluginPort osclientplugin.sendto(oscmsg, (gstt.LjayServerIP, PluginPort)) oscmsg.clearData() - #if gstt.debug >0: - # print oscaddress, oscargs, "was sent to",name + if gstt.debug >0: + print oscaddress, oscargs, "was sent to",name return True except: @@ -123,20 +142,23 @@ def Ping(name): def Kill(name): - data = Data(name) - print "" + #data = Data(name) + print "Killing",name + + OSCsend(name,"/quit") + + ''' if data["process"] != None: print name, "plugin is owned by LJ." print "Killing plugin", name - OSCsend(name,"/quit") #data["process"].terminate() - sendWSall("/status Killing "+ name +".") + else: print "Killing asked but plugin is not owned by LJ" sendWSall("/status Not own plugin") - + ''' # Send a command to given plugin. Will also start it if command contain /start 1 def Send(name, oscpath): @@ -151,7 +173,8 @@ def Send(name, oscpath): #sendWSall("/" + name + "/start 1") #sendWSall("/status " + name + " online") if gstt.debug > 0: - print "Send got", oscpath, "for plugin", name, "currently online." + print '' + print "Plugins manager got", oscpath, "for plugin", name, "currently online." # If start 0, try to kill plugin @@ -164,15 +187,17 @@ def Send(name, oscpath): # Send osc command elif len(oscpath) == 1: OSCsend(name, oscpath[0], oscargs='noargs') - else: + elif len(oscpath) == 2: OSCsend(name, oscpath[0], oscargs=oscpath[1]) + elif len(oscpath) == 3: + OSCsend(name, oscpath[0], oscargs=(oscpath[1], oscpath[2])) return True # Plugin not online.. else: if gstt.debug >0: - print "Send says plugin " + name + " is offline." + print "Plugin manager send says plugin " + name + " is offline." #print "Command", oscpath sendWSall("/status Plugin " + name + " offline") @@ -181,7 +206,7 @@ def Send(name, oscpath): # Try to Start it if /start 1 if oscpath[0].find("start") != -1 and oscpath[1] == "1": if gstt.debug >0: - print "Trying to start", name, "..." + print "Plugin Manager Trying to start", name, "..." Start(name) return False diff --git a/libs/plugins.pyc b/libs/plugins.pyc new file mode 100644 index 0000000..e585c87 Binary files /dev/null and b/libs/plugins.pyc differ diff --git a/libs/scrolldisp.py b/libs/scrolldisp.py new file mode 100644 index 0000000..856b640 --- /dev/null +++ b/libs/scrolldisp.py @@ -0,0 +1,803 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +''' +ScrollDisp +v0.7.0 + +An example of an unicornhat hack for Launchpad Mini or Bhoreal. + +This is a custom version of scrolldisp.py that display text on unicornhat +with use of bhorunicornhat to use with a Bhoreal or a Launchpad mini mk2 + +Default device is the launchpad. + + +Command line to display 2 chars: + +To display 'OK' : +python3 scrolldisp.py OK + +To display a rainbow : +python3 scrolldisp.py ~R + +See the end of this script for more option like scrolling or use a bhoreal in command line. + + +As a Library : + +Display(text, color=(255,255,255), delay=0.2, mididest ='launchpad') + +DisplayScroll(text, color=(255,255,255), delay=0.2, mididest = 'launchpad') + +Remember there is Cls functions +launchpad.Cls() +bhoreal.Cls() + +''' + +#from unicorn_hat_sim import unicornhat as u + +import bhorunicornhat as u +import time, math, sys + +class ScrollDisp: + columns = [] + mappings = {'!': [" ", + "#", + "#", + "#", + "#", + " ", + "#", + " "], + '\'': [" ", + "#", + "#", + " ", + " ", + " ", + " ", + " "], + '(': [" ", + " #", + "# ", + "# ", + "# ", + "# ", + " #", + " "], + ')': [" ", + "# ", + " #", + " #", + " #", + " #", + "# ", + " "], + ',': [" ", + " ", + " ", + " ", + " ", + " ", + " #", + "# "], + '-': [" ", + " ", + " ", + " ", + "###", + " ", + " ", + " "], + '.': [" ", + " ", + " ", + " ", + " ", + " ", + "#", + " "], + '0': [" ", + " ## ", + "# #", + "# #", + "# #", + "# #", + " ## ", + " "], + '1': [" ", + " # ", + "## ", + " # ", + " # ", + " # ", + "###", + " "], + '2': [" ", + " ## ", + "# #", + " #", + " # ", + " # ", + "####", + " "], + '3': [" ", + "####", + " #", + " # ", + " #", + "# #", + " ## ", + " "], + '4': [" ", + "# #", + "# #", + "####", + " #", + " #", + " #", + " "], + '5': [" ", + "####", + "# ", + "### ", + " #", + "# #", + " ## ", + " "], + '6': [" ", + " ## ", + "# ", + "### ", + "# #", + "# #", + " ## ", + " "], + '7': [" ", + "####", + " #", + " # ", + " # ", + " # ", + " # ", + " "], + '8': [" ", + " ## ", + "# #", + " ## ", + "# #", + "# #", + " ## ", + " "], + '9': [" ", + " ## ", + "# #", + " ###", + " #", + " #", + " ## ", + " "], + ':': [" ", + " ", + " ", + "#", + " ", + " ", + "#", + " "], + 'A': [" ", + " ## ", + "# #", + "# #", + "####", + "# #", + "# #", + " "], + 'B': [" ", + "### ", + "# #", + "### ", + "# #", + "# #", + "### ", + " "], + 'C': [" ", + " ## ", + "# #", + "# ", + "# ", + "# #", + " ## ", + " "], + 'D': [" ", + "### ", + "# #", + "# #", + "# #", + "# #", + "### ", + " "], + 'E': [" ", + "####", + "# ", + "### ", + "# ", + "# ", + "####", + " "], + 'F': [" ", + "####", + "# ", + "### ", + "# ", + "# ", + "# ", + " "], + 'G': [" ", + " ## ", + "# #", + "# ", + "# ##", + "# #", + " ## ", + " "], + 'H': [" ", + "# #", + "# #", + "####", + "# #", + "# #", + "# #", + " "], + 'I': [" ", + "###", + " # ", + " # ", + " # ", + " # ", + "###", + " "], + 'J': [" ", + " ###", + " #", + " #", + " #", + "# #", + " ## ", + " "], + 'K': [" ", + "# #", + "# # ", + "## ", + "## ", + "# # ", + "# #", + " "], + 'L': [" ", + "# ", + "# ", + "# ", + "# ", + "# ", + "###", + " "], + 'M': [" ", + "# #", + "####", + "# #", + "# #", + "# #", + "# #", + " "], + 'N': [" ", + "# #", + "## #", + "# ##", + "# #", + "# #", + "# #", + " "], + 'O': [" ", + " ## ", + "# #", + "# #", + "# #", + "# #", + " ## ", + " "], + 'P': [" ", + "### ", + "# #", + "### ", + "# ", + "# ", + "# ", + " "], + 'Q': [" ", + " ## ", + "# #", + "# #", + "# #", + "# # ", + " # #", + " "], + 'R': [" ", + "### ", + "# #", + "### ", + "## ", + "# # ", + "# #", + " "], + 'S': [" ", + " ###", + "# ", + " # ", + " # ", + " #", + "### ", + " "], + 'T': [" ", + "#####", + " # ", + " # ", + " # ", + " # ", + " # ", + " "], + 'U': [" ", + "# #", + "# #", + "# #", + "# #", + "# #", + " ## ", + " "], + 'V': [" ", + "# # ", + "# # ", + "# # ", + "# # ", + "# # ", + " # ", + " "], + 'W': [" ", + "# #", + "# #", + "# #", + "# # #", + "## ##", + "# #", + " "], + 'X': [" ", + "# #", + "# #", + " # ", + " # ", + "# #", + "# #", + " "], + 'Y': [" ", + "# #", + "# #", + "# #", + " # ", + " # ", + " # ", + " "], + 'Z': [" ", + "####", + " #", + " # ", + " # ", + "# ", + "####", + " "], + '[': [" ", + "##", + "# ", + "# ", + "# ", + "# ", + "##", + " "], + ']': [" ", + "##", + " #", + " #", + " #", + " #", + "##", + " "], + '_': [" ", + " ", + " ", + " ", + " ", + " ", + " ", + "####"], + 'a': [" ", + " ", + " ## ", + " #", + " ###", + "# #", + " ###", + " "], + 'b': [" ", + "# ", + "# ", + "### ", + "# #", + "# #", + "### ", + " "], + 'c': [" ", + " ", + " ## ", + "# #", + "# ", + "# #", + " ## ", + " "], + 'd': [" ", + " #", + " #", + " ###", + "# #", + "# #", + " ###", + " "], + 'e': [" ", + " ", + " ## ", + "# #", + "####", + "# ", + " ## ", + " "], + 'f': [" ", + " ## ", + "# #", + "# ", + "## ", + "# ", + "# ", + " "], + 'g': [" ", + " ", + " ## ", + "# #", + " ###", + " #", + "### ", + " "], + 'h': [" ", + "# ", + "# ", + "# # ", + "## #", + "# #", + "# #", + " "], + 'i': [" ", + " # ", + " ", + "## ", + " # ", + " # ", + "###", + " "], + 'j': [" ", + " #", + " ", + " #", + " #", + " #", + "# #", + " # "], + 'k': [" ", + "# ", + "# #", + "# # ", + "## ", + "# # ", + "# #", + " "], + 'l': [" ", + "## ", + " # ", + " # ", + " # ", + " # ", + "###", + " "], + 'm': [" ", + " ", + "## # ", + "# # #", + "# #", + "# #", + "# #", + " "], + 'n': [" ", + " ", + "### ", + "# #", + "# #", + "# #", + "# #", + " "], + 'o': [" ", + " ", + " ## ", + "# #", + "# #", + "# #", + " ## ", + " "], + 'p': [" ", + " ", + "### ", + "# #", + "# #", + "### ", + "# ", + "# "], + 'q': [" ", + " ", + " ###", + "# #", + "# #", + " ###", + " #", + " #"], + 'r': [" ", + " ", + "# ##", + "## ", + "# ", + "# ", + "# ", + " "], + 's': [" ", + " ", + " ###", + "# ", + " ## ", + " #", + "### ", + " "], + 't': [" ", + " # ", + "####", + " # ", + " # ", + " # ", + " ##", + " "], + 'u': [" ", + " ", + "# #", + "# #", + "# #", + "# #", + " ## ", + " "], + 'v': [" ", + " ", + "# #", + "# #", + " # # ", + " # # ", + " # ", + " "], + 'w': [" ", + " ", + "# #", + "# #", + "# # #", + "# # #", + " # # ", + " "], + 'x': [" ", + " ", + "# #", + "# #", + " ## ", + "# #", + "# #", + " "], + 'y': [" ", + " ", + "# #", + "# #", + " ###", + " #", + "# #", + " ## "], + 'z': [" ", + " ", + "####", + " # ", + " # ", + "# ", + "####", + " "] + } + sharpnotes = { + 'A': [" #", + " ## ", + "# #", + "# #", + "####", + "# #", + "# #", + " "], + 'C': [" #", + " ## ", + "# #", + "# ", + "# ", + "# #", + " ## ", + " "], + 'D': [" #", + "### ", + "# #", + "# #", + "# #", + "# #", + "### ", + " "], + 'F': [" #", + "### ", + "# ", + "### ", + "# ", + "# ", + "# ", + " "], + 'G': [" #", + " ## ", + "# #", + "# ", + "# ##", + "# #", + " ## ", + " "] + } + def append_mapping(self, char, color): + #self.append_space() + bitmap = self.mappings[char] + n = len(bitmap[0]) + for x in range(n): + self.columns.append([(color if bitmap[i][x] == '#' else (0,0,0)) for i in range(8)]) + + def append_rainbow(self): + for x in range(8): + r = int((math.cos(x * math.pi / 4) + 1) * 127) + g = int((math.cos((x - 8.0 / 3.0) * math.pi / 4) + 1) * 127) + b = int((math.cos((x + 8.0 / 3.0) * math.pi / 4) + 1) * 127) + self.columns.append([(r,g,b) for i in range(8)]) + + def append_space(self, n=1): + for x in range(n): + self.columns.append([(0,0,0) for i in range(8)]) + + def append_buffer(self): + self.append_space(9) + + def append_letter(self, char, color=None): + if char == ' ': + self.append_space(2) + elif char == 0: + self.append_rainbow() + elif char in self.mappings.keys(): + self.append_mapping(char, color) + else: + self.columns.append([(255,255,255),(255,255,255),(255,255,255),(255,255,255),(255,255,255),(255,255,255),(255,255,255),(255,255,255)]) + print("unknown char {0} ({1})".format(char, ord(char))) + + + def append_sharpnote(self, text, color=(255,255,255)): + + # Note + # Should be a better test for A-G letter. + if text[0] in self.mappings.keys(): + bitmap = self.sharpnotes[text[0]] + + n = len(bitmap[0]) + for x in range(n): + self.columns.append([(color if bitmap[i][x] == '#' else (0,0,0)) for i in range(8)]) + + + # Octave + if text[2] in self.mappings.keys(): + self.append_letter(text[2], color) + + + def append_string(self, text, color=(255,255,255)): + i = 0 + while i < len(text): + if text[i] == '~': + i += 1 + if text[i] == 'R': #rainbow + self.append_letter(0) + else: + self.append_letter(text[i], color) + i += 1 + + def set_text(self, text, color=(255,255,255)): + self.columns = [] + #self.append_buffer() + + if len(text) == 3 and text[1] == "#": + self.append_sharpnote(text) + else: + self.append_string(text) + + self.append_buffer() + + def __init__(self, text=""): + self.set_text(text) + + def start(self, delay=0.1): + + u.set_pixels(self.columns[0:8]) + u.show() + time.sleep(delay) + + def startScroll(self, delay=0.1): + + for x in range(len(self.columns) - 8): + u.set_pixels(self.columns[x:x+8]) + u.show() + time.sleep(delay) + + +def Display(text, color=(255,255,255), delay=0.2, mididest ='launchpad'): + disp = ScrollDisp() + #print(text) + + if mididest == 'bhoreal': + u.dest(mididest,180) + else: + u.dest(mididest,270) + + disp.set_text(text, color) + disp.start(delay) + +def DisplayScroll(text, color=(255,255,255), delay=0.2, mididest = 'launchpad'): + disp = ScrollDisp() + if mididest == 'bhoreal': + u.dest(mididest,180) + else: + u.dest(mididest,270) + disp.set_text(text, color) + disp.startScroll(delay) + + +if __name__ == '__main__': + + from libs import midi3 + + # Implemented for script compatibility but actually do nothing on supported devices + u.brightness(0.5) + + # 2 chars with no scrolling + Display(' '.join(sys.argv[1:])) + + + # text with scrolling + # DisplayScroll(' '.join(sys.argv[1:])) + + + + # To use with a Bhoreal just add mididest = 'bhoreal' in Display() + # or DisplayScroll() + + # Display(' '.join(sys.argv[1:]), mididest = 'bhoreal') diff --git a/settings.py b/libs/settings.py similarity index 100% rename from settings.py rename to libs/settings.py diff --git a/libs/settings.pyc b/libs/settings.pyc new file mode 100644 index 0000000..0f777a6 Binary files /dev/null and b/libs/settings.pyc differ diff --git a/tracer.py b/libs/tracer.py similarity index 100% rename from tracer.py rename to libs/tracer.py diff --git a/libs/tracer.pyc b/libs/tracer.pyc new file mode 100644 index 0000000..37242aa Binary files /dev/null and b/libs/tracer.pyc differ diff --git a/main.py b/main.py index 13875a1..3dd6a92 100755 --- a/main.py +++ b/main.py @@ -4,12 +4,15 @@ ''' LJ Laser Server v0.8.1 +Inspiration for new WebUI icon menu : +https://codepen.io/AlbertFeynman/pen/mjXeMV + Laser server + webUI servers (ws + OSC) - get point list to draw : /pl/lasernumber - for report /lstt/lasernumber /lack/lasernumber /cap/lasernumber - A nice ws debug tool : websocat -- a "plugin" is a code that send point to LJ. Plugins if they have an open OSC port can be checked and restart if in the same computer. +- a "plugin" is a generator that send points to LJ. Plugins if they have an open OSC port can be checked and restart if in the same computer. Todo : @@ -21,43 +24,42 @@ All used ports: 8002 OSC incoming 9001 WS communication with WebGUI -Plugins Ports (see LJ.conf) +Plugins OSC Ports (see LJ.conf) ''' -import time -import gstt -import redis - print "" print "" print "LJ Laser Server" -print "v0.8.1" +print "v0.8.2" print "" -import settings +import redis + +from libs import gstt, settings settings.Read() -import cli +# Arguments may alter .conf file so import settings first then cli +from libs import cli + settings.Write() + from multiprocessing import Process, Queue, TimeoutError import random, ast -import tracer -import homographyp -import commands -import font1 +from libs import plugins, tracer, homographyp, commands, font1 import subprocess import sys import os +#import midi from OSC import OSCServer, OSCClient, OSCMessage from websocket_server import WebsocketServer #import socket import types, thread, time -import plugins + r = redis.StrictRedis(host=gstt.LjayServerIP , port=6379, db=0) args =[0,0] @@ -88,7 +90,7 @@ def dac_process(number, pl): # webUI server # -print "Laser client number :",gstt.LasClientNumber +print "Laser client number :",gstt.SceneNumber serverIP = gstt.LjayServerIP print "Redis IP :", serverIP @@ -145,115 +147,17 @@ def handle_timeout(self): oscserver.handle_timeout = types.MethodType(handle_timeout, oscserver) -''' -osclientext = OSCClient() -oscmsg = OSCMessage() -osclientext.connect((oscserverIPout, oscserverPORTout)) - -# send UI string as OSC message to oscserver 8001 -# sendoscserver(oscaddress, [arg1, arg2,...]) - -def sendoscserver(oscaddress,oscargs=''): - - oscmsg = OSCMessage() - oscmsg.setAddress(oscaddress) - oscmsg.append(oscargs) - - #print ("sending to oscserver : ",oscmsg) - try: - osclientext.sendto(oscmsg, (oscserverIPout, oscserverPORTout)) - oscmsg.clearData() - except: - print ('Connection to oscserver IP', oscserverIPout, 'port', oscserverPORTout,'refused : died ?') - sendWSall("/on 0") - sendWSall("/status NoLJay") - - #time.sleep(0.001) - - -# send UI string as OSC message to Nozosc 8003 -# sendnozosc(oscaddress, [arg1, arg2,...]) - -osclientnozoid = OSCClient() -osclientnozoid.connect((NozoscIPout, NozoscPORTout)) - -def sendnozosc(oscaddress,oscargs=''): - - oscmsg = OSCMessage() - oscmsg.setAddress(oscaddress) - oscmsg.append(oscargs) - - print "Sending OSC to Nozosc server :", oscaddress,'with args', oscargs - try: - osclientnozoid.sendto(oscmsg, (NozoscIPout, NozoscPORTout)) - oscmsg.clearData() - except: - print 'Connection to nozosc IP', NozoscIPout,'port', NozoscPORTout,' refused : died ?' - sendWSall("/on 0") - sendWSall("/status No Nozosc ") - - #time.sleep(0.001) - -# send UI string as OSC message to Planet 8005 -# sendplanet(oscaddress, [arg1, arg2,...]) - -osclientplanet = OSCClient() -osclientplanet.connect((planetIPout, planetPORTout)) - -def sendplanet(oscaddress,oscargs=''): - - oscmsg = OSCMessage() - oscmsg.setAddress(oscaddress) - oscmsg.append(oscargs) - - print "Sending OSC to Planet server :", oscaddress,'with args :', oscargs - try: - osclientplanet.sendto(oscmsg, (planetIPout, planetPORTout)) - oscmsg.clearData() - except: - print 'OSC send to planet IP', planetIPout, 'port', planetPORTout, "refused : died ?" - sendWSall("/planet/start 0") - sendWSall("/status No Planet") - - #time.sleep(0.001) - -# send UI string as OSC message to Bank 0 8010 -# sendbank0(oscaddress, [arg1, arg2,...]) - -osclientbank0 = OSCClient() -osclientbank0.connect((planetIPout, planetPORTout)) - -def sendbank0(oscaddress,oscargs=''): - - oscmsg = OSCMessage() - oscmsg.setAddress(oscaddress) - oscmsg.append(oscargs) - - print "Sending OSC to Bank0 server :", oscaddress,'with args :', oscargs - try: - osclientbank0.sendto(oscmsg, (bank0IPout, bank0PORTout)) - oscmsg.clearData() - except: - print 'OSC send to bank0 IP', bank0IPout, 'port', bank0PORTout, "refused : died ?" - sendWSall("/bank0/start 0") - sendWSall("/status No Bank0") - - #time.sleep(0.001) - -''' # OSC default path handler : send incoming OSC message to UI via websocket 9001 def handler(path, tags, args, source): oscpath = path.split("/") - print "" - print "OSC handler in main said : path", path," oscpath ", oscpath," args", args - #print "debug", gstt.debug - #if gstt.debug >0: - # print "default handler" - # print "OSC said path", path," oscpath ", oscpath," args", args - - sendWSall(path + " " + str(args[0])) + if gstt.debug > 0: + print "" + print "OSC handler in main said : path", path," oscpath ", oscpath," args", args + + if oscpath[1] != "pong": + sendWSall(path + " " + str(args[0])) commands.handler(oscpath,args) @@ -268,26 +172,19 @@ def osc_frame(): def PingAll(): + print ("Pinging all plugins...") for plugin in gstt.plugins.keys(): - # Plugin Online - if plugins.Ping(plugin): - - sendWSall("/"+ plugin + "/start 1") - if gstt.debug >0: - print "plugin", plugin, "answered." + print("pinging", plugin) + #sendWSall("/"+ plugin + "/start 0") + plugins.Ping(plugin) - # Plugin Offline - else: - sendWSall("/"+ plugin + "/start 0") - if gstt.debug >0: - print "plugin", plugin, "didn't answered." # OSC server Thread : handler, dacs reports and simulator points sender to UI. def osc_thread(): - while True: + #while True: try: while True: @@ -326,9 +223,9 @@ def osc_thread(): # last number of points sent to etherdream buffer sendWSall("/points/" + str(laserid) + " " + str(r.get('/cap/'+str(laserid)))) - #print "Sending simu frame from",'/pl/'+str(gstt.LasClientNumber)+'/'+str(gstt.Laser) - #print r.get('/pl/'+str(gstt.LasClientNumber)+'/'+str(gstt.Laser)) - sendWSall("/simul" +" "+ r.get('/pl/'+str(gstt.LasClientNumber)+'/'+str(gstt.Laser))) + #print "Sending simu frame from",'/pl/'+str(gstt.SceneNumber)+'/'+str(gstt.Laser) + #print r.get('/pl/'+str(gstt.SceneNumber)+'/'+str(gstt.Laser)) + sendWSall("/simul" +" "+ r.get('/pl/'+str(gstt.SceneNumber)+'/'+str(gstt.Laser))) except Exception as e: @@ -350,8 +247,10 @@ def new_client(client, wserver): sendWSall("/status Hello %d" % client['id']) for laserid in range(0,gstt.LaserNumber): + sendWSall("/ip/" + str(laserid) + " " + str(gstt.lasersIPS[laserid])) sendWSall("/kpps/" + str(laserid)+ " " + str(gstt.kpps[laserid])) + sendWSall("/laser"+str(laserid)+"/start 1") if gstt.swapX[laserid] == 1: sendWSall("/swap/X/" + str(laserid)+ " 1") @@ -383,6 +282,7 @@ def message_received(client, wserver, message): oscpath = message.split(" ") if gstt.debug > 0: print "WS Client", client['id'], "said :", message, "splitted in an oscpath :", oscpath + PingAll() message4plugin = False @@ -394,42 +294,10 @@ def message_received(client, wserver, message): message4plugin = True if plugins.Send(plugin,oscpath): - print "message sent correctly to", plugin - - - #plugins.sendWSall("/status Running...") + print "message sent correctly to", plugin + else: + print"plugin was offline" - ''' - if plugins.Send("planet",oscpath): - pass - - elif plugins.Send("nozoid",oscpath): - pass - - elif plugins.Send("ai",oscpath): - pass - - elif plugins.Send("lissa",oscpath): - pass - - elif plugins.Send("bank0",oscpath): - pass - - elif plugins.Send("simu",oscpath): - pass - - elif len(oscpath) > 1: - args[0] = str(oscpath[1]) - commands.handler(oscpath[0].split("/"),args) - #print oscpath[0].split("/"),oscpath[1] - - - if oscpath[0] == "/on": - if oscpath[1] == "1": - sendoscserver("/on") - else: - sendoscserver("/off") - ''' # WS received message is an LJ command @@ -448,46 +316,6 @@ def message_received(client, wserver, message): print "" - - ''' - # I message included "nozoid" forward the message as OSC to nozoid IP port 8003 - elif oscpath[0].find("nozoid") != -1: - - if plugins.Ping("nozoid"): - - sendWSall("/nozoid/start 1") - - if oscpath[0].find("start") != -1: - print "Nozoid 0",oscpath[0],"1", oscpath[1] - - if len(oscpath) == 1: - sendnozosc(oscpath[0], oscargs='noargs') - else: - sendnozosc(oscpath[0], oscargs=oscpath[1]) - else: - sendWSall("/status Nozoid offline") - sendWSall("/planet/start 0") - - # If message included "ai" do something - elif oscpath[0].find("ai") != -1: - print "ai order ", oscpath - - # If message included "lissa" do something - elif oscpath[0].find("lissa") != -1: - print "lissa order ", oscpath - - # If message included "bank0" do something - elif oscpath[0].find("bank0") != -1: - - if plugins.Ping("bank0"): - if len(oscpath) == 1: - sendbank0(oscpath[0], oscargs='noargs') - else: - sendbank0(oscpath[0], oscargs=oscpath[1]) - else: - sendWSall("/status Bank0 offline") - ''' - # if needed a loop back : WS Client -> server -> WS Client #sendWSall("ws"+message) @@ -501,13 +329,18 @@ def sendWSall(message): #if gstt.debug >0: #print("WS sending %s" % (message)) wserver.send_message_to_all(message) - - + +''' +print "" +print "Midi Configuration" +midi.InConfig() +midi.OutConfig() +''' # Creating a startup point list for each client : 0,1,2,... print "" -for clientid in range(0,gstt.MaxLasClient): +for clientid in range(0,gstt.MaxScenes+1): print "Creating startup point lists for client",clientid,"..." digit_points = font1.DigitsDots(clientid,65280) @@ -519,8 +352,8 @@ for clientid in range(0,gstt.MaxLasClient): r.set('/order/'+str(laserid), 0) -if r.set("/clientkey","/pl/"+str(gstt.LasClientNumber)+"/")==True: - print "sent clientkey : /pl/"+str(gstt.LasClientNumber)+"/" +if r.set("/clientkey","/pl/"+str(gstt.SceneNumber)+"/")==True: + print "sent clientkey : /pl/"+str(gstt.SceneNumber)+"/" print "" print "Etherdream connection check is NOT DISPLAYED" @@ -579,9 +412,8 @@ try: print "Resetting all Homographies.." for laserid in range(0,gstt.LaserNumber): homographyp.newEDH(laserid) - print "" - print "ws server running forver..." + print "WS server running forever..." wserver.run_forever() diff --git a/plugins/VJing/bank0.py b/plugins/VJing/bank0.py index c76709a..76ec986 100644 --- a/plugins/VJing/bank0.py +++ b/plugins/VJing/bank0.py @@ -1,17 +1,15 @@ -#!/usr/bin/python2.7 +#!/usr/bin/python3 # -*- coding: utf-8 -*- # -*- mode: Python -*- ''' -VJing Bank 0 +LJ v0.8.1 -was Franken for compo laser at coockie 2018 demoparty +IdiotIA for THSF 10 -0 : Starfields -1 : generic pose animations -2 : Faces -3 : Dancers -4 : IdiotIA +Include IdiotIA and Starfields + + /pose/ljclient LICENCE : CC Sam Neurohack, Loloster, @@ -20,41 +18,55 @@ Sam Neurohack, Loloster, import math -#import gstt -#from globalVars import * + import numpy as np import pdb from datetime import datetime from random import randrange import redis -import lj3 -import sys,time -import os +import sys + +import time,traceback +import os +ljpath = r'%s' % os.getcwd().replace('\\','/') +# import from shell +#sys.path.append('../../libs') +#import from LJ +sys.path.append(ljpath +'/libs/') +print (ljpath+'/libs') +import lj23 +from OSC3 import OSCServer, OSCClient, OSCMessage -from osc4py3.as_eventloop import * -from osc4py3 import oscbuildparse -#from osc4py3 import oscmethod as osm -from osc4py3.oscmethod import * import argparse -#f_sine = 0 +# 0.25 : each frame will be repeated 4 times. +animspeed = 0.25 - -screen_size = [400,400] +screen_size = [700,700] xy_center = [screen_size[0]/2,screen_size[1]/2] -message = "Hello" -OSCinPort = 8010 - -redisIP = '127.0.0.1' +message = "LO" +OSCinPort = 8011 +oscrun = True ljclient = 0 +idiotiaDisplay = [True,True,False,False] +#idiotiaDisplay = [False,False,False,False] +liveDisplay = [False,False,False,False] + +fieldsDisplay = [False,False,True,True] +#fieldsDisplay = [True,True,True,True] +currentIdiotia = 0 + print ("") print ("Arguments parsing if needed...") -argsparser = argparse.ArgumentParser(description="VJ Bank 0 for LJ") +argsparser = argparse.ArgumentParser(description="Pose bank for LJ") argsparser.add_argument("-r","--redisIP",help="IP of the Redis server used by LJ (127.0.0.1 by default) ",type=str) +argsparser.add_argument("-m","--myIP",help="Local IP (127.0.0.1 by default) ",type=str) argsparser.add_argument("-c","--client",help="LJ client number (0 by default)",type=int) argsparser.add_argument("-v","--verbose",help="Verbosity level (0 by default)",type=int) +argsparser.add_argument("-a","--anim",help="IdiotIA anim (0 by default)",type=int) +argsparser.add_argument("-L","--Lasers",help="Number of lasers connected (4 by default).",type=int) args = argsparser.parse_args() @@ -69,14 +81,32 @@ if args.client: else: ljclient = 0 +if args.anim: + currentIdiotia = args.anim +else: + currentIdiotia = 0 + # Redis Computer IP if args.redisIP != None: redisIP = args.redisIP else: redisIP = '127.0.0.1' +# myIP +if args.myIP != None: + myIP = args.myIP +else: + myIP = '127.0.0.1' + +# Lasers = number of laser connected +if args.Lasers != None: + LaserNumber = args.Lasers +else: + LaserNumber = 4 + + +lj23.Config(redisIP,ljclient,"pose") -lj3.Config(redisIP,ljclient) def hex2rgb(hexcode): @@ -87,11 +117,177 @@ def rgb2hex(rgb): return int('0x%02x%02x%02x' % tuple(rgb),0) +# IdiotIA +import json +#CurrentPose = 1 -# Curve 0 many starfields +# Get frame number for pose path describe in PoseDir +def lengthPOSE(pose_dir): + + #if debug > 0: + # print("Check directory",'poses/' + pose_dir) + if os.path.exists('poses/' + pose_dir): + numfiles = sum(1 for f in os.listdir('poses/' + pose_dir) if os.path.isfile(os.path.join('poses/' + pose_dir + '/', f)) and f[0] != '.') + return numfiles + else: + if debug > 0: + print("but it doesn't even exist!") + return 0 + + +def prepareIdiotIA(currentAnim): + + WebStatus("Checking anims...") + print() + print("Reading available IdiotIA anims...") + # anim format (name, xpos, ypos, resize, currentframe, totalframe, count, speed) + # 0 1 2 3 4 5 6 7 + # total frames is fetched from directory by lengthPOSE() + + anims[0] = ['boredhh' , xy_center[0] - 100, xy_center[1] + 30, 550, 0, 0, 0, animspeed] + anims[1] = ['belka4' , xy_center[0] - 70, xy_center[1] + 380, 680, 0, 0, 0, animspeed] + anims[2] = ['belka3' , xy_center[0] - 100, xy_center[1] + 360, 700, 0, 0, 0, animspeed] + anims[3] = ['hhhead' , xy_center[0] - 100, xy_center[1] + 300, 600, 0, 0, 0, animspeed] + anims[4] = ['hhhead2', xy_center[0] - 100, xy_center[1] + 300, 600, 0, 0, 0, animspeed] + anims[5] = ['hhhead4', xy_center[0] - 100, xy_center[1] + 280, 600, 0, 0, 0, animspeed] + anims[6] = ['hhred' , xy_center[0] - 250, xy_center[1] + 220, 550, 0, 0, 0, animspeed] + anims[7] = ['hhred2' , xy_center[0] - 200, xy_center[1] + 200, 550, 0, 0, 0, animspeed] + anims[8] = ['lady1' , xy_center[0] - 100, xy_center[1] + 300, 600, 0, 0, 0, animspeed] + anims[9] = ['lady1' , xy_center[0] - 100, xy_center[1] + 280, 600, 0, 0, 0, animspeed] + anims[10] = ['lady2' , xy_center[0] - 100, xy_center[1] + 280, 600, 0, 0, 0, animspeed] + anims[11] = ['lady3' , xy_center[0] - 100, xy_center[1] + 300, 600, 0, 0, 0, animspeed] + anims[12] = ['lady4' , xy_center[0] - 100, xy_center[1] + 300, 600, 0, 0, 0, animspeed] + anims[13] = ['mila6' , xy_center[0] - 100, xy_center[1] + 280, 600, 0, 0, 0, animspeed] + anims[14] = ['mila5' , xy_center[0] - 100, xy_center[1] + 280, 600, 0, 0, 0, animspeed] + anims[15] = ['idiotia1', xy_center[0] - 100, xy_center[1] + 300, 600, 0, 0, 0, animspeed] + anims[16] = ['idiotia1', xy_center[0] - 100, xy_center[1] + 300, 600, 0, 0, 0, animspeed] + anims[17] = ['belka4', xy_center[0] - 100, xy_center[1] + 280, 600, 0, 0, 0, animspeed] + anims[18] = ['belka3', xy_center[0] - 100, xy_center[1] + 280, 600, 0, 0, 0, animspeed] + + #for laseranims in anims: + + for anim in anims: + #print(anim) + anim[5] = lengthPOSE(anim[0]) + WebStatus("Checking "+ anim[0] +"...") + if debug > 0: + print('poses/' + anim[0], "length :", anim[5], "frames") + + print("Current IdiotIA anim is",anims[currentIdiotia][0],"("+str(currentIdiotia)+")") + + +# get absolute face position points +def getFACE(pose_json,pose_points, people): + + dots = [] + for dot in pose_points: + + if len(pose_json['people'][people]['face_keypoints_2d']) != 0: + #print "people 0" + if pose_json['people'][people]['face_keypoints_2d'][dot * 3] != -1 and pose_json['people'][people]['face_keypoints_2d'][(dot * 3)+1] != -1: + dots.append((pose_json['people'][people]['face_keypoints_2d'][dot * 3], pose_json['people'][people]['face_keypoints_2d'][(dot * 3)+1])) + + return dots + + +# Face keypoints +def face(pose_json, people): + pose_points = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] + return getFACE(pose_json,pose_points, people) + +def browL(pose_json, people): + pose_points = [26,25,24,23,22] + return getFACE(pose_json,pose_points, people) + +def browR(pose_json, people): + pose_points = [21,20,19,18,17] + return getFACE(pose_json,pose_points, people) + +def eyeR(pose_json, people): + pose_points = [36,37,38,39,40,41,36] + return getFACE(pose_json,pose_points, people) + +def eyeL(pose_json, people): + pose_points = [42,43,44,45,46,47,42] + return getFACE(pose_json,pose_points, people) + +def pupR(pose_json, people): + pose_points = [68,68] + print(getFACE(pose_json,pose_points, people)) + return getFACE(pose_json,pose_points, people) + +def pupL(pose_json, people): + pose_points = [69,69] + return getFACE(pose_json,pose_points, people) + + +def nose(pose_json, people): + pose_points = [27,28,29,30] + return getFACE(pose_json,pose_points, people) + +def mouth(pose_json, people): + pose_points = [48,59,58,57,56,55,54,53,52,51,50,49,48,60,67,66,65,64,63,62,61,60] + return getFACE(pose_json,pose_points, people) + + + +# display the currentIdiotia animation on all lasers according to display flag +def IdiotIA(): + + # All laser loop + for laser in range(LaserNumber): + # for anim in anims[laseranims]: + + # if display flag is True, send the face points. + if idiotiaDisplay[laser]: + + anim = anims[currentIdiotia] + #print(anim) + + PL = laser + #print PL, anim + + dots = [] + + # increase current frame [4] of speed [7] frames + #print(anim[4],anim[7],anim[4]+anim[7]) + + anim[4] = anim[4]+anim[7] + + # compare to total frame [5] + if anim[4] >= anim[5]: + anim[4] = 0 + + posename = 'poses/' + anim[0] + '/' + anim[0] +'-'+str("%05d"%int(anim[4]))+'.json' + posefile = open(posename , 'r') + posedatas = posefile.read() + pose_json = json.loads(posedatas) + + if debug>0: + WebStatus("Frame : "+str("%05d"%int(anim[4]))) + + # Draw Face + + for people in range(len(pose_json['people'])): + + #lj23.rPolyLineOneColor(face(pose_json, people), c = white, PL = laser closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + lj23.rPolyLineOneColor(browL(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + lj23.rPolyLineOneColor(browR(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + lj23.rPolyLineOneColor(eyeR(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + #lj23.rPolyLineOneColor(pupR(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + lj23.rPolyLineOneColor(eyeL(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + #lj23.rPolyLineOneColor(pupL(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + lj23.rPolyLineOneColor(nose(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + lj23.rPolyLineOneColor(mouth(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + + lj23.DrawPL(PL) + + +# Init Starfields def prepareSTARFIELD(): global star, stars0, stars1, stars2, starfieldcount, starspeed, displayedstars, displayedstars, num_stars, max_depth + WebStatus("Init starfields...") stars0=[] stars1=[] stars2=[] @@ -112,44 +308,23 @@ def prepareSTARFIELD(): star = [randrange(-25,25), randrange(-25,25), randrange(1, max_depth)] stars2.append(star) + +# Todo : Currently compute all starfields even if field display flag is False. + def Starfield(hori=0,verti=0): global star, stars0, stars1, stars2, starfieldcount, starspeed, displayedstars, displayedstars, num_stars, max_depth starfieldcount += 1 #print starfieldcount starpoints = [] - - # Move starfield according to joypads. Not used in the demo - ''' - # Tflight joystick : - # y axis 1 top -1 bottom 1 - # x axis 0 left -1 right 1 - # Main fire button 5 - # hat (x,y) x -1 left x 1 right y -1 bottom y 1 top - # speed axis 3 backward 1 forward -1 - - if Nbpads > 0: - # Move center on X axis according to pad - if pad1.get_axis(0)<-0.1 or pad1.get_axis(0)>0.1: - hori = pad1.get_axis(0) - #print hori - # Move center on Y axis according to pad - if pad1.get_axis(1)<-0.1 or pad1.get_axis(1)>0.1: - verti= pad1.get_axis(1) - #print verti - ''' - #print displayedstars, 'stars displayed' # Increase number of if displayedstars < num_stars and starfieldcount % 15 == 0: displayedstars += 1 - if displayedstars == num_stars and starfieldcount % 10 == 0: - starspeed += 0.005 - - #if Nbpads > 0: - # starspeed = (1-pad1.get_axis(3)) + #if displayedstars == num_stars and starfieldcount % 10 == 0: + # starspeed += 0.005 #print starspeed @@ -189,12 +364,12 @@ def Starfield(hori=0,verti=0): if np.sign(stars0[starnumber][0]) == np.sign(hori): x0 = int(stars0[starnumber][0] * k0 + xy_center[0] + (hori*600)) else: - x0 = int(stars0[starnumber][0] * k0 + xy_center[0] + (hori*300)) + x0 = int(stars0[starnumber][0] * k0 + xy_center[0] + (hori*500)) if np.sign(stars0[starnumber][1]) == np.sign(verti): y0 = int(stars0[starnumber][1] * k0 + xy_center[1] + (verti*600)) else: - y0 = int(stars0[starnumber][1] * k0 + xy_center[1] + (verti*300)) + y0 = int(stars0[starnumber][1] * k0 + xy_center[1] + (verti*500)) if np.sign(stars1[starnumber][0]) == np.sign(hori): @@ -219,22 +394,17 @@ def Starfield(hori=0,verti=0): y2 = int(stars2[starnumber][1] * k2 + xy_center[1] + (verti*300)) - # Add star to pointlist PL 0 - if 0 <= x0 < screen_size[0] - 2 and 0 <= y0 < screen_size[1] - 2: - #f.LineTo((x,y), 0x80000000) - lj3.PolyLineOneColor([(x0,y0),((x0+1),(y0+1))], c = rgb2hex([255,255,255]), PL = 0, closed = False) - #fwork.PolyLineOneColor([(x0,y0),((x0+1),(y0+1))], c=rgb2hex([255,255,255]), PL = 0, closed = False) + # Add star to pointlist PL 0 if field display flag is true + if fieldsDisplay[0] and 0 <= x0 < screen_size[0] - 2 and 0 <= y0 < screen_size[1] - 2: + lj23.PolyLineOneColor([(x0,y0),((x0+1),(y0+1))], c = white, PL = 0, closed = False) - # Add star to pointlist PL 1 - if 0 <= x1 < screen_size[0] - 2 and 0 <= y1 < screen_size[1] - 2: - lj3.PolyLineOneColor([(x1,y1),((x1+1),(y1+1))], c = rgb2hex([255,255,255]), PL = 0, closed = False) - - #fwork.PolyLineOneColor([(x1,y1),((x1+1),(y1+1))], c=rgb2hex([255,255,255]), PL = 1, closed = False) + # Add star to pointlist PL 1 if field display flag is true + if fieldsDisplay[1] and 0 <= x1 < screen_size[0] - 2 and 0 <= y1 < screen_size[1] - 2: + lj23.PolyLineOneColor([(x1,y1),((x1+1),(y1+1))], c = white, PL = 1, closed = False) - # Add star to pointlist PL 2 - #if 0 <= x2 < screen_size[0] - 2 and 0 <= y2 < screen_size[1] - 2: - # fwork.PolyLineOneColor([(x2,y2),((x2+1),(y2+1))], c=colorify.rgb2hex([255,255,255]), PL = 2, closed = False) - # #f.PolyLineOneColor([(x,y),((x+2),(y+2))], COLOR_WHITE) + # Add star to pointlist PL 2 if field display flag is true + if fieldsDisplay[2] and 0 <= x2 < screen_size[0] - 2 and 0 <= y2 < screen_size[1] - 2: + lj23.PolyLineOneColor([(x2,y2),((x2+1),(y2+1))], c= white, PL = 2, closed = False) ''' if starfieldcount < 200: @@ -243,582 +413,338 @@ def Starfield(hori=0,verti=0): fwork.PolyLineOneColor([(x3,y3),((x3+2),(y3+2))], c=colorify.rgb2hex([255,255,255]), PL = 3, closed = False) ''' - - lj3.Text(message, color, PL = 2, xpos = 300, ypos = 300, resize = 1, rotx =0, roty =0 , rotz=0) - lj3.DrawPL(0) - lj3.DrawPL(1) - lj3.DrawPL(2) - #lj3.DrawPL(3) + # Laser 3 Display a word. + if fieldsDisplay[3]: + lj23.Text(message, white, PL = 3, xpos = 300, ypos = 300, resize = 1, rotx =0, roty =0 , rotz=0) -# Curve 1 : generic pose animations -import json -CurrentPose = 1 -# get absolute body position points -def getCOCO(pose_json,pose_points, people): - - dots = [] - for dot in pose_points: - if len(pose_json['part_candidates'][people][str(dot)]) != 0: - dots.append((pose_json['part_candidates'][people][str(dot)][0], pose_json['part_candidates'][people][str(dot)][1])) - return dots + # If field display is True for each laser + for laser in range(LaserNumber): + + # Actually send the field point list. + if fieldsDisplay[laser]: + lj23.DrawPL(laser) -# get relative (-1 0 1) body position points. a position -1, -1 means doesn't exist -def getBODY(pose_json,pose_points, people): +# display the Realtime open pose face according to flag. +def LiveFace(): - dots = [] - for dot in pose_points: - #print pose_points - if len(pose_json['people'][people]['pose_keypoints_2d']) != 0: - #print "people 0" - if pose_json['people'][people]['pose_keypoints_2d'][dot * 3] != -1 and pose_json['people'][people]['pose_keypoints_2d'][(dot * 3)+1] != -1: - dots.append((pose_json['people'][people]['pose_keypoints_2d'][dot * 3], pose_json['people'][people]['pose_keypoints_2d'][(dot * 3)+1])) - return dots + # All laser loop + for laser in range(LaserNumber): + # for anim in anims[laseranims]: + + # if display flag is True, send the face points. + if liveDisplay[laser]: + pass -# get absolute face position points -def getFACE(pose_json,pose_points, people): +# +# OSC +# - dots = [] - for dot in pose_points: +oscserver = OSCServer( (myIP, OSCinPort) ) +oscserver.timeout = 0 +#oscrun = True - if len(pose_json['people'][people]['face_keypoints_2d']) != 0: - #print "people 0" - if pose_json['people'][people]['face_keypoints_2d'][dot * 3] != -1 and pose_json['people'][people]['face_keypoints_2d'][(dot * 3)+1] != -1: - dots.append((pose_json['people'][people]['face_keypoints_2d'][dot * 3], pose_json['people'][people]['face_keypoints_2d'][(dot * 3)+1])) +# this method of reporting timeouts only works by convention +# that before calling handle_request() field .timed_out is +# set to False +def handle_timeout(self): + self.timed_out = True - return dots +# funny python's way to add a method to an instance of a class +import types +oscserver.handle_timeout = types.MethodType(handle_timeout, oscserver) +# default handler +def OSChandler(path, tags, args, source): -# Body parts -def bodyCOCO(pose_json, people): - pose_points = [10,9,8,1,11,12,13] - return getBODY(pose_json,pose_points, people) - -def armCOCO(pose_json, people): - pose_points = [7,6,5,1,2,3,4] - return getBODY(pose_json,pose_points, people) - -def headCOCO(pose_json, people): - pose_points = [1,0] - return getBODY(pose_json,pose_points, people) - - -# Face keypoints -def face(pose_json, people): - pose_points = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] - return getFACE(pose_json,pose_points, people) - -def browL(pose_json, people): - pose_points = [26,25,24,23,22] - return getFACE(pose_json,pose_points, people) - -def browR(pose_json, people): - pose_points = [21,20,19,18,17] - return getFACE(pose_json,pose_points, people) - -def eyeR(pose_json, people): - pose_points = [36,37,38,39,40,41,36] - return getFACE(pose_json,pose_points, people) - -def eyeL(pose_json, people): - pose_points = [42,43,44,45,46,47,42] - return getFACE(pose_json,pose_points, people) - -def nose(pose_json, people): - pose_points = [27,28,29,30] - return getFACE(pose_json,pose_points, people) - -def mouth(pose_json, people): - pose_points = [48,59,58,57,56,55,54,53,52,51,50,49,48,60,67,66,65,64,63,62,61,60] - return getFACE(pose_json,pose_points, people) - -import os - - -# Get frame number for pose path describe in PoseDir -def lengthPOSE(pose_dir): - - if debug > 0: - print("Check directory",'poses/' + pose_dir) - if os.path.exists('poses/' + pose_dir): - numfiles = sum(1 for f in os.listdir('poses/' + pose_dir) if os.path.isfile(os.path.join('poses/' + pose_dir + '/', f)) and f[0] != '.') - if debug > 0: - print(numfiles,"images") - return numfiles + oscaddress = ''.join(path.split("/")) + print("Default OSC Handler : msg from Client : " + str(source[0]),) + print("OSC address", path, "with",) + if len(args) > 0: + print("args", args) else: - if debug > 0: - print("but it doesn't even exist!") - return 0 + print("noargs") + #oscIPout = str(source[0]) + #osclient.connect((oscIPout, oscPORTout)) -def preparePOSE(): - global anims0, anims1, anims2 - # anim format (name, xpos,ypos, resize, currentframe, totalframe, count, speed) - # total frames is fetched from directory file count + +# RAW OSC Frame available ? +def OSCframe(): + # clear timed_out flag + #print "oscframe" + oscserver.timed_out = False + # handle all pending requests then return + while not oscserver.timed_out: + oscserver.handle_request() + + +# Stop osc server +def OSCstop(): + + oscserver.close() + + +# /pose/idiotia/lasernumber 1 +def OSCidiotia(address, value): - anims1 = [['sky',50,100,300,0,0,0,1],['2dancer1', 400,100, 300,0,0,0,1],['1dancer', 400,100, 300,0,0,0,1],['window1',100,100,300,0,0,0,1]] - anims2 = [['window1', 400,200, 300,0,0,0,1],['2dancer1',100,200,300,0,0,0,1]] - for anim in anims1: - anim[5]= lengthPOSE(anim[0]) - anims0 = anims1 - - -# display n pose animations on Laser 0 -def Pose(): - global anims0, anims1, anims2 - - for anim in anims0: - PL = 0 - dots = [] - print(anim, anim[5]) - # repeat anim[7] time the same frame - anim[6] +=1 - if anim[6] == anim[7]: - - anim[6] = 0 - # increase current frame and compare to total frame - anim[4] += 1 - if anim[4] == anim[5]: - anim[4] = 0 - - - posename = 'poses/' + anim[0] + '/' + anim[0] +'-'+str("%05d"%anim[4])+'.json' - posefile = open(posename , 'r') - posedatas = posefile.read() - pose_json = json.loads(posedatas) - - for people in range(len(pose_json['people'])): - - lj3.rPolyLineOneColor(bodyCOCO(pose_json, people), c=color, PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(armCOCO(pose_json, people), c=color, PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(headCOCO(pose_json, people), c=color, PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - - # Face - ''' - #lj3.rPolyLineOneColor(face(pose_json, people), c=color, PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(browL(pose_json, people), c=color, PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(browR(pose_json, people), c=color, PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(eyeR(pose_json, people), c=color, PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(eyeL(pose_json, people), c=color, PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(nose(pose_json, people), c=color, PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(mouth(pose_json, people), c=color, PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - ''' - - lj3.DrawPL(PL) - time.sleep(0.02) - - - # decrease current frame - if keystates[pygame.K_w]: # and not keystates_prev[pygame.K_w]: - CurrentPose -= 1 - if CurrentPose < 2: - CurrentPose = numfiles -1 - #time.sleep(0.033) - print("Frame : ",CurrentPose) - - # increaser current frame - if keystates[pygame.K_x]: # and not keystates_prev[pygame.K_x]: - CurrentPose += 1 - if CurrentPose > numfiles -1: - CurrentPose = 1 - #time.sleep(0.033) - print("Frame : ",CurrentPose) - - - -# Curve 2 Faces -import json -CurrentPose = 1 - -def prepareFACES(): - - - # anim format (name, xpos, ypos, resize, currentframe, totalframe, count, frame repeat) - # 0 1 2 3 4 5 6 7 - # total frame is fetched from directory file count - - anims[0] = [['detroit1', 300,300, 100,0,0,0,1]] - anims[1] = [['detroit1', 400,200, 200,0,0,0,1]] - anims[2] = [['detroit1', 500,200, 300,0,0,0,1]] - - ''' - # read anims number of frames from disk. - for anim in range(len(anims0)): - anims0[anim][5]= lengthPOSE(anims0[anim][0]) - for anim in range(len(anims1)): - anims1[anim][5]= lengthPOSE(anims1[anim][0]) - for anim in range(len(anims2)): - anims2[anim][5]= lengthPOSE(anims2[anim][0]) - ''' - - #replace code below - ''' - for laseranims in range(3): - if debug > 0: - print "anims:",anims[laseranims], - for anim in range(len(anims[laseranims])): - anims[laseranims][anim][5]= lengthPOSE(anims[laseranims][anim][0]) - if debug > 1: - print anims[laseranims][anim][5] - ''' - #by this one - #thanks to https://stackoverflow.com/questions/19184335/is-there-a-need-for-rangelena - - - for laseranims in anims: - - for anim in laseranims: - anim[5] = lengthPOSE(anim[0]) - - if debug > 0: - print("anim :", anim) - print("length :", anim[5]) - - - -# display the face animation describe in PoseDir -def Faces(): - - for laseranims in range(3): - for anim in anims[laseranims]: - PL = laseranims - #print PL, anim - - dots = [] - - # increase counter [6] - # compare to repeat [7] time the same frame - anim[6] +=1 - if anim[6] == anim[7]: - - # reset repeat - anim[6] = 0 - - # increase current frame [4] - anim[4] += 1 - - # compare to total frame [5] - if anim[4] == anim[5]: - anim[4] = 0 - - - posename = 'poses/' + anim[0] + '/' + anim[0] +'-'+str("%05d"%anim[4])+'.json' - posefile = open(posename , 'r') - posedatas = posefile.read() - pose_json = json.loads(posedatas) - - # Face - - for people in range(len(pose_json['people'])): - - #lj3.rPolyLineOneColor(face(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(browL(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(browR(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(eyeR(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(eyeL(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(nose(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(mouth(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - - lj3.DrawPL(PL) - time.sleep(0.02) - -# Curve 3 -# Dancers -import json -CurrentPose = 1 - -def prepareDANCERS(): - - # anim format (name, xpos,ypos, resize, currentframe, totalframe, count, speed) - # total frame is fetched from directory file count - - anims[0] = [['1dancer',500,200,300,0,0,0,10]] - anims[1] = [['2dancer1',500,200,300,0,0,0,10]] - anims[2] = [['window1',500,200,300,0,0,0,10]] - #anims[1] = [['2dancer1',100,200,300,0,0,0,10]] - #anims[2] = [['window1',400,200, 300,0,0,0,10]] - # read anims number of frames from disk. - - for laseranims in range(3): - for anim in range(len(anims[laseranims])): - anims[laseranims][anim][5]= lengthPOSE(anims[laseranims][anim][0]) - -# display the pose animation describe in PoseDir -def Dancers(): - - for laseranims in range(3): - - for anim in anims[laseranims]: - PL = laseranims - #print PL, anim - dots = [] - #print anim, anim[5] - # repeat anim[7] time the same frame - anim[6] +=1 - if anim[6] == anim[7]: - - anim[6] = 0 - # increase current frame and compare to total frame - anim[4] += 1 - if anim[4] == anim[5]: - anim[4] = 0 - - - #bhorosc.sendresol("/layer1/clip1/connect",1) - #bhorosc.sendresol("/layer1/clip1/connect",0) - - posename = 'poses/' + anim[0] + '/' + anim[0] +'-'+str("%05d"%anim[4])+'.json' - posefile = open(posename , 'r') - posedatas = posefile.read() - pose_json = json.loads(posedatas) - - - for people in range(len(pose_json['people'])): - lj3.rPolyLineOneColor(bodyCOCO(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(armCOCO(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - - lj3.rPolyLineOneColor(browL(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(browR(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(eyeR(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(eyeL(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(nose(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(mouth(pose_json, people), c=color, PL = laseranims, closed = False,xpos = anim[1], ypos = anim[2], resize = anim[3]) - - - lj3.DrawPL(PL) - - ''' - lj3.PolyLineOneColor(bodyCOCO(pose_json), c=color, PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.PolyLineOneColor(armCOCO(pose_json), c=color, PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.PolyLineOneColor(headCOCO(pose_json), c=color, PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - - - PL[PL] = fwork.LinesPL(PL) - ''' - - -# Curve 4 IdiotIA -import json -CurrentPose = 1 - -def prepareIdiotIA(): - - - # anim format (name, xpos,ypos, resize, currentframe, totalframe, count, speed) - # total frame is fetched from directory file count - - anims[0] = [['detroit1', 300,300, 100,0,0,0,1]] - anims[1] = [['detroit1', 400,200, 200,0,0,0,1]] - anims[2] = [['detroit1', 500,200, 300,0,0,0,1]] - - ''' - # read anims number of frames from disk. - for anim in range(len(anims0)): - anims0[anim][5]= lengthPOSE(anims0[anim][0]) - for anim in range(len(anims1)): - anims1[anim][5]= lengthPOSE(anims1[anim][0]) - for anim in range(len(anims2)): - anims2[anim][5]= lengthPOSE(anims2[anim][0]) - ''' - - #replace code below - ''' - for laseranims in range(3): + laser = int(address[14:]) if debug > 0: - print "anims:",anims[laseranims], - for anim in range(len(anims[laseranims])): - anims[laseranims][anim][5]= lengthPOSE(anims[laseranims][anim][0]) - if debug > 1: - print anims[laseranims][anim][5] - ''' - #by this one - #thanks to https://stackoverflow.com/questions/19184335/is-there-a-need-for-rangelena - - for laseranims in anims: - if debug > 1: - print("anims:",laseranims) - for anim in laseranims: - anim[5]=lengthPOSE(anim[0]) - if debug > 1: - print(anim[5]) + print("pose idiotia got ",address,value) + print("laser", laser, value) + if value == "1" or value == 1: + + idiotiaDisplay[laser] = True + liveDisplay[laser] = False + fieldsDisplay[laser] = False + print(idiotiaDisplay,liveDisplay,fieldsDisplay) + + else: + + idiotiaDisplay[laser] = False + print(idiotiaDisplay,liveDisplay,fieldsDisplay) + + UpdatePoseUI() + +# /pose/anim/animnumber 1 +def OSCanim(address, value): + global currentIdiotia + + anim = int(address[11:]) + + if debug > 0: + print("pose anim got :", address, type(value), value) + print("anim", anim) + + if value == "1" or value == 1: + currentIdiotia = anim + UpdatePoseUI() + WebStatus("Running "+ anims[currentIdiotia][0]+"...") -# display the face animation describe in PoseDir -def Faces(): - for laseranims in range(3): - for anim in anims[laseranims]: - PL = laseranims - #print PL, anim - dots = [] - #print anim, anim[5] - # repeat anim[7] time the same frame - anim[6] +=1 - if anim[6] == anim[7]: +# /pose/speed/speed value +# value : 1 slower / 2 stop / 3 play / 4 faster +def OSCspeed(address, value): + global anims + + speedflag = int(address[12:]) - anim[6] = 0 - # increase current frame and compare to total frame - anim[4] += 1 - if anim[4] == anim[5]: - anim[4] = 0 + if debug > 0: + print("pose speed got :", address, type(value), value) + print("speed", speedflag) + + if value == "1" or value == 1: + # slower + if speedflag == 1: + anims[currentIdiotia][7] = 0.1 + UpdateSpeedUI() + WebStatus("Pose speed is 0.1") + + # stop + if speedflag == 2: + anims[currentIdiotia][7] = 0 + UpdateSpeedUI() + WebStatus("Pose speed is stop") + + # play + if speedflag == 3: + anims[currentIdiotia][7] = 0.25 + UpdateSpeedUI() + WebStatus("Pose speed is 0.25") + + # faster + if speedflag == 4: + anims[currentIdiotia][7] = 2 + UpdateSpeedUI() + WebStatus("Pose speed is 2") - posename = 'poses/' + anim[0] + '/' + anim[0] +'-'+str("%05d"%anim[4])+'.json' - posefile = open(posename , 'r') - posedatas = posefile.read() - pose_json = json.loads(posedatas) - - # Face - - for people in range(len(pose_json['people'])): - - #lj3.rPolyLineOneColor(face(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(browL(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(browR(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(eyeR(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(eyeL(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(nose(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - lj3.rPolyLineOneColor(mouth(pose_json, people), c=color, PL = laseranims, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) - - lj3.DrawPL(PL) - time.sleep(0.02) +# /pose/live/lasernumber value +def OSClive(address, value): + + print("live",address,value) + laser = int(address[11:]) + #print("laser", laser, value) + + if value == "1" or value == 1: + idiotiaDisplay[laser] = False + liveDisplay[laser] = True + fieldsDisplay[laser] = False + UpdatePoseUI() + +# /pose/field/lasernumber value +def OSCfield(address, value): + + if debug >0: + print("Pose field got", address, "with value", type(value), value) + laser = int(address[12:]) + #print("laser", laser, value) + + if value == "1" or value == 1: + print("field",laser,"true") + idiotiaDisplay[laser] = False + liveDisplay[laser] = False + fieldsDisplay[laser] = True + UpdatePoseUI() + + +# /pose/ljclient def OSCljclient(value): - # Will receive message address, and message data flattened in s, x, y - print("Bank0 got /bank0/ljclient with value", value) + print("Pose bank got /pose/ljclient with value", value) ljclient = value - lj3.LjClient(ljclient) - -def OSCpl(value): - - print("Bank0 got /bank0/pl with value", value) - lj3.WebStatus("Bank0 to pl "+ str(value)) - lj3.LjPl(value) - - -# Dancers, Starfield, Pose, Face -def OSCrun(value): - # Will receive message address, and message data flattened in s, x, y - print("I got /run with value", value) - doit = value - -# /quit -def OSCquit(): - - WebStatus("Bank0 stopping") - print("Stopping OSC...") - lj3.OSCstop() - sys.exit() - -def WebStatus(message): - lj3.SendLJ("/status",message) + lj23.LjClient(ljclient) +''' # /pose/ping value def OSCping(value): - print("bank0 got /pose/ping with value", value) - lj3.OSCping("bank0") - + lj23.OSCping() ''' +''' +# Starfield, idiotia +def OSCrun(value): + # Will receive message address, and message data flattened in s, x, y + print("Pose bank got /run with value", value) + doit = value +''' +# /quit dummyvalue +def quit(value): + # don't do this at home (or it'll quit blender) + global oscrun + + oscrun = False + print("Stopped by /quit.") + lj23.ClosePlugin() -print('Loading Bank0...') +def WebStatus(message): + lj23.SendLJ("/status",message) -WebStatus("Load Bank0") +# Update Pose webUI +def UpdatePoseUI(): + + #WebStatus("Updating Pose UI...") + for laser in range(LaserNumber): + + if idiotiaDisplay[laser]: + lj23.SendLJ("/pose/idiotia/" + str(laser) + " 1") + else: + lj23.SendLJ("/pose/idiotia/" + str(laser) + " 0") + + if liveDisplay[laser]: + lj23.SendLJ("/pose/live/" + str(laser) + " 1") + else: + lj23.SendLJ("/pose/live/" + str(laser) + " 0") + + if fieldsDisplay[laser]: + lj23.SendLJ("/pose/field/" + str(laser) + " 1") + else: + lj23.SendLJ("/pose/field/" + str(laser) + " 0") + + + for anim in range(19): + if anim == currentIdiotia: + lj23.SendLJ("/pose/anim/" + str(anim) + " 1") + else: + lj23.SendLJ("/pose/anim/" + str(anim) + " 0") + +def UpdateSpeedUI(): + + lj23.SendLJ("/pose/speed/1 0") + lj23.SendLJ("/pose/speed/2 0") + lj23.SendLJ("/pose/speed/3 0") + lj23.SendLJ("/pose/speed/4 0") + + + +print('Loading Pose bank...') +WebStatus("Loading Pose bank...") +lj23.SendLJ("/pose/start", 1) # OSC Server callbacks -print("Starting OSC at 127.0.0.1 port",OSCinPort,"...") +print("Starting OSC server at", myIP, ":", OSCinPort, "...") osc_startup() -osc_udp_server("127.0.0.1", OSCinPort, "InPort") +osc_udp_server(myIP, OSCinPort, "InPort") -osc_method("/bank0/run*", OSCrun) -osc_method("/bank0/ping*", OSCping) -osc_method("/bank0/ljclient", OSCljclient) -osc_method("/bank0/ljpl", OSCpl) -osc_method("/quit", OSCquit) +#osc_method("/pose/run*", OSCrun) +osc_method("/ping", lj23.OSCping) +osc_method("/quit*", quit) +osc_method("/pose/ljclient", OSCljclient) +osc_method("/pose/idiotia/*", OSCidiotia, argscheme=OSCARG_ADDRESS + OSCARG_DATAUNPACK) +osc_method("/pose/field/*", OSCfield,argscheme=OSCARG_ADDRESS + OSCARG_DATAUNPACK) +osc_method("/pose/live/*", OSClive, argscheme=OSCARG_ADDRESS + OSCARG_DATAUNPACK) +osc_method("/pose/anim/*", OSCanim, argscheme=OSCARG_ADDRESS + OSCARG_DATAUNPACK) +osc_method("/pose/speed/*", OSCspeed, argscheme=OSCARG_ADDRESS + OSCARG_DATAUNPACK) -''' -import pygame -pygame.init() -Nbpads = pygame.joystick.get_count() -print ("Joypads : ", str(Nbpads)) +anims =[[]]*19 -if Nbpads != 2: - - print ('') - print ('') - print ("THIS VERSION NEEDS 2 PADS. PLEASE CONNECT THEM.") - print ('') - sys.exit() - - -if Nbpads > 1: - - pad2 = pygame.joystick.Joystick(1) - pad2.init() - print ("Pad2 :", pad2.get_name()) - numButtons = pad2.get_numbuttons() - #print ("Axis Pad 2 :", str(pad2.get_numaxes())) - #print ("Buttons Pad 2 :" , str(numButtons)) - - # joy is pad abstraction to handle many different devices. - joy2 = lj3.setup_controls(pad2) - -if Nbpads > 0: - - pad1 = pygame.joystick.Joystick(0) - pad1.init() - print ("Pad1 :",pad1.get_name()) - numButtons = pad1.get_numbuttons() - joy1 = lj3.setup_controls(pad1) - #print ("Axis Pad 1 :", str(pad1.get_numaxes())) - #print ("Buttons Pad 1 :" , str(numButtons)) - - -''' - -anims =[[],[],[],[]] -color = lj3.rgb2int(255,255,255) - -#prepareSTARFIELD() -#preparePOSE() -#prepareDANCERS() -prepareFACES() - +prepareIdiotIA(0) +prepareSTARFIELD() #doit = Starfield -#doit = Pose -doit = Faces -#doit = Dancers +#doit = IdiotIA -WebStatus("Bank0 ready.") -print("Bank0 ready") +white = lj23.rgb2int(255,255,255) +red = lj23.rgb2int(255,0,0) +blue = lj23.rgb2int(0,0,255) +green = lj23.rgb2int(0,255,0) + +print("Updating Pose UI...") +UpdatePoseUI() + +WebStatus("Running "+ anims[currentIdiotia][0]+"...") +#WebStatus("Pose bank running.") +#print("Pose bank running") +print("Running "+ anims[currentIdiotia][0]+" on " + str(LaserNumber) +" lasers.") def Run(): try: - while 1: - #Starfield(hori=0,verti=0) - doit() + while lj23.oscrun: + + OSCframe() + # If you want an idea + # t0 = time.time() + Starfield(hori=0,verti=0) + IdiotIA() + #LiveFace() + time.sleep(0.002) + #t1 = time.time() + # looptime = t1 - t0 + # 25 frames/sec -> 1 frame is 0.04 sec long + # if looptime is 0.01 sec + # 0.04/0.01 = 4 loops with the same anim + # so speedanim is 1 / 4 = 0.25 + # speedanim = 1 / (0.04 / looptime) + + + #print("Took %f" % (t1 - t0, )) except KeyboardInterrupt: pass + except Exception as e: + import sys, traceback + print ('\n---------------------') + print ('Exception: %s' % e) + print ('- - - - - - - - - - -') + traceback.print_tb(sys.exc_info()[2]) + print ("\n") + # Gently stop on CTRL C finally: - WebStatus("Bank0 Exit") - print("Stopping OSC...") - lj3.OSCstop() - - print ("Bank0 Stopped.") + lj23.ClosePlugin() + OSCstop() Run() diff --git a/plugins/VJing/idiotia.py b/plugins/VJing/idiotia.py index 4a55c00..55a8dec 100644 --- a/plugins/VJing/idiotia.py +++ b/plugins/VJing/idiotia.py @@ -24,33 +24,67 @@ import pdb from datetime import datetime from random import randrange import redis -import lj3 -import sys,time,traceback +import sys +import ast import os +import time,traceback + + +ljpath = r'%s' % os.getcwd().replace('\\','/') + +# import from shell + +sys.path.append(ljpath +'/../../libs/') +print(ljpath +'/../../libs/') +#import from LJ +sys.path.append(ljpath +'/libs/') +print (ljpath +'/libs/') + + +is_py2 = sys.version[0] == '2' +if is_py2: + from OSC import OSCServer, OSCClient, OSCMessage +else: + from OSC3 import OSCServer, OSCClient, OSCMessage + +import lj23 as lj3 + +''' from osc4py3.as_eventloop import * from osc4py3 import oscbuildparse #from osc4py3 import oscmethod as osm from osc4py3.oscmethod import * +''' + import argparse # 0.25 : each frame will be repeated 4 times. -animspeed = 0.25 +animspeed = 0.1 screen_size = [700,700] xy_center = [screen_size[0]/2,screen_size[1]/2] -message = "LO" +message = "TEAMLASER" OSCinPort = 8011 ljclient = 0 -idiotiaDisplay = [True,True,False,False] -#idiotiaDisplay = [False,False,False,False] -liveDisplay = [False,False,False,False] +#liveDisplay = [False,False,False,False] -fieldsDisplay = [False,False,True,True] +liveDisplay = [True, True, True, True] #fieldsDisplay = [True,True,True,True] +idiotiaDisplay = [False, False, False, False] +fieldsDisplay = [False, False, False, False] + + + +''' +fieldsDisplay = [False,False,True,True] +''' + +#idiotiaDisplay = [True,True,False,False] + currentIdiotia = 0 print ("") @@ -97,10 +131,29 @@ else: if args.Lasers != None: LaserNumber = args.Lasers else: - LaserNumber = 4 + LaserNumber = 1 -lj3.Config(redisIP,ljclient) +r = lj3.Config(redisIP,ljclient,"pose") + + +# +# OSC +# + +oscserver = OSCServer( (myIP, OSCinPort) ) +oscserver.timeout = 0 +#oscrun = True + +# this method of reporting timeouts only works by convention +# that before calling handle_request() field .timed_out is +# set to False +def handle_timeout(self): + self.timed_out = True + +# funny python's way to add a method to an instance of a class +import types +oscserver.handle_timeout = types.MethodType(handle_timeout, oscserver) @@ -119,11 +172,14 @@ import json # Get frame number for pose path describe in PoseDir def lengthPOSE(pose_dir): - #if debug > 0: - # print("Check directory",'poses/' + pose_dir) - if os.path.exists('poses/' + pose_dir): - numfiles = sum(1 for f in os.listdir('poses/' + pose_dir) if os.path.isfile(os.path.join('poses/' + pose_dir + '/', f)) and f[0] != '.') - return numfiles + if debug > 0: + print("Checking directory",'plugins/VJing/poses/' + pose_dir) + if os.path.exists('plugins/VJing/poses/' + pose_dir): + + numfiles = sum(1 for f in os.listdir('plugins/VJing/poses/' + pose_dir) if os.path.isfile(os.path.join('plugins/VJing/poses/' + pose_dir + '/', f)) and f[0] != '.') + if debug > 0: + print(numfiles, 'frames') + return numfiles else: if debug > 0: print("but it doesn't even exist!") @@ -166,11 +222,108 @@ def prepareIdiotIA(currentAnim): anim[5] = lengthPOSE(anim[0]) WebStatus("Checking "+ anim[0] +"...") if debug > 0: - print('poses/' + anim[0], "length :", anim[5], "frames") + print('plugins/VJing/poses/' + anim[0], "length :", anim[5], "frames") print("Current IdiotIA anim is",anims[currentIdiotia][0],"("+str(currentIdiotia)+")") +''' +pose_keypoints_2d +face_keypoints_2d +hand_left_keypoints_2d +hand_right_keypoints_2d +pose_keypoints_3d +face_keypoints_3d +hand_left_keypoints_3d +hand_right_keypoints_3d + +"/0/face_keypoints_2d" +"[-0.0946419, -0.521328, 0.675269, -0.0883931, -0.413923, 0.69358, -0.0758954, -0.302815, 0.73599, -0.059232, -0.191707, 0.640398, -0.0384026, -0.0917102, 0.683398, -0.00507569, -0.00652742, 0.643006, 0.0428318, 0.0601374, 0.558626, 0.0844904, 0.108284, 0.576597, 0.142813, 0.108284, 0.621916, 0.178223, 0.0749518, 0.546389, 0.207384, 0.0193979, 0.556877, 0.22613, -0.0361562, 0.660514, 0.244876, -0.102821, 0.713871, 0.267789, -0.1843, 0.706604, 0.290701, -0.265779, 0.680418, 0.307364, -0.347258, 0.617497, 0.31153, -0.428738, 0.53877, -0.0446514, -0.547253, 0.784288, -0.00715858, -0.569474, 0.873856, 0.0303342, -0.558364, 0.835336, 0.0740758, -0.543549, 0.827985, 0.111569, -0.525031, 0.809191, 0.213632, -0.543549, 0.819012, 0.242794, -0.569474, 0.884373, 0.265706, -0.5954, 0.842317, 0.292784, -0.613918, 0.804365, 0.313613, -0.625028, 0.740405, 0.163642, -0.443552, 0.805348, 0.169891, -0.362073, 0.837128, 0.169891, -0.280594, 0.839031, 0.174057, -0.213929, 0.68711, 0.111569, -0.162078, 0.725528, 0.134481, -0.154671, 0.837339, 0.159476, -0.150968, 0.887387, 0.178223, -0.158375, 0.835752, 0.196969, -0.180597, 0.698174, 0.00950491, -0.432441, 0.864517, 0.0345001, -0.458366, 0.859197, 0.0636612, -0.46207, 0.867471, 0.0907393, -0.432441, 0.852409, 0.0636612, -0.417627, 0.876186, 0.0324172, -0.413923, 0.834265, 0.21155, -0.454663, 0.875569, 0.232379, -0.491699, 0.816674, 0.26154, -0.50281, 0.878064, 0.282369, -0.476884, 0.839634, 0.263623, -0.450959, 0.912212, 0.24071, -0.450959, 0.896937, 0.0719929, -0.047267, 0.761505, 0.101154, -0.065785, 0.864081, 0.130315, -0.0694886, 0.910774, 0.159476, -0.065785, 0.895222, 0.17614, -0.0731922, 0.846551, 0.194886, -0.0731922, 0.74389, 0.209467, -0.0731922, 0.573107, 0.194886, -0.0213418, 0.677465, 0.17614, 0.0156941, 0.755373, 0.15531, 0.0231014, 0.765641, 0.124066, 0.0231014, 0.863117, 0.0928223, -0.00282377, 0.823042, 0.0928223, -0.0398598, 0.743914, 0.128232, -0.0361562, 0.932259, 0.157393, -0.0361562, 0.877732, 0.17614, -0.0398598, 0.853163, 0.196969, -0.0620813, 0.645926, 0.17614, -0.0398598, 0.80738, 0.157393, -0.0324526, 0.872388, 0.128232, -0.0361562, 0.924673, 0.0511636, -0.450959, 0.801577, 0.244876, -0.480588, 0.915322]" + +"/peopleCount" +"2" +''' + +def bodyREDIS(people): + + dots = [] + pose = [] + redispose = [] + + pose_points = [10,9,8,1,11,12,13] + print ("people body", people) + + print ("/"+str(people)+"/pose_keypoints_2d") + #pose = np.array(ast.literal_eval(r.get("/"+str(people)+"/pose_keypoints_2d"))) + redispose = r.get("/"+str(people)+"/pose_keypoints_2d") + #print ("redispose",redispose) + poseast = ast.literal_eval(redispose) + #print ("poseast",poseast) + #print (poseast[0]) + pose = np.array(poseast) + #print (np.array((ast.literal_eval(strg)))) + #print pose + #print(pose[0], pose[1]) + #print("len pose", len(pose)) + ''' + for dot in range(len(pose)/3): + #print dot + dots.append(((pose[dot * 3], pose[(dot * 3)+1]))) + #print((pose[dot * 3], pose[(dot * 3)+1])) + ''' + for dot in range(len(pose_points)): + bodypoint = pose_points[dot] + if pose[bodypoint * 3] != -1 and pose[(bodypoint * 3)+1] != -1: + + dots.append(((pose[bodypoint * 3], pose[(bodypoint * 3)+1]))) + #print((pose[dot * 3], pose[(dot * 3)+1])) + #print "body point ", pose_points[dot],dot, (pose[bodypoint * 3], pose[(bodypoint * 3)+1]) + print dots + return dots + +# display the Realtime open pose face according to flag. +def LivePose(): + + + laser = 0 + # Old style : if display flag is True for given laser, send the face points. + # New style : should send the people points in a PL, then use the PL's dest object to describe + # what to do with it + + if liveDisplay[0]: + peoplenumber = int(r.get("/peopleCount")) + print peoplenumber + for currentnumber in range(peoplenumber): + PL = 0 + #print PL, anim + # Draw Pose + + x_offset = 26 * (- (0.9*peoplenumber) + 3*currentnumber) + for people in range(peoplenumber): + + print("people", people) + x_offset = 26 * (- (0.9*peoplenumber) + 3*currentnumber) + print x_offset + lj3.rPolyLineOneColor(bodyREDIS(people), c= white, PL = PL, closed = False, xpos = xy_center[0]+ x_offset, ypos = xy_center[1], resize = 250) + + lj3.DrawPL(PL) + + + +# get relative (-1 0 1) body position points. a position -1, -1 means doesn't exist +def getBODY(pose_json,pose_points, people): + + dots = [] + for dot in pose_points: + #print pose_points + if len(pose_json['people'][people]['pose_keypoints_2d']) != 0: + #print "people 0" + if pose_json['people'][people]['pose_keypoints_2d'][dot * 3] != -1 and pose_json['people'][people]['pose_keypoints_2d'][(dot * 3)+1] != -1: + dots.append((pose_json['people'][people]['pose_keypoints_2d'][dot * 3], pose_json['people'][people]['pose_keypoints_2d'][(dot * 3)+1])) + + + return dots + # get absolute face position points def getFACE(pose_json,pose_points, people): @@ -185,6 +338,21 @@ def getFACE(pose_json,pose_points, people): return dots + +# Body parts +def bodyCOCO(pose_json, people): + pose_points = [10,9,8,1,11,12,13] + return getBODY(pose_json,pose_points, people) + +def armCOCO(pose_json, people): + pose_points = [7,6,5,1,2,3,4] + return getBODY(pose_json,pose_points, people) + +def headCOCO(pose_json, people): + pose_points = [1,0] + return getBODY(pose_json,pose_points, people) + + # Face keypoints def face(pose_json, people): pose_points = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] @@ -239,22 +407,20 @@ def IdiotIA(): anim = anims[currentIdiotia] #print(anim) - PL = laser - #print PL, anim - dots = [] # increase current frame [4] of speed [7] frames - #print(anim[4],anim[7],anim[4]+anim[7]) - + # print(anim[4],anim[7],anim[4]+anim[7]) + # print("frame", anim[4]) anim[4] = anim[4]+anim[7] - + # print("animspeed",anim[7], "newframe", anim[4], "maximum frame", anim[5] ) # compare to total frame [5] if anim[4] >= anim[5]: anim[4] = 0 - posename = 'poses/' + anim[0] + '/' + anim[0] +'-'+str("%05d"%int(anim[4]))+'.json' + posename = 'plugins/VJing/poses/' + anim[0] + '/' + anim[0] +'-'+str("%05d"%int(anim[4]))+'.json' + # print(posename) posefile = open(posename , 'r') posedatas = posefile.read() pose_json = json.loads(posedatas) @@ -421,12 +587,47 @@ def Starfield(hori=0,verti=0): lj3.DrawPL(laser) + + + + # display the Realtime open pose face according to flag. def LiveFace(): # All laser loop for laser in range(LaserNumber): # for anim in anims[laseranims]: + print ("laser", laser) + + # if display flag is True, send the face points. + if liveDisplay[laser]: + + PL = laser + #print PL, anim + dots = [] + pose_json = json.loads(posedatas) + + # Draw Face + + for people in range(len(pose_json['people'])): + + r.get(n) + lj3.rPolyLineOneColor(bodyCOCO(pose_json, people), c=colorify.rgb2hex(gstt.color), PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + lj3.rPolyLineOneColor(armCOCO(pose_json, people), c=colorify.rgb2hex(gstt.color), PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + lj3.rPolyLineOneColor(headCOCO(pose_json, people), c=colorify.rgb2hex(gstt.color), PL = 0, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + + #lj3.rPolyLineOneColor(face(pose_json, people), c = white, PL = laser closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + lj3.rPolyLineOneColor(browL(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + lj3.rPolyLineOneColor(browR(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + lj3.rPolyLineOneColor(eyeR(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + #lj3.rPolyLineOneColor(pupR(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + lj3.rPolyLineOneColor(eyeL(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + #lj3.rPolyLineOneColor(pupL(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + lj3.rPolyLineOneColor(nose(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + lj3.rPolyLineOneColor(mouth(pose_json, people), c = white, PL = laser, closed = False, xpos = anim[1], ypos = anim[2], resize = anim[3]) + + lj3.DrawPL(PL) + # if display flag is True, send the face points. if liveDisplay[laser]: @@ -435,18 +636,20 @@ def LiveFace(): # /pose/idiotia/lasernumber 1 -def OSCidiotia(address, value): +def OSCidiotia(path, tags, args, source): - print("pose idiotia got ",address,value) - laser = int(address[14:]) - print("laser", laser, value) + print("pose idiotia got",path, args) + laser = int(args[0]) + value = int(args[1]) - if value == "1" or value == 1: + if value == 1: + + print("switch on idiotia for laser", laser, value) idiotiaDisplay[laser] = True liveDisplay[laser] = False fieldsDisplay[laser] = False - print(idiotiaDisplay,liveDisplay,fieldsDisplay) + #print(idiotiaDisplay,liveDisplay,fieldsDisplay) else: @@ -455,58 +658,65 @@ def OSCidiotia(address, value): UpdatePoseUI() -# /pose/anim/animnumber 1 -def OSCanim(address, value): - global currentIdiotia - - print("pose anim got :", address, type(value), value) - anim = int(address[11:]) - print("anim", anim) - if value == "1" or value == 1: + +# /pose/anim +def OSCanim(path, tags, args, source): + + print("pose anim got",path, args) + anim = int(args[0]) + state = int(args[1]) + + #print(anim, state) + + if state == 1: + print("/pose/anim switch to",anim) currentIdiotia = anim UpdatePoseUI() - WebStatus("Running "+ anims[currentIdiotia][0]+"...") + WebStatus("Ruuning "+ anims[currentIdiotia][0]+"...") # /pose/live/lasernumber value -def OSClive(address, value): +def OSClive(path, tags, args, source): - print("live",address,value) - laser = int(address[11:]) - #print("laser", laser, value) + print("pose live got",path, args) + laser = int(args[0]) + value = int(args[1]) - if value == "1" or value == 1: - idiotiaDisplay[laser] = False - liveDisplay[laser] = True - fieldsDisplay[laser] = False + + if value == "1": + print("live for laser", laser) + idiotiaDisplay[value] = False + liveDisplay[value] = True + fieldsDisplay[value] = False UpdatePoseUI() # /pose/field/lasernumber value -def OSCfield(address, value): +def OSCfield(path, tags, args, source): - print("Pose field got", address, "with value", type(value), value) - laser = int(address[12:]) - #print("laser", laser, value) + print("pose field got",path, args) + laser = int(args[0]) + value = int(args[1]) - if value == "1" or value == 1: - print("field",laser,"true") - idiotiaDisplay[laser] = False - liveDisplay[laser] = False - fieldsDisplay[laser] = True + + if value == "1": + print("field for laser", laser) + idiotiaDisplay[value] = False + liveDisplay[value] = False + fieldsDisplay[value] = True UpdatePoseUI() # /pose/ljclient -def OSCljclient(value): - print("Pose bank got /pose/ljclient with value", value) - ljclient = value - lj3.LjClient(ljclient) +def OSCljclient(path, tags, args, source): + + print("pose got /viewgen/ljclient with value", args[0]) + lj.WebStatus("viewgen to virtual "+ str(args[0])) + ljclient = args[0] + lj.LjClient(ljclient) + -# /pose/ping value -def OSCping(value): - lj3.OSCping("pose") ''' # Starfield, idiotia @@ -515,7 +725,7 @@ def OSCrun(value): print("Pose bank got /run with value", value) doit = value ''' - +''' # /quit def OSCquit(): @@ -523,6 +733,7 @@ def OSCquit(): print("Stopping OSC...") lj3.OSCstop() sys.exit() +''' def WebStatus(message): lj3.SendLJ("/status",message) @@ -561,20 +772,13 @@ def UpdatePoseUI(): print('Loading Pose bank...') WebStatus("Loading Pose bank...") -# OSC Server callbacks -print("Starting OSC server at", myIP, ":", OSCinPort, "...") -osc_startup() -osc_udp_server(myIP, OSCinPort, "InPort") - -#osc_method("/pose/run*", OSCrun) -osc_method("/ping*", OSCping) -osc_method("/pose/ljclient", OSCljclient) -osc_method("/quit", OSCquit) -osc_method("/pose/idiotia/*", OSCidiotia, argscheme=OSCARG_ADDRESS + OSCARG_DATAUNPACK) -osc_method("/pose/field/*", OSCfield,argscheme=OSCARG_ADDRESS + OSCARG_DATAUNPACK) -osc_method("/pose/live/*", OSClive, argscheme=OSCARG_ADDRESS + OSCARG_DATAUNPACK) -osc_method("/pose/anim/*", OSCanim, argscheme=OSCARG_ADDRESS + OSCARG_DATAUNPACK) - +oscserver.addMsgHandler("/pose/ljclient", OSCljclient) +oscserver.addMsgHandler("/pose/idiotia", OSCidiotia) +oscserver.addMsgHandler("/pose/field", OSCfield) +oscserver.addMsgHandler("/pose/live", OSClive) +oscserver.addMsgHandler("/pose/anim", OSCanim) +# Add OSC generic plugins commands : 'default", /ping, /quit, /pluginame/obj, /pluginame/var, /pluginame/adddest, /pluginame/deldest +lj3.addOSCdefaults(oscserver) anims =[[]]*19 @@ -593,21 +797,22 @@ green = lj3.rgb2int(0,255,0) print("Updating Pose UI...") UpdatePoseUI() -WebStatus("Running "+ anims[currentIdiotia][0]+"...") +WebStatus("Pose "+ anims[currentIdiotia][0]+".") #WebStatus("Pose bank running.") #print("Pose bank running") -print("Running "+ anims[currentIdiotia][0]+" on " + str(LaserNumber) +" lasers.") +print("Pose "+ anims[currentIdiotia][0]+" ready on " + str(LaserNumber) +" lasers.") def Run(): try: - while 1: + while lj3.oscrun: - lj3.OSCframe() # If you want an idea # t0 = time.time() - Starfield(hori=0,verti=0) - IdiotIA() + lj3.OSCframe() + #Starfield(hori=0,verti=0) + #IdiotIA() + LivePose() #LiveFace() time.sleep(0.002) #t1 = time.time() diff --git a/plugins/games/ljpong/entities.py b/plugins/games/ljpong/entities.py index b640faf..abe5653 100644 --- a/plugins/games/ljpong/entities.py +++ b/plugins/games/ljpong/entities.py @@ -76,7 +76,7 @@ BALL_MAX = 4 BALL_SIZE_X = 3 BALL_SIZE_Y = 3 LASER_ANGLE = 0 - +plnumber = 0 GRAVITY = 0.0001 @@ -122,6 +122,7 @@ def LogoDraw(plnumber): for xy in pl_color[0]: xy_list.append((LOGO_OFFSET_X + xy[0], LOGO_OFFSET_Y + xy[1])) #print xy_list + #print plnumber lj.PolyLineOneColor(xy_list, c, plnumber, False) diff --git a/plugins/games/ljpong/lj.py b/plugins/games/ljpong/lj.py deleted file mode 100644 index 5244032..0000000 --- a/plugins/games/ljpong/lj.py +++ /dev/null @@ -1,322 +0,0 @@ -# coding=UTF-8 - -''' -LJ v0.8.1 -Some LJ functions useful for python 2.7 clients (was framy.py) -Functions and documentation here is low priority as python 2 support will stop soon. -Better code your plugin with python 3 and lj3.py. - - -Config -PolyLineOneColor -rPolyLineOneColor -Text -SendLJ : remote control -LjClient : -LjPl : -DrawPL -WebStatus - -LICENCE : CC -Sam Neurohack - -''' - -import math -import redis -from OSC import OSCServer, OSCClient, OSCMessage - -redisIP = '127.0.0.1' -r = redis.StrictRedis(host=redisIP, port=6379, db=0) - -ClientNumber = 0 - -point_list = [] -pl = [[],[],[],[]] - - - -def SendLJ(oscaddress,oscargs=''): - - oscmsg = OSCMessage() - oscmsg.setAddress(oscaddress) - oscmsg.append(oscargs) - - print ("sending OSC message : ",oscmsg) - try: - osclientlj.sendto(oscmsg, (redisIP, 8002)) - oscmsg.clearData() - except: - print ('Connection to LJ refused : died ?') - pass - #time.sleep(0.001 - -def WebStatus(message): - SendLJ("/status", message) - - -ASCII_GRAPHICS = [ - -#implementé - - [(-50,30), (-30,-30), (30,-30), (10,30), (-50,30)], #0 - [(-20,30), (0,-30), (-20,30)], #1 - [(-30,-10), (0,-30), (30,-10), (30,0), (-30,30), (30,30)], #2 - [(-30,-30), (0,-30), (30,-10), (0,0), (30,10), (0,30), (-30,30)], #3 - [(30,10), (-30,10), (0,-30), (0,30)], #4 - [(30,-30), (-30,-30), (-30,0), (0,0), (30,10), (0,30), (-30,30)], #5 - [(30,-30), (0,-30), (-30,-10), (-30,30), (0,30), (30,10), (30,0), (-30,0)], #6 - [(-30,-30), (30,-30), (-30,30)], #7 - [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30), (-30,0), (30,0)], #8 - [(30,0), (-30,0), (-30,-10), (0,-30), (30,-30), (30,10), (0,30), (-30,30)], #9 - -# A implementer - [(-30,10), (30,-10), (30,10), (0,30), (-30,10), (-30,-10), (0,-30), (30,-10)], #: - [(-30,-10), (0,-30), (0,30)], [(-30,30), (30,30)], #; - [(-30,-10), (0,-30), (30,-10), (30,0), (-30,30), (30,30)], #< - [(-30,-30), (0,-30), (30,-10), (0,0), (30,10), (0,30), (-30,30)], #= - [(30,10), (-30,10), (0,-30), (0,30)], #> - [(30,-30), (-30,-30), (-30,0), (0,0), (30,10), (0,30), (-30,30)], #? - [(30,-30), (0,-30), (-30,-10), (-30,30), (0,30), (30,10), (30,0), (-30,0)], #@ - -# Implementé - - - [(-30,30), (-30,-30), (30,-30), (30,30), (30,0), (-30,0)], #A - [(-30,30), (-30,-30), (30,-30), (30,30), (30,0), (-30,0)], #A - [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30), (-30,0), (30,0)], #B - [(30,30), (-30,30), (-30,-30), (30,-30)], #C - [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30)], #D - [(30,30), (-30,30), (-30,-0), (30,0), (-30,0), (-30,-30), (30,-30)], #E - [(-30,30), (-30,-0), (30,0), (-30,0), (-30,-30), (30,-30)], #F - [(0,0), (30,0), (30,30), (-30,30), (-30,-30),(30,-30)], #G - [(-30,-30), (-30,30), (-30,0), (30,0), (30,30), (30,-30)], #H - [(0,30), (0,-30)], #I - [(-30,30), (0,-30), (0,-30), (-30,-30), (30,-30)], #J - [(-30,-30), (-30,30), (-30,0), (30,-30), (-30,0), (30,30)], #K - [(30,30), (-30,30), (-30,-30)], #L - [(-30,30), (-30,-30), (0,0), (30,-30), (30,30)], #M - [(-30,30), (-30,-30), (30,30), (30,-30)], #N - [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30)], #O - [(-30,0), (30,0), (30,-30), (-30,-30), (-30,30)], #P - [(30,30), (30,-30), (-30,-30), (-30,30), (30,30),(35,35)], #Q - [(-30,30), (-30,-30), (30,-30), (30,0), (-30,0), (30,30)], #R - [(30,-30), (-30,-30), (-30,0), (30,0), (30,30), (-30,30)], #S - [(0,30), (0,-30), (-30,-30), (30,-30)], #T - [(-30,-30), (-30,30), (30,30), (30,-30)], #U - [(-30,-30), (0,30), (30,-30)], #V - [(-30,-30), (-30,30), (0,0), (30,30), (30,-30)], #W - [(-30,30), (30,-30)], [(-30,-30), (30,30)], #X - [(0,30), (0,0), (30,-30), (0,0), (-30,-30)], #Y - [(30,30), (-30,30), (30,-30), (-30,-30)], #Z - - # A implementer - - [(-30,-10), (0,-30), (0,30)], [(-30,30), (30,30)], #[ - [(-30,-10), (0,-30), (30,-10), (30,0), (-30,30), (30,30)], #\ - [(-30,-30), (0,-30), (30,-10), (0,0), (30,10), (0,30), (-30,30)], #] - [(30,10), (-30,10), (0,-30), (0,30)], #^ - [(30,-30), (-30,-30), (-30,0), (0,0), (30,10), (0,30), (-30,30)], #_ - [(30,-30), (0,-30), (-30,-10), (-30,30), (0,30), (30,10), (30,0), (-30,0)], #` - - # Implementé - - [(-20,20), (-20,-20), (20,-20), (20,20), (20,0), (-20,0)], #a - [(-20,20), (-20,-20), (20,-20), (20,20), (-20,20), (-20,0), (20,0)], #b - [(20,20), (-20,20), (-20,-20), (20,-20)], #c - [(-20,20), (-20,-20), (20,-20), (20,20), (-20,20)], #d - [(20,20), (-20,20), (-20,-0), (20,0), (-20,0), (-20,-20), (20,-20)], #e - [(-20,20), (-20,-0), (20,0), (-20,0), (-20,-20), (20,-20)], #f - [(0,0), (20,0), (20,20), (-20,20), (-20,-20),(20,-20)], #g - [(-20,-20), (-20,20), (-20,0), (20,0), (20,20), (20,-20)], #H - [(0,20), (0,-20)], #I - [(-20,20), (0,-20), (0,-20), (-20,-20), (20,-20)], #J - [(-20,-20), (-20,20), (-20,0), (20,-20), (-20,0), (20,20)], #K - [(20,20), (-20,20), (-20,-20)], #L - [(-20,20), (-20,-20), (0,0), (20,-20), (20,20)], #M - [(-20,20), (-20,-20), (20,20), (20,-20)], #N - [(-20,20), (-20,-20), (20,-20), (20,20), (-20,20)], #O - [(-20,0), (20,0), (20,-20), (-20,-20), (-20,20)], #P - [(20,20), (20,-20), (-20,-20), (-20,20), (20,20),(25,25)], #Q - [(-20,20), (-20,-20), (20,-20), (20,0), (-20,0), (20,20)], #R - [(20,-20), (-20,-20), (-20,0), (20,0), (20,20), (-20,20)], #S - [(0,20), (0,-20), (-20,-20), (20,-20)], #T - [(-20,-20), (-20,20), (20,20), (20,-20)], #U - [(-20,-20), (0,20), (20,-20)], #V - [(-20,-20), (-20,20), (0,0), (20,20), (20,-20)], #W - [(-20,20), (20,-20)], [(-20,-20), (20,20)], #X - [(0,20), (0,0), (20,-20), (0,0), (-20,-20)], #Y - [(20,20), (-20,20), (20,-20), (-20,-20)], #Z - - [(-2,15), (2,15)] # Point a la place de { -] - -def Config(redisIP,client): - global ClientNumber - - r = redis.StrictRedis(host=redisIP, port=6379, db=0) - ClientNumber = client - #print "client configured",ClientNumber - - -def LjClient(client): - global ClientNumber - - ClientNumber = client - -def LjPl(pl): - global PL - - PL = pl - - -def LineTo(xy, c, PL): - - pl[PL].append((xy + (c,))) - -def Line(xy1, xy2, c, PL): - LineTo(xy1, 0, PL) - LineTo(xy2, c , PL) - - -def PolyLineOneColor(xy_list, c, PL , closed ): - #print "--" - #print "c",c - #print "xy_list",xy_list - #print "--" - xy0 = None - for xy in xy_list: - if xy0 is None: - xy0 = xy - #print "xy0:",xy0 - LineTo(xy0,0, PL) - LineTo(xy0,c, PL) - else: - #print "xy:",xy - LineTo(xy,c, PL) - if closed: - LineTo(xy0,c, PL) - - -# Computing points coordinates for rPolyline function from 3D and around 0,0 to pygame coordinates -def Pointransf(xy, xpos = 0, ypos =0, resize =1, rotx =0, roty =0 , rotz=0): - - x = xy[0] * resize - y = xy[1] * resize - z = 0 - - rad = math.radians(rotx) - cosaX = math.cos(rad) - sinaX = math.sin(rad) - - y2 = y - y = y2 * cosaX - z * sinaX - z = y2 * sinaX + z * cosaX - - rad = math.radians(roty) - cosaY = math.cos(rad) - sinaY = math.sin(rad) - - z2 = z - z = z2 * cosaY - x * sinaY - x = z2 * sinaY + x * cosaY - - rad = math.radians(rotz) - cosZ = math.cos(rad) - sinZ = math.sin(rad) - - x2 = x - x = x2 * cosZ - y * sinZ - y = x2 * sinZ + y * cosZ - - #print xy, (x + xpos,y+ ypos) - return (x + xpos,y+ ypos) - ''' - to understand why it get negative Y - - # 3D to 2D projection - factor = 4 * gstt.cc[22] / ((gstt.cc[21] * 8) + z) - print xy, (x * factor + xpos, - y * factor + ypos ) - return (x * factor + xpos, - y * factor + ypos ) - ''' - -# Send 2D point list around 0,0 with 3D rotation resizing and reposition around xpos ypos -#def rPolyLineOneColor(self, xy_list, c, PL , closed, xpos = 0, ypos =0, resize =1, rotx =0, roty =0 , rotz=0): -def rPolyLineOneColor(xy_list, c, PL , closed, xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0): - xy0 = None - for xy in xy_list: - if xy0 is None: - xy0 = xy - LineTo(Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz),0, PL) - LineTo(Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz),c, PL) - else: - LineTo(Pointransf(xy, xpos, ypos, resize, rotx, roty, rotz),c, PL) - if closed: - LineTo(Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz),c, PL) - - -def LinesPL(PL): - print "Stupido !! your code is to old : use DrawPL() instead of LinesPL()" - DrawPL(PL) - - -def DrawPL(PL): - #print '/pl/0/'+str(PL), str(pl[PL]) - if r.set('/pl/'+str(ClientNumber)+'/'+str(PL), str(pl[PL])) == True: - #print '/pl/'+str(ClientNumber)+'/'+str(PL), str(pl[PL]) - pl[PL] = [] - return True - else: - return False - -def ResetPL(self, PL): - pl[PL] = [] - - - -def DigitsDots(number,color): - dots =[] - for dot in ASCII_GRAPHICS[number]: - #print dot - dots.append((gstt.xy_center[0]+dot[0],gstt.xy_center[1]+dot[1],color)) - #self.point_list.append((xy + (c,))) - return dots - -def CharDots(char,color): - - dots =[] - for dot in ASCII_GRAPHICS[ord(char)-46]: - dots.append((dot[0],dot[1],color)) - return dots - -def Text(message,c, PL, xpos, ypos, resize, rotx, roty, rotz): - - dots =[] - - l = len(message) - i= 0 - #print message - - for ch in message: - - #print "" - # texte centre en x automatiquement selon le nombre de lettres l - x_offset = 26 * (- (0.9*l) + 3*i) - # Digits - if ord(ch)<58: - char_pl_list = ASCII_GRAPHICS[ord(ch) - 48] - else: - char_pl_list = ASCII_GRAPHICS[ord(ch) - 46] - char_draw = [] - #dots.append((char_pl_list[0][0] + x_offset,char_pl_list[0][1],0)) - - for xy in char_pl_list: - char_draw.append((xy[0] + x_offset,xy[1],c)) - i +=1 - #print ch,char_pl_list,char_draw - rPolyLineOneColor(char_draw, c, PL , False, xpos, ypos, resize, rotx, roty, rotz) - #dots.append(char_draw) - - - - \ No newline at end of file diff --git a/plugins/games/ljpong/main.py b/plugins/games/ljpong/main.py index 9a76036..2b91a2f 100755 --- a/plugins/games/ljpong/main.py +++ b/plugins/games/ljpong/main.py @@ -14,6 +14,9 @@ import itertools import sys import os import types +sys.path.append('../../../libs') +import lj + ''' is_py2 = sys.version[0] == '2' @@ -26,7 +29,7 @@ else: import thread import time import random -import lj +import lj23 as lj import entities from controller import setup_controls import argparse @@ -116,13 +119,15 @@ if args.laser: else: plnumber = 0 +entities.plnumber = plnumber + # Redis Computer IP if args.redisIP != None: redisIP = args.redisIP else: redisIP = '127.0.0.1' -lj.Config(redisIP,ljclient) +lj.Config(redisIP,ljclient,"ljpong") def StartPlaying(first_time = False): @@ -209,24 +214,26 @@ OSCRunning = True def OSCljclient(path, tags, args, source): - print("LJ Pong got /ljpong/ljclient with value", args[0]) + print("LJPong got /ljpong/ljclient with value", args[0]) lj.WebStatus("LJPong to virtual "+ str(args[0])) ljclient = args[0] lj.LjClient(ljclient) def OSCpl(path, tags, args, source): + global plnumber print("LJ Pong got /ljpong/pl with value", args[0]) lj.WebStatus("LJPong to pl "+ str(args[0])) - plnumber = args[0] - + plnumber = int(args[0]) + lj.LjPl(plnumber) +''' # /ping def OSCping(path, tags, args, source): print("LJ Pong got /ping") lj.SendLJ("/pong","ljpong") lj.SendLJ("/ljpong/start",1) - +''' def OSC_frame(): # clear timed_out flag @@ -246,10 +253,10 @@ print "at", OSCIP, "port",str(OSCPort) oscserver.handle_timeout = types.MethodType(handle_timeout, oscserver) # OSC callbacks - +lj.addOSCdefaults(oscserver) oscserver.addMsgHandler( "/ljpong/ljclient", OSCljclient ) oscserver.addMsgHandler("/ljpong/pl", OSCpl) -oscserver.addMsgHandler("/ping", OSCping) +#oscserver.addMsgHandler("/ping", lj.OSCping) print "Running..." diff --git a/plugins/laserglyph.py b/plugins/laserglyph.py index 97efd56..ad0085c 100644 --- a/plugins/laserglyph.py +++ b/plugins/laserglyph.py @@ -1,27 +1,55 @@ -# coding=UTF-8 +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# -*- mode: Python -*- + ''' + +Laserglyph +v0.1.0 + Anaglyphed rotating cube (for red and green glasses) This client uses the drawing functions (polyline) provided by LJ in lj.py -You must check in lj.py if the redis server IP is correct. LICENCE : CC -''' +by Sam Neurohack + + +''' +import sys +import os +print() +ljpath = r'%s' % os.getcwd().replace('\\','/') + +# import from shell + +sys.path.append(ljpath +'/../libs/') + +#import from LJ +sys.path.append(ljpath +'/libs/') +print (ljpath+'/../libs/') + +import lj23 as lj + +from OSC3 import OSCServer, OSCClient, OSCMessage import redis -import lj3 import math import time import argparse +''' from osc4py3.as_eventloop import * from osc4py3 import oscbuildparse #from osc4py3 import oscmethod as osm from osc4py3.oscmethod import * +''' OSCinPort = 8004 -myIP = "192.168.2.52" +oscrun = True +# myIP = "127.0.0.1" +PL = 0 print ("") print ("Arguments parsing if needed...") @@ -59,7 +87,7 @@ if args.myIP != None: else: myIP = '127.0.0.1' -print("redisIP",redisIP) +print("myIP",myIP) if args.verbose: debug = args.verbose @@ -67,8 +95,12 @@ else: debug = 0 -lj3.Config(redisIP,ljclient) +lj.Config(redisIP,ljclient,"glyph") +white = lj.rgb2int(255,255,255) +red = lj.rgb2int(255,0,0) +blue = lj.rgb2int(0,0,255) +green = lj.rgb2int(0,255,0) width = 800 height = 600 @@ -87,7 +119,7 @@ observer_altitude = 30000 #observer_altitude = 10000 # elevation = z coordinate # 0.0, -2000 pop out -map_plane_altitude = 0.0 +map_plane_altitude = 0.0 # Cube coordinates # Define the vertices that compose each of the 6 faces. @@ -101,9 +133,29 @@ vertices = [ ( 1.0,- 1.0, 1.0), (- 1.0,- 1.0, 1.0) ] -faces = [(0,1,2,3),(0,4,5,1),(1,5,6,2),(2,3,7,6),(6,5,4,7),(7,3,0,4)] +#faces = [(0,1,2,3),(0,4,5,1),(1,5,6,2),(2,3,7,6),(6,5,4,7),(7,3,0,4)] +faces = [(0,1,2,3),(0,4,5,1),(1,5,6,2),(2,3,7,6),(7,3,0,4),(7,3,0,4)] +# name, intensity, active, xy, color, red, green, blue, PL , closed): +Leftcube = lj.FixedObject('Leftcube', True, 255, [], red, 255, 0, 0, PL , True) +Rightcube = lj.FixedObject('Rightcube', True, 255, [], green, 0, 255, 0, PL , True) +# 'Destination' for each PL +# name, number, active, PL , scene, laser +# PL 0 +Dest0 = lj.DestObject('0', 0, True, 0 , 0, 0) +Dest1 = lj.DestObject('1', 1, True, 0 , 1, 1) +''' +viewgen3Lasers = [True,False,False,False] +# Add here, one by one, as much destination as you want for each PL. +# LJ and OSC can remotely add/delete destinations here. + +lj.Dests = { + "0": {"PL": 0, "scene": 0, "laser": 0}, + "1": {"PL": 0, "scene": 1, "laser": 1} + } + +''' def LeftShift(elevation): @@ -115,29 +167,36 @@ def RightShift(elevation): diff = map_plane_altitude - elevation return (1 - nadir) * eye_spacing * diff / (observer_altitude - elevation) -# If you want to use rgb for color : -def rgb2int(r,g,b): - return int('0x%02x%02x%02x' % (r,g,b),0) - -def OSCljclient(value): +# OSC +# - print("Glyph got /glyph/ljclient with value", value) - lj3.WebStatus("Glyph to virtual "+ str(value)) - ljclient = value - lj3.LjClient(ljclient) +oscserver = OSCServer( (myIP, OSCinPort) ) +oscserver.timeout = 0 +#oscrun = True -def OSCpl(value): +# this method of reporting timeouts only works by convention +# that before calling handle_request() field .timed_out is +# set to False +def handle_timeout(self): + self.timed_out = True - print("Glyph got /glyph/pl with value", value) - lj3.WebStatus("Glyph to pl "+ str(value)) - lj3.LjPl(value) +# funny python's way to add a method to an instance of a class +import types +oscserver.handle_timeout = types.MethodType(handle_timeout, oscserver) + + +# OSC callbacks + +# /viewgen/ljclient +def OSCljclient(path, tags, args, source): + + print("Got /viewgen/ljclient with value", args[0]) + lj.WebStatus("viewgen to virtual "+ str(args[0])) + ljclient = args[0] + lj.LjClient(ljclient) -# /pose/ping value -def OSCping(value): - lj3.OSCping("glyph") -''' def Proj(x,y,z,angleX,angleY,angleZ): rad = angleX * math.pi / 180 @@ -174,18 +233,28 @@ def Run(): Left = [] Right = [] counter =0 - lj3.WebStatus("LaserGlyph") + lj.WebStatus("LaserGlyph") + lj.SendLJ("/glyph/start 1") # OSC Server callbacks - print("Starting OSC at",myIP," port",OSCinPort,"...") + print("Starting OSC server at",myIP," port",OSCinPort,"...") + ''' osc_startup() osc_udp_server(myIP, OSCinPort, "InPort") - osc_method("/ping*", OSCping) + osc_method("/ping", lj.OSCping) + osc_method("/quit*", quit) osc_method("/glyph/ljclient", OSCljclient) + ''' + oscserver.addMsgHandler( "/glyph/ljclient", OSCljclient ) + + # Add OSC generic plugins commands : 'default", /ping, /quit, /pluginame/obj, /pluginame/var, /pluginame/adddest, /pluginame/deldest + lj.addOSCdefaults(oscserver) try: - while 1: + while lj.oscrun: + + lj.OSCframe() Left = [] Right = [] @@ -216,24 +285,31 @@ def Run(): # Drawing step, 2 possibilities # Red and Green drawn by laser 0 - lj3.PolyLineOneColor(Left, c = red, PL = 0, closed = True) - lj3.PolyLineOneColor(Right, c = green, PL = 0, closed = True) - lj3.DrawPL(0) + #lj.PolyLineOneColor(Left, c = red, PL = PL, closed = True) + #lj.PolyLineOneColor(Right, c = green, PL = PL, closed = True) + + lj.PolyLineOneColor(Left, c = Leftcube.color , PL = Leftcube.PL, closed = Leftcube.closed) + lj.PolyLineOneColor(Right, c = Rightcube.color , PL = Rightcube.PL, closed = Rightcube.closed) + #print(len(Left)) + + #lj.DrawPL(PL) + #print(Dest0.name, Dest1.name) + lj.DrawDests() ''' # Red on laser 1 and green on laser 2 - lj3.PolyLineOneColor(Left, c = red, PL = 1, closed = True) - lj3.PolyLineOneColor(Right, c = green, PL = 2, closed = True) - lj3.DrawPL(1) - lj3.DrawPL(2) + lj.PolyLineOneColor(Left, c = red, PL = 1, closed = True) + lj.PolyLineOneColor(Right, c = green, PL = 2, closed = True) + lj.DrawPL(1) + lj.DrawPL(2) ''' time.sleep(0.1) counter += 1 - if counter >360: - counter =0 + if counter > 360: + counter = 0 except KeyboardInterrupt: pass @@ -242,18 +318,7 @@ def Run(): finally: - lj3.WebStatus("Glyph Exit") - print("Stopping OSC...") - lj3.OSCstop() - pass - - print ("LaserGlyph Stopped.") - - -white = rgb2int(255,255,255) -red = rgb2int(255,0,0) -blue = rgb2int(0,0,255) -green = rgb2int(0,255,0) + lj.ClosePlugin() Run() diff --git a/plugins/livewords.py b/plugins/livewords.py index 5f7771b..8f0af02 100644 --- a/plugins/livewords.py +++ b/plugins/livewords.py @@ -6,9 +6,11 @@ LICENCE : CC ''' import redis -import lj3 + import sys,time import argparse +sys.path.append('../libs') +import lj3 from osc4py3.as_eventloop import * from osc4py3 import oscbuildparse @@ -20,11 +22,12 @@ myIP = "127.0.0.1" duration = 300 OSCinPort = 8006 +oscrun = True -Word0 = "LASER" -Word1 = "LASER" -Word2 = "LASER" -Word3 = "LASER" +Word0 = "ONE" +Word1 = "TWO" +Word2 = "THREE" +Word3 = "FOUR" ''' is_py2 = sys.version[0] == '2' @@ -67,7 +70,7 @@ else: debug = 0 -lj3.Config(redisIP,ljclient) +lj3.Config(redisIP,ljclient,"words") #r = redis.StrictRedis(host=redisIP, port=6379, db=0) @@ -108,17 +111,15 @@ def OSCljclient(value): -# /ping -def OSCping(): - lj3.OSCping("words") - lj3.SendLJ("words/text/0",Word0) - lj3.SendLJ("words/text/1",Word1) +# /quit dummyvalue +def quit(value): + # don't do this at home (or it'll quit blender) + global oscrun -# /quit -def OSCquit(): - - lj3.OSCquit("words") + oscrun = False + print("Stopped by /quit.") + lj3.ClosePlugin() def Run(): @@ -131,9 +132,9 @@ def Run(): osc_method("/words/text/1*", OSCword1) osc_method("/words/text/2*", OSCword2) osc_method("/words/text/3*", OSCword3) - osc_method("/ping*", OSCping) osc_method("/words/ljclient*", OSCljclient) - osc_method("/quit", OSCquit) + osc_method("/ping", lj3.OSCping) + osc_method("/quit*", quit) color = lj3.rgb2int(255,255,255) lj3.WebStatus("Loading Words...") @@ -145,7 +146,7 @@ def Run(): try: - while 1: + while oscrun: lj3.OSCframe() @@ -170,11 +171,8 @@ def Run(): finally: - lj3.WebStatus("Words Exit") - print("Stopping Words OSC...") - lj3.OSCstop() + lj3.ClosePlugin() - print ("Words Stopped.") Run() diff --git a/plugins/livewords3.py b/plugins/livewords3.py new file mode 100644 index 0000000..d5ded12 --- /dev/null +++ b/plugins/livewords3.py @@ -0,0 +1,241 @@ +# coding=UTF-8 + +''' +Live words on different lasers +LICENCE : CC +''' + +import redis + +import sys,time +import argparse + +import os +ljpath = r'%s' % os.getcwd().replace('\\','/') +# import from shell +sys.path.append('../libs') + +#import from LJ +sys.path.append(ljpath +'/libs/') +#print (ljpath+'/libs') +import lj23 as lj + +is_py2 = sys.version[0] == '2' +if is_py2: + from OSC import OSCServer, OSCClient, OSCMessage + print ("Importing lj23 and OSC from libs...") +else: + from OSC3 import OSCServer, OSCClient, OSCMessage + print ("Importing lj23 and OSC3 from libs...") + +myIP = "127.0.0.1" + +duration = 300 + +OSCinPort = 8006 +oscrun = True + +Word0 = "BRAINFUCK" +Word1 = "D" +Word2 = "CAPTCHA" +Word3 = "D" + + +''' +is_py2 = sys.version[0] == '2' +if is_py2: + from Queue import Queue +else: + from queue import Queue +''' +print ("Words is checking arguments parsing if needed...") +argsparser = argparse.ArgumentParser(description="Text Cycling for LJ") +argsparser.add_argument("-r","--redisIP",help="IP of the Redis server used by LJ (127.0.0.1 by default) ",type=str) +argsparser.add_argument("-m","--myIP",help="Local IP (127.0.0.1 by default) ",type=str) +argsparser.add_argument("-c","--client",help="LJ client number (0 by default)",type=int) +argsparser.add_argument("-v","--verbose",help="Verbosity level (0 by default)",type=int) + +args = argsparser.parse_args() + + +if args.client: + ljclient = args.client +else: + ljclient = 0 + +# Redis Computer IP +if args.redisIP != None: + redisIP = args.redisIP +else: + redisIP = '127.0.0.1' + +# myIP +if args.myIP != None: + myIP = args.myIP +else: + myIP = '127.0.0.1' + + +if args.verbose: + debug = args.verbose +else: + debug = 0 + + +lj.Config(redisIP,ljclient,"words") +#r = redis.StrictRedis(host=redisIP, port=6379, db=0) + +# 'Destination' for each PL +# name, number, active, PL , scene, laser +# PL 0 +Dest0 = lj.DestObject('0', 0, True, 0, 0, 0) +# PL 1 +Dest1 = lj.DestObject('1', 1, True, 1, 0, 1) +# PL 2 +Dest2 = lj.DestObject('2', 2, True, 2, 0, 2) +# PL 3 +Dest3 = lj.DestObject('3', 3, True, 3, 0, 3) + +# +# OSC +# + +oscserver = OSCServer( (myIP, OSCinPort) ) +oscserver.timeout = 0 +#oscrun = True + +# this method of reporting timeouts only works by convention +# that before calling handle_request() field .timed_out is +# set to False +def handle_timeout(self): + self.timed_out = True + +# funny python's way to add a method to an instance of a class +import types +oscserver.handle_timeout = types.MethodType(handle_timeout, oscserver) + + +# RAW OSC Frame available ? +def OSCframe(): + # clear timed_out flag + #print "oscframe" + oscserver.timed_out = False + # handle all pending requests then return + while not oscserver.timed_out: + oscserver.handle_request() + + +# Stop osc server +def OSCstop(): + + oscserver.close() + + + +def OSCword0(value): + global Word0 + + # Will receive message address, and message data flattened in s, x, y + print("Words 0 got /words/text/0 with value", value) + Word0 = value + +def OSCword1(value): + global Word1 + + # Will receive message address, and message data flattened in s, x, y + print("Words 1 got /words/text/1 with value", value) + Word1 = value + +def OSCword2(value): + global Word2 + + # Will receive message address, and message data flattened in s, x, y + print("Words 2 got /words/text/2 with value", value) + Word2 = value + +def OSCword3(value): + global Word3 + + # Will receive message address, and message data flattened in s, x, y + print("Words 3 got /words/text/3 with value", value) + Word3 = value + +def OSCljclient(value): + # Will receive message address, and message data flattened in s, x, y + print("Words got /words/ljclient with value", value) + lj.WebStatus("Words to virtual "+ str(value)) + ljclient = value + lj.LjClient(ljclient) + + + + +# /quit dummyvalue +def quit(value): + # don't do this at home (or it'll quit blender) + global oscrun + + oscrun = False + print("Stopped by /quit.") + lj.ClosePlugin() + + +def Run(): + + # OSC Server callbacks + print("Words starting its OSC server at", myIP, "port",OSCinPort,"...") + #oscserver.addMsgHandler( "default", lj.OSChandler ) + #oscserver.addMsgHandler( "/words/ljclient", OSCljclient ) + oscserver.addMsgHandler( "/words/text/0", OSCword0) + oscserver.addMsgHandler( "/words/text/1", OSCword1) + oscserver.addMsgHandler( "/words/text/2", OSCword2) + oscserver.addMsgHandler( "/words/text/3", OSCword3) + #oscserver.addMsgHandler( "/ping", lj.OSCping) + #oscserver.addMsgHandler( "/quit", lj.OSCquit) + # Add OSC generic plugins commands : 'default", /ping, /quit, /pluginame/obj, /pluginame/var, /pluginame/adddest, /pluginame/deldest + lj.addOSCdefaults(oscserver) + + color = lj.rgb2int(0,255,0) + lj.WebStatus("Loading Words...") + lj.WebStatus("Words ready.") + lj.SendLJ("/words/start 1") + + lj.SendLJ("words/text/0",Word0) + lj.SendLJ("words/text/1",Word1) + + try: + + while lj.oscrun: + + OSCframe() + + lj.Text(Word0, color, PL = 0, xpos = 300, ypos = 300, resize = 1, rotx =0, roty =0 , rotz=0) + #lj.DrawPL(0) + + lj.Text(Word1, color, PL = 1, xpos = 300, ypos = 300, resize = 1, rotx =0, roty =0 , rotz=0) + #lj.DrawPL(1) + + lj.Text(Word2, color, PL = 2, xpos = 300, ypos = 300, resize = 1, rotx =0, roty =0 , rotz=0) + #lj.DrawPL(2) + + lj.Text(Word3, color, PL = 3, xpos = 300, ypos = 300, resize = 1, rotx =0, roty =0 , rotz=0) + #lj.DrawPL(3) + + lj.DrawDests() + time.sleep(0.01) + + except KeyboardInterrupt: + pass + + # Gently stop on CTRL C + + finally: + + lj.ClosePlugin() + OSCstop() + + + +Run() + + diff --git a/plugins/lj3.py b/plugins/lj3.py deleted file mode 100644 index a5b8ef4..0000000 --- a/plugins/lj3.py +++ /dev/null @@ -1,645 +0,0 @@ -# coding=UTF-8 - -''' - -lj3 v0.8.1 - -Some LJ functions useful for python clients (was framy.py) - -OSC functions commented, waiting working on OSC in python3 - -Config(redisIP, client number) -PolyLineOneColor -rPolyLineOneColor - -Text(word, color, PL, xpos, ypos, resize, rotx, roty, rotz) : Display a word -SendLJ(adress,message) : LJ remote control. See commands.py -WebStatus(message) : display message on webui -DrawPL(point list number) : once you stacked all wanted elements, like 2 polylines, send them to lasers. -rgb2int(r,g,b) -LjClient(client): Change Client number in redis keys -LjPl(pl): Change pl number in redis keys = laser target. - - -OSCstart(): Start the OSC system. -OSCframe(): Handle incoming OSC message. Calling the right callback -OSCstop(): Properly close the OSC system -OSCping(value): Answer to LJ pings by sending /pong value -OSCquit(name): Exit calling script using name in terminal - -setup_controls(joystick) - -XboxController : getLeftHori, getLeftVert, getRightHori, getRightVert, getLeftTrigger, getRightTrigger -Ps3Controller : getLeftHori, getLeftVert, getRightHori, getRightVert, getLeftTrigger, getRightTrigger, getUp, getDown, getLeft, getRight, getFire1, getFire2(self): -MySaitekController : getLeftHori,getLeftVert, getRightHori,getRightVert, getLeftTrigger,getRightTrigger -MyThrustController : getLeftHori, getLeftVert, getRightHori, getRightVert, getLeftTrigger, getRightTrigger -CSLController : getLeftHori,getLeftVert,getRightHori, getRightVert,getLeftTrigger,getRightTrigger,getFire1,getFire2 -my USB Joystick : getUp,getDown,getLeft,getRight,etLeftTrigger, getRightTrigger,getFire1, getFire2 - - -LICENCE : CC -Sam Neurohack - -''' - -import math -import redis - -# Import needed modules from osc4py3 -from osc4py3.as_eventloop import * -from osc4py3 import oscbuildparse -#from osc4py3 import oscmethod as osm -from osc4py3.oscmethod import * - - -#redisIP = '127.0.0.1' -#r = redis.StrictRedis(host=redisIP, port=6379, db=0) - -ClientNumber = 0 - -point_list = [] -pl = [[],[],[],[]] - -# -# OSC interaction with LJ -# - -def OSCstart(): - # Start the system. - osc_startup() - #osc_udp_client(redisIP, 8002, "LJ 8002") - -def OSCframe(): - #print("OSCprocess") - osc_process() - -# Properly close the system. Todo -def OSCstop(): - osc_terminate() - - -def SendLJ(oscaddress,oscargs=''): - - try: - msg = oscbuildparse.OSCMessage(oscaddress, None, [oscargs]) - # print(msg) - osc_send(msg, "LJ 8002") - OSCframe() - - except: - print ('Connection to LJ refused : died ?') - pass - -def WebStatus(message): - SendLJ("/status", message) - - -# Answer to LJ pings with /pong pluginame -def OSCping(value): - SendLJ("/pong",value) - - -# /quit -def OSCquit(name): - - WebStatus(name + " quit.") - print("Stopping OSC...") - - OSCstop() - sys.exit() - -''' -def handlerfunction(s, x, y): - # Will receive message data unpacked in s, x, y - pass - -def handlerfunction2(address, s, x, y): - # Will receive message address, and message data flattened in s, x, y - pass - -# Make server channels to receive packets. -osc_udp_server("127.0.0.1", 3721, "localhost") -osc_udp_server("0.0.0.0", 3724, "anotherserver") -''' - - - -ASCII_GRAPHICS = [ - -# caracteres corrects - - [(-50,30), (-30,-30), (30,-30), (10,30), (-50,30)], #0 - [(-20,30), (0,-30), (-20,30)], #1 - [(-30,-10), (0,-30), (30,-10), (30,0), (-30,30), (30,30)], #2 - [(-30,-30), (0,-30), (30,-10), (0,0), (30,10), (0,30), (-30,30)], #3 - [(30,10), (-30,10), (0,-30), (0,30)], #4 - [(30,-30), (-30,-30), (-30,0), (0,0), (30,10), (0,30), (-30,30)], #5 - [(30,-30), (0,-30), (-30,-10), (-30,30), (0,30), (30,10), (30,0), (-30,0)], #6 - [(-30,-30), (30,-30), (-30,30)], #7 - [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30), (-30,0), (30,0)], #8 - [(30,0), (-30,0), (-30,-10), (0,-30), (30,-30), (30,10), (0,30), (-30,30)], #9 - -# caracteres a implementer - [(-30,10), (30,-10), (30,10), (0,30), (-30,10), (-30,-10), (0,-30), (30,-10)], #: - [(-30,-10), (0,-30), (0,30)], [(-30,30), (30,30)], #; - [(-30,-10), (0,-30), (30,-10), (30,0), (-30,30), (30,30)], #< - [(-30,-30), (0,-30), (30,-10), (0,0), (30,10), (0,30), (-30,30)], #= - [(30,10), (-30,10), (0,-30), (0,30)], #> - [(30,-30), (-30,-30), (-30,0), (0,0), (30,10), (0,30), (-30,30)], #? - [(30,-30), (0,-30), (-30,-10), (-30,30), (0,30), (30,10), (30,0), (-30,0)], #@ - -# Caracteres corrects - - - [(-30,30), (-30,-30), (30,-30), (30,30), (30,0), (-30,0)], #A - [(-30,30), (-30,-30), (30,-30), (30,30), (30,0), (-30,0)], #A - [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30), (-30,0), (30,0)], #B - [(30,30), (-30,30), (-30,-30), (30,-30)], #C - [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30)], #D - [(30,30), (-30,30), (-30,-0), (30,0), (-30,0), (-30,-30), (30,-30)], #E - [(-30,30), (-30,-0), (30,0), (-30,0), (-30,-30), (30,-30)], #F - [(0,0), (30,0), (30,30), (-30,30), (-30,-30),(30,-30)], #G - [(-30,-30), (-30,30), (-30,0), (30,0), (30,30), (30,-30)], #H - [(0,30), (0,-30)], #I - [(-30,30), (0,-30), (0,-30), (-30,-30), (30,-30)], #J - [(-30,-30), (-30,30), (-30,0), (30,-30), (-30,0), (30,30)], #K - [(30,30), (-30,30), (-30,-30)], #L - [(-30,30), (-30,-30), (0,0), (30,-30), (30,30)], #M - [(-30,30), (-30,-30), (30,30), (30,-30)], #N - [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30)], #O - [(-30,0), (30,0), (30,-30), (-30,-30), (-30,30)], #P - [(30,30), (30,-30), (-30,-30), (-30,30), (30,30),(35,35)], #Q - [(-30,30), (-30,-30), (30,-30), (30,0), (-30,0), (30,30)], #R - [(30,-30), (-30,-30), (-30,0), (30,0), (30,30), (-30,30)], #S - [(0,30), (0,-30), (-30,-30), (30,-30)], #T - [(-30,-30), (-30,30), (30,30), (30,-30)], #U - [(-30,-30), (0,30), (30,-30)], #V - [(-30,-30), (-30,30), (0,0), (30,30), (30,-30)], #W - [(-30,30), (30,-30)], [(-30,-30), (30,30)], #X - [(0,30), (0,0), (30,-30), (0,0), (-30,-30)], #Y - [(30,30), (-30,30), (30,-30), (-30,-30)], #Z - - # A implementer - - [(-30,-10), (0,-30), (0,30)], [(-30,30), (30,30)], #[ - [(-30,-10), (0,-30), (30,-10), (30,0), (-30,30), (30,30)], #\ - [(-30,-30), (0,-30), (30,-10), (0,0), (30,10), (0,30), (-30,30)], #] - [(30,10), (-30,10), (0,-30), (0,30)], #^ - [(30,-30), (-30,-30), (-30,0), (0,0), (30,10), (0,30), (-30,30)], #_ - [(30,-30), (0,-30), (-30,-10), (-30,30), (0,30), (30,10), (30,0), (-30,0)], #` - - # Implementé - - [(-20,20), (-20,-20), (20,-20), (20,20), (20,0), (-20,0)], #a - [(-20,20), (-20,-20), (20,-20), (20,20), (-20,20), (-20,0), (20,0)], #b - [(20,20), (-20,20), (-20,-20), (20,-20)], #c - [(-20,20), (-20,-20), (20,-20), (20,20), (-20,20)], #d - [(20,20), (-20,20), (-20,-0), (20,0), (-20,0), (-20,-20), (20,-20)], #e - [(-20,20), (-20,-0), (20,0), (-20,0), (-20,-20), (20,-20)], #f - [(0,0), (20,0), (20,20), (-20,20), (-20,-20),(20,-20)], #g - [(-20,-20), (-20,20), (-20,0), (20,0), (20,20), (20,-20)], #H - [(0,20), (0,-20)], #I - [(-20,20), (0,-20), (0,-20), (-20,-20), (20,-20)], #J - [(-20,-20), (-20,20), (-20,0), (20,-20), (-20,0), (20,20)], #K - [(20,20), (-20,20), (-20,-20)], #L - [(-20,20), (-20,-20), (0,0), (20,-20), (20,20)], #M - [(-20,20), (-20,-20), (20,20), (20,-20)], #N - [(-20,20), (-20,-20), (20,-20), (20,20), (-20,20)], #O - [(-20,0), (20,0), (20,-20), (-20,-20), (-20,20)], #P - [(20,20), (20,-20), (-20,-20), (-20,20), (20,20),(25,25)], #Q - [(-20,20), (-20,-20), (20,-20), (20,0), (-20,0), (20,20)], #R - [(20,-20), (-20,-20), (-20,0), (20,0), (20,20), (-20,20)], #S - [(0,20), (0,-20), (-20,-20), (20,-20)], #T - [(-20,-20), (-20,20), (20,20), (20,-20)], #U - [(-20,-20), (0,20), (20,-20)], #V - [(-20,-20), (-20,20), (0,0), (20,20), (20,-20)], #W - [(-20,20), (20,-20)], [(-20,-20), (20,20)], #X - [(0,20), (0,0), (20,-20), (0,0), (-20,-20)], #Y - [(20,20), (-20,20), (20,-20), (-20,-20)], #Z - - [(-2,15), (2,15)] # Point a la place de { -] - -def rgb2int(r,g,b): - return int('0x%02x%02x%02x' % (r,g,b),0) - - -def Config(redisIP,client): - global ClientNumber, r - - r = redis.StrictRedis(host=redisIP, port=6379, db=0) - ClientNumber = client - osc_udp_client(redisIP, 8002, "LJ 8002") - return r - - -def LjClient(client): - global ClientNumber - - ClientNumber = client - -def LjPl(pl): - global PL - - PL = pl - - -def LineTo(xy, c, PL): - - pl[PL].append((xy + (c,))) - -def Line(xy1, xy2, c, PL): - LineTo(xy1, 0, PL) - LineTo(xy2, c , PL) - - -def PolyLineOneColor(xy_list, c, PL , closed ): - #print "--" - #print "c",c - #print "xy_list",xy_list - #print "--" - xy0 = None - for xy in xy_list: - if xy0 is None: - xy0 = xy - #print "xy0:",xy0 - LineTo(xy0,0, PL) - LineTo(xy0,c, PL) - else: - #print "xy:",xy - LineTo(xy,c, PL) - if closed: - LineTo(xy0,c, PL) - - -# Computing points coordinates for rPolyline function from 3D and around 0,0 to pygame coordinates -def Pointransf(xy, xpos = 0, ypos =0, resize =1, rotx =0, roty =0 , rotz=0): - - x = xy[0] * resize - y = xy[1] * resize - z = 0 - - rad = rotx * math.pi / 180 - cosaX = math.cos(rad) - sinaX = math.sin(rad) - - y2 = y - y = y2 * cosaX - z * sinaX - z = y2 * sinaX + z * cosaX - - rad = roty * math.pi / 180 - cosaY = math.cos(rad) - sinaY = math.sin(rad) - - z2 = z - z = z2 * cosaY - x * sinaY - x = z2 * sinaY + x * cosaY - - rad = rotz * math.pi / 180 - cosZ = math.cos(rad) - sinZ = math.sin(rad) - - x2 = x - x = x2 * cosZ - y * sinZ - y = x2 * sinZ + y * cosZ - - #print xy, (x + xpos,y+ ypos) - return (x + xpos,y+ ypos) - ''' - to understand why it get negative Y - - # 3D to 2D projection - factor = 4 * gstt.cc[22] / ((gstt.cc[21] * 8) + z) - print xy, (x * factor + xpos, - y * factor + ypos ) - return (x * factor + xpos, - y * factor + ypos ) - ''' - -# Send 2D point list around 0,0 with 3D rotation resizing and reposition around xpos ypos -#def rPolyLineOneColor(self, xy_list, c, PL , closed, xpos = 0, ypos =0, resize =1, rotx =0, roty =0 , rotz=0): -def rPolyLineOneColor(xy_list, c, PL , closed, xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0): - xy0 = None - for xy in xy_list: - if xy0 is None: - xy0 = xy - LineTo(Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz),0, PL) - LineTo(Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz),c, PL) - else: - LineTo(Pointransf(xy, xpos, ypos, resize, rotx, roty, rotz),c, PL) - if closed: - LineTo(Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz),c, PL) - - -def LinesPL(PL): - print ("Stupido !! your code is to old : use DrawPL() instead of LinesPL()") - DrawPL(PL) - -def DrawPL(PL): - #print '/pl/0/'+str(PL), str(pl[PL]) - if r.set('/pl/'+str(ClientNumber)+'/'+str(PL), str(pl[PL])) == True: - pl[PL] = [] - return True - else: - return False - -def ResetPL(self, PL): - pl[PL] = [] - - -def DigitsDots(number,color): - dots =[] - for dot in ASCII_GRAPHICS[number]: - #print dot - dots.append((gstt.xy_center[0]+dot[0],gstt.xy_center[1]+dot[1],color)) - #self.point_list.append((xy + (c,))) - return dots - - -def CharDots(char,color): - - dots =[] - for dot in ASCII_GRAPHICS[ord(char)-46]: - dots.append((dot[0],dot[1],color)) - return dots - - -def Text(message,c, PL, xpos, ypos, resize, rotx, roty, rotz): - - dots =[] - - l = len(message) - i= 0 - #print message - - for ch in message: - - #print "" - # texte centre en x automatiquement selon le nombre de lettres l - x_offset = 26 * (- (0.9*l) + 3*i) - #print i,x_offset - # if digit - if ord(ch)<58: - char_pl_list = ASCII_GRAPHICS[ord(ch) - 48] - else: - char_pl_list = ASCII_GRAPHICS[ord(ch) - 46 ] - - char_draw = [] - #dots.append((char_pl_list[0][0] + x_offset,char_pl_list[0][1],0)) - - for xy in char_pl_list: - char_draw.append((xy[0] + x_offset,xy[1],c)) - i +=1 - #print ch,char_pl_list,char_draw - rPolyLineOneColor(char_draw, c, PL , False, xpos, ypos, resize, rotx, roty, rotz) - #print ("laser",PL,"message",message) - #dots.append(char_draw) - - - -import re - -def setup_controls(joystick): - """ - Joystick wrapper. - """ - if re.search('playstation', joystick.get_name(), re.I): - return Ps3Controller(joystick) - - elif re.search('X-box', joystick.get_name(), re.I): - return XboxController(joystick) - - elif re.search('Saitek', joystick.get_name(), re.I): - return MySaitekController(joystick) - - elif re.search('Thrustmaster dual analog 3.2', joystick.get_name(), re.I): - return MyThrustController(joystick) - - elif re.search('2n1 USB', joystick.get_name(), re.I): - return CSLController(joystick) - - elif re.search('Joystick', joystick.get_name(), re.I): - return USBController(joystick) - - return Controller(joystick) - -class Controller(object): - - def __init__(self, joystick): - """Pass a PyGame joystick instance.""" - self.js = joystick - - def getLeftHori(self): - return self.js.get_axis(2) - - def getLeftVert(self): - return self.js.get_axis(3) - - def getRightHori(self): - return self.js.get_axis(0) - - def getRightVert(self): - return self.js.get_axis(1) - - def getLeftTrigger(self): - return self.js.get_button(9) - - def getRightTrigger(self): - return self.js.get_button(2) - -class XboxController(Controller): - - def __init__(self, joystick): - super(XboxController, self).__init__(joystick) - - def getLeftHori(self): - return self.js.get_axis(0) - - def getLeftVert(self): - return self.js.get_axis(1) - - def getRightHori(self): - return self.js.get_axis(3) - - def getRightVert(self): - return self.js.get_axis(4) - - def getLeftTrigger(self): - return self.js.get_axis(2) - - def getRightTrigger(self): - return self.js.get_button(11) - -class Ps3Controller(Controller): - -#up 4 _DOWN 6 left 7 right 5 croix 14 rond 13 triangle 12 - - def __init__(self, joystick): - super(Ps3Controller, self).__init__(joystick) - - def getLeftHori(self): - return self.js.get_axis(0) - - def getLeftVert(self): - return self.js.get_axis(1) - - def getRightHori(self): - return self.js.get_axis(2) - - def getRightVert(self): - return self.js.get_axis(3) - - def getLeftTrigger(self): - # TODO: Verify - return self.js.get_button(8) - - def getRightTrigger(self): - # TODO: Verify - return self.js.get_button(9) - - def getUp(self): - return self.js.get_button(4) - - def getDown(self): - return self.js.get_button(6) - - def getLeft(self): - return self.js.get_button(7) - - def getRight(self): - return self.js.get_button(5) - - def getFire1(self): - return self.js.get_button(14) - - def getFire2(self): - return self.js.get_button(13) - - -class MySaitekController(Controller): - - def __init__(self, joystick): - super(MySaitekController, self).__init__(joystick) - - def getLeftHori(self): - return self.js.get_axis(0) - - def getLeftVert(self): - return self.js.get_axis(1) - - def getRightHori(self): - return self.js.get_axis(3) - - def getRightVert(self): - return self.js.get_axis(2) - - def getLeftTrigger(self): - return self.js.get_button(6) - - def getRightTrigger(self): - return self.js.get_button(7) - -class MyThrustController(Controller): - - def __init__(self, joystick): - super(MyThrustController, self).__init__(joystick) - - def getLeftHori(self): - return self.js.get_axis(0) - - def getLeftVert(self): - return self.js.get_axis(1) - - def getRightHori(self): - return self.js.get_axis(2) - - def getRightVert(self): - return self.js.get_axis(3) - - def getLeftTrigger(self): - return self.js.get_button(5) - - def getRightTrigger(self): - return self.js.get_button(7) - - -class CSLController(Controller): - - def __init__(self, joystick): - super(CSLController, self).__init__(joystick) - - def getLeftHori(self): - return self.js.get_axis(2) - - def getLeftVert(self): - return self.js.get_axis(3) - - def getRightHori(self): - return self.js.get_axis(0) - - def getRightVert(self): - return self.js.get_axis(1) - - def getLeftTrigger(self): - return self.js.get_button(6) - - def getRightTrigger(self): - return self.js.get_button(7) - - def getFire1(self): - return self.js.get_button(2) - - def getFire2(self): - return self.js.get_button(1) - -class USBController(Controller): - - -# my USB Joystick -#up axis 0 -1 DOWN axis 0 1 left axis 1 1 right axis 1 -1 bouton gauche 10 bouton droite 9 - - def __init__(self, joystick): - super(USBController, self).__init__(joystick) - - - def getUp(self): - if self.js.get_axis(0) == -1: - return 1 - else: - return 0 - - def getDown(self): - if self.js.get_axis(0) > 0.9: - return 1 - else: - return 0 - - def getLeft(self): - if self.js.get_axis(1) == 1: - return 1 - else: - return 0 - - def getRight(self): - if self.js.get_axis(1) == -1: - return 1 - else: - return 0 - - def getLeftTrigger(self): - return self.js.get_button(10) - - def getRightTrigger(self): - return self.js.get_button(9) - - def getFire1(self): - if self.js.get_button(10) == 1: - print ("fire 1") - return self.js.get_button(10) - - def getFire2(self): - if self.js.get_button(9) == 1: - print ("fire 2") - return self.js.get_button(9) - - - - diff --git a/plugins/maxwell.py b/plugins/maxwell.py new file mode 100644 index 0000000..7b45ef4 --- /dev/null +++ b/plugins/maxwell.py @@ -0,0 +1,1035 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# -*- mode: Python -*- +''' + +Maxwell +v0.1.0 + +This client uses the relative drawing functions (rpolyline) -> your coordinates must be centered around 0,0. + +- Mixer : + LRmixer : right curve percent + LRtype : "add", "minus", "multiply" + +- points numbers + +- Curve objects : required parameters : + name, curvetype, freqlimit, freq, inversion + +- Scaler : + Everything is resize by a constant + +- Translator : + translations X Y Z by a constant + +- Rotator : + rotations X Y Z by a constant + +- Duplicator : duplicators, duplicatorsAngle + Duplicators are extra rpolyline with Angle added as global rotation parameter along *all* axis, this need to be checked conceptually + + +TODO : + +- in Curve Objects : + amptype, amp, phasemodtype, phasemodspeed, phasemodspeed, phaseoffsettype, phaseoffset + +- Other functions for lfos, translator, rotator, scaler, duplicator,... +- CC inputs for all parameters + + + +LICENCE : CC + +by Sam Neurohack + +''' +import sys +import os +print() +ljpath = r'%s' % os.getcwd().replace('\\','/') + +# import from shell + +sys.path.append(ljpath +'/../libs/') + +#import from LJ +sys.path.append(ljpath +'/libs/') +print ('Maxwell plugin startup') + +import lj23 as lj +sys.path.append('../libs') +from OSC3 import OSCServer, OSCClient, OSCMessage +import redis +import socketserver +import math +import time +import argparse +import numpy as np +import weakref +from math import pi as PI +import scipy.signal as signal +import midi3 + + +OSCinPort = 8012 +#oscrun = True +# myIP = "127.0.0.1" +PL = 0 + +print ("") +print ("Arguments parsing if needed...") +argsparser = argparse.ArgumentParser(description="Text Cycling for LJ") +argsparser.add_argument("-r","--redisIP",help="IP of the Redis server used by LJ (127.0.0.1 by default) ",type=str) +argsparser.add_argument("-c","--client",help="LJ client number (0 by default)",type=int) +argsparser.add_argument("-l","--laser",help="Laser number to be displayed (0 by default)",type=int) +argsparser.add_argument("-v","--verbose",help="Verbosity level (0 by default)",type=int) +argsparser.add_argument("-m","--myIP",help="Local IP (127.0.0.1 by default) ",type=str) + +args = argsparser.parse_args() + + +if args.client: + ljclient = args.client +else: + ljclient = 0 + +if args.laser: + plnumber = args.laser +else: + plnumber = 0 + +# Redis Computer IP +if args.redisIP != None: + redisIP = args.redisIP +else: + redisIP = '127.0.0.1' + +print("redisIP",redisIP) + +# myIP +if args.myIP != None: + myIP = args.myIP +else: + myIP = '127.0.0.1' + +print("myIP",myIP) + +if args.verbose: + debug = args.verbose +else: + debug = 0 + + +lj.Config(redisIP,ljclient,"maxw") + +white = lj.rgb2int(255,255,255) +red = lj.rgb2int(255,0,0) +blue = lj.rgb2int(0,0,255) +green = lj.rgb2int(0,255,0) + +points = 512 +# maxpoints = 500 +samples = points +samparray = [0] * samples + +# Channel 1 midi CC +cc1 = [0]*127 +# Channel 2 midi CC +cc2 = [0]*127 + +# Mixer +LRmixer = 0 +LRtype = 'minus' # "minus", 'multiply' + +# Duplicators +duplicators = 1 +duplicatorsAngle = 30 + +Oscillators = [] +LFOs = [] +Rotators = [] +Translators = [] + + +width = 800 +height = 600 +centerX = width / 2 +centerY = height / 2 + +# 3D to 2D projection parameters +fov = 256 +viewer_distance = 2.2 + + +# Anaglyph computation parameters for right and left eyes. +eye_spacing = 100 +nadir = 0.5 +observer_altitude = 30000 +#observer_altitude = 10000 +# elevation = z coordinate +# 0.0, -2000 pop out +map_plane_altitude = 0.0 + +# Relative Object (name, active, intensity, xy, color, red, green, blue, PL , closed, xpos , ypos , resize , rotx , roty , rotz) +#Leftshape = lj.RelativeObject('Leftshape', True, 255, [], red, 255, 0, 0, PL , True, 100 , 100 , 1 , 0 , 0 , 0) + + +# name, intensity, active, xy, color, red, green, blue, PL , closed): +Leftshape = lj.FixedObject('Leftshape', True, 255, [], red, 255, 0, 0, PL , False) +#Rightshape = lj.FixedObject('Rightshape', True, 255, [], green, 0, 255, 0, PL , False) + +# 'Destination' for each PL +# name, number, active, PL , scene, laser +# PL 0 +Dest0 = lj.DestObject('0', 0, True, 0 , 0, 0) +#Dest1 = lj.DestObject('1', 1, True, 0 , 1, 1) + +''' +viewgen3Lasers = [True,False,False,False] +# Add here, one by one, as much destination as you want for each PL. +# LJ and OSC can remotely add/delete destinations here. + +lj.Dests = { + "0": {"PL": 0, "scene": 0, "laser": 0}, + "1": {"PL": 0, "scene": 1, "laser": 1} + } + +''' + + +# +# OSC +# + +oscserver = OSCServer( (myIP, OSCinPort) ) +oscserver.timeout = 0 + +# this method of reporting timeouts only works by convention +# that before calling handle_request() field .timed_out is +# set to False +def handle_timeout(self): + self.timed_out = True + +# funny python's way to add a method to an instance of a class +import types +oscserver.handle_timeout = types.MethodType(handle_timeout, oscserver) + + +# OSC callbacks + +# /viewgen/ljclient +def OSCljclient(path, tags, args, source): + + print("Got /viewgen/ljclient with values", args[0]) + lj.WebStatus("viewgen to virtual "+ str(args[0])) + ljclient = args[0] + lj.LjClient(ljclient) + + +# /noteon note velocity +def OSCnoteon(path, tags, args, source): + + note = args[0] + velocity = args[1] + # Do something with it + +# /noteoff note +def OSCnoteoff(path, tags, args, source): + + note = args[0] + # Do something with it + +# /cc number value +def OSCcc(path, tags, args, source): + + cc1[args[0]]= args[1] + #cc = args[0] + #value = args[1] + +# +# CC functions +# + +# /cc cc number value +def cc(ccnumber, value): + + if ccnumber > 127: + cc2[ccnumber - 127]= value + else: + midichannel = basemidichannel + cc1[ccnumber]= value + + #print("Sending Midi channel", midichannel, "cc", ccnumber, "value", value) + #midi3.MidiMsg([CONTROLLER_CHANGE+midichannel-1,ccnumber,value], learner) + + +def FindCC(FunctionName): + + for Maxfunction in range(len(maxwell['ccs'])): + if FunctionName == maxwell['ccs'][Maxfunction]['Function']: + #print(FunctionName, "is CC", Maxfunction) + return Maxfunction + + +def LoadCC(): + global maxwell + + print("Loading Maxwell CCs Functions...") + f=open("maxwell.json","r") + s = f.read() + maxwell = json.loads(s) + print(len(maxwell['ccs']),"Functions") + print("Loaded.") + + +def SendCC(path,init): + + funcpath = path.split("/") + func = funcpath[len(funcpath)-1] + if func in specificvalues: + value = specificvalues[func][init] + else: + value = int(init) + + #print("sending CC", FindCC(path), "with value", value) + cc(FindCC(path),value) + time.sleep(0.005) + + +# +# computing functions +# + +def ssawtooth(samples, freq, phase): + + samparray = [0] * samples + t = np.linspace(0+phase, 1+phase, samples) + for ww in range(samples): + samparray[ww] = signal.sawtooth(2 * np.pi * freq * t[ww]) + return samparray + +def ssquare(samples, freq, phase): + + samparray = [0] * samples + t = np.linspace(0+phase, 1+phase, samples) + for ww in range(samples): + samparray[ww] = signal.square(2 * np.pi * freq * t[ww]) + return samparray + +def ssine(samples, freq, phase): + + samparray = [0] * samples + t = np.linspace(0+phase, 1+phase, samples) + for ww in range(samples): + samparray[ww] = np.sin(2 * np.pi * freq * t[ww]) + return samparray + +''' +def sline(samples, 1): + + samparray = [0] * samples + for ww in range(samples): + samparray[ww] = ww + return samparray +''' + +def slinear(samples, min, max): + + samparray = [0] * samples + linearinc = (max-min)/samples + for ww in range(samples): + if ww == 0: + samparray[ww] = min + else: + samparray[ww] = samparray[ww-1] + linearinc + #print('linear min max', min, max) + #print ('linear',samparray) + return samparray + +def sconstant(samples, values): + + samparray = [0] * samples + for ww in range(samples): + samparray[ww] = values + return samparray + +def remap(s,min1,max1, min2, max2): + a1, a2 = min1, max1 + b1, b2 = min2, max2 + return b1 + ((s - a1) * (b2 - b1) / (a2 - a1)) + +def cc2range(s,min,max): + a1, a2 = 0,127 + b1, b2 = min, max + return b1 + ((s - a1) * (b2 - b1) / (a2 - a1)) + +def range2cc(s,min,max): + a1, a2 = min, max + b1, b2 = 0,127 + return b1 + ((s - a1) * (b2 - b1) / (a2 - a1)) + +def LeftShift(elevation): + + diff = elevation - map_plane_altitude + return nadir * eye_spacing * diff / (observer_altitude - elevation) + +def RightShift(elevation): + + diff = map_plane_altitude - elevation + return (1 - nadir) * eye_spacing * diff / (observer_altitude - elevation) + +def Proj(x,y,z,angleX,angleY,angleZ): + + rad = angleX * math.pi / 180 + cosa = math.cos(rad) + sina = math.sin(rad) + y2 = y + y = y2 * cosa - z * sina + z = y2 * sina + z * cosa + + rad = angleY * math.pi / 180 + cosa = math.cos(rad) + sina = math.sin(rad) + z2 = z + z = z2 * cosa - x * sina + x = z2 * sina + x * cosa + + rad = angleZ * math.pi / 180 + cosa = math.cos(rad) + sina = math.sin(rad) + x2 = x + x = x2 * cosa - y * sina + y = x2 * sina + y * cosa + + """ Transforms this 3D point to 2D using a perspective projection. """ + factor = fov / (viewer_distance + z) + x = x * factor + centerX + y = - y * factor + centerY + return (x,y) + + +# +# Main Oscillators +# + +specificvalues = { + "sine": 0, + "saw": 33, + "square": 95, + "linear": 127, + "constant" : 128, + "1": 0, + "4": 26, + "16": 52, + "32": 80, + "127": 127, + "solid": 0, + "lfo": 127, + "off": 0, + "on": 127, + "add": 0, + "minus": 50, + "multiply": 127, + "lfo1": 33, + "lfo2": 95, + "lfo3": 127, + "manual": 0, + } + +''' +"colormodtype": { + "sin": 0, + "linear": 127 + } +''' +# return v1,v2,v3 or v4 according to "value" 0-127 +def vals4(value,v1,v2,v3,v4): + if value < 32: + return v1 + if value > 31 and value < 64: + return v2 + if value > 61 and value < 96: + return v3 + if value > 95: + return v4 + +# return v1,v2,v3,v4 or v5 according to "value" 0-127 +def vals5(value,v1,v2,v3,v4,v5): + if value < 26: + return v1 + if value > 25 and value < 52: + return v2 + if value > 51 and value < 80: + return v3 + if value > 79 and value < 104: + return v4 + if value > 104: + return v5 + + +class OsciObject: + + _instances = set() + counter = 0 + kind = 'fixed' + + def __init__(self, name, curvetype, amp, inversion, baseCC): + + self.name = name + self.baseCC = baseCC + + # Amplitude 64 values positive and 64 values negative -256 to +256 + self.amp = amp + # curvetypes : sine, saw, square, linear, constant ? + self.curvetype = curvetype + self.freqlimit = 4 + # Curvetype frequency : 128 possible values between 1 - freqlimit + self.freq = 2 + # Amplitude Curvetype : constant, lfo1, lfo2, lfo3 + self.amptype = 'constant' + + # Phase modification type : linear or sine. + self.phasemodtype = 'linear' + # Phase modification 64 speed forward and 64 speed backward. + # Speed is increment browsing + self.phasemodspeed = 1 + #self.phasemodspeed = 0 + + self.phaseoffsettype = 'manual' + self.phaseoffset = 200 + + self.ampoffset = cc2range(cc1[self.baseCC + 9],0,32) + self.ampoffsettype = cc1[self.baseCC + 10] + + self.inversion = inversion + + self.phasemodcurve = [0]*points # ssine(points, self.freq, self.phasemodspeed) + self.phaseoffsetcurve = ssine(points, self.freq, self.phasemodspeed) + self.values = ssine(points, self.freq, self.phasemodspeed) + self.counter = 0 + + self.samples = samples + + self._instances.add(weakref.ref(self)) + OsciObject.counter += 1 + + # print(self.name, "kind", self.kind, "port", self.port) + + @classmethod + def getinstances(cls): + dead = set() + for ref in cls._instances: + obj = ref() + if obj is not None: + yield obj + else: + dead.add(ref) + cls._instances -= dead + + def __del__(self): + OsciObject.counter -= 1 + + + def CC2VAR(self): + + if cc1[self.baseCC] == 128: + self.curvetype = "constant" + else: + self.curvetype = vals4(cc1[self.baseCC], "sine", "saw","square","linear") + + self.freqlimit = vals5(cc1[self.baseCC + 2],"1","4","16","32","127") + self.freq = cc2range(cc1[self.baseCC + 1], 0, self.freqlimit) + self.amptype = vals4(cc1[self.baseCC + 4], "constant", "lfo1","lfo2","lfo3") + self.amp = cc2range(cc1[self.baseCC + 3] , -256, 256) + + if cc1[self.baseCC]+ 6 < 64: + self.phasemodtype = "linear" + else: + self.phasemodtype ="sine" + + # phasemodspeed : 0 to 32 ?, because why not 32 ? to test. + self.phasemodspeed = cc2range(cc1[self.baseCC + 5], 0, 32) + self.phaseoffsettype = vals4(cc1[self.baseCC + 8], "manual", "lfo1","lfo2","lfo3") + # phaseoffset : between 0 to 10 ? + self.phaseoffset = cc2range(cc1[self.baseCC + 7], 0, 10) + + self.ampoffsettype = vals4(cc1[self.baseCC + 10], "manual", "lfo1","lfo2","lfo3") + # ampoffset : between 0 to 10 ? + self.ampoffset = cc2range(cc1[self.baseCC + 9], 0, 10) + + self.inversion = cc1[self.baseCC + 11] + + def VAR2CC(self): + + ''' + /osc/left/X/curvetype is Artnet 0 MIDI Channel 1 CC 0 "sine"/0 - "saw"/33 - "square"/95 - "linear"/127 - "constant"/128 + /osc/left/X/freq is Artnet 1 MIDI Channel 1 CC 1 0 - freqlimit + /osc/left/X/freqlimit is Artnet 2 MIDI Channel 1 CC 2 "1"/0 - "4"/26 - "16"/52 - "32"/80 - "127"/127 + /osc/left/X/amp is Artnet 3 MIDI Channel 1 CC 3 0/-256 - 127/256 + /osc/left/X/amptype is Artnet 4 MIDI Channel 1 CC 4 "constant"/0 - "lfo1"/33 - "lfo2"/95 - "lfo3"/127 + /osc/left/X/phasemodspeed is Artnet 5 MIDI Channel 1 CC 5 0 - 32 ? + /osc/left/X/phasemodtype is Artnet 6 MIDI Channel 1 CC 6 : "linear"/ - "sin"/ + /osc/left/X/phaseoffset is Artnet 7 MIDI Channel 1 CC 7 0 - 10 ? + /osc/left/X/phaseoffsettype is Artnet 8 MIDI Channel 1 CC 8 "manual"/0 - "lfo1"/33 - "lfo2"/95 - "lfo3"/127 + /osc/left/X/ampoffset is Artnet 9 MIDI Channel 1 CC 9 0 - 10 ? + /osc/left/X/ampoffsettype is Artnet 10 MIDI Channel 1 CC 10 "manual"/0 - "lfo1"/33 - "lfo2"/95 - "lfo3"/127 + /osc/left/X/inversion is Artnet 11 MIDI Channel 1 CC 11 : "off"/0 - "on"/127 + ''' + + cc1[self.baseCC + 3] = range2cc(self.amp, -256, 256) + cc1[self.baseCC] = specificvalues[self.curvetype] + cc1[self.baseCC + 2] = specificvalues[str(self.freqlimit)] + cc1[self.baseCC + 1] = range2cc(self.freq, 0, self.freqlimit) + if self.amptype == 'constant': + cc1[self.baseCC + 4] = 0 + else: + cc1[self.baseCC + 4] = specificvalues[self.amptype] + + # Phase modification type : linear or sine. + if self.phasemodtype == 'linear': + cc1[self.baseCC + 6] = 0 + else: + cc1[self.baseCC + 6] = 90 + cc1[self.baseCC + 5] = range2cc(self.phasemodspeed, 0, 32) + + # Phase offset + cc1[self.baseCC + 8] = specificvalues[self.phaseoffsettype] + cc1[self.baseCC + 7] = range2cc(self.phaseoffset, 0, 10) + + # Amp offset + cc1[self.baseCC + 9] = range2cc(self.ampoffset, 0, 10) + cc1[self.baseCC + 10] = specificvalues[self.ampoffsettype] + + if self.inversion == True: + cc1[self.baseCC + 11] = 127 + else: + cc1[self.baseCC + 11] = 0 + + + def Curve(self): + + self.values = [0] * points + self.ampcurve = [0] * points + self.phasemodcurve = [0] * points + self.phaseoffsetcurve = [0] * points + + self.counter += 1 + #print ('counter', self.counter) + if self.counter == points: + self.counter = 0 + # Phase offset curve + #self.phasemodcurve = slinear(points, -PI, PI) # ssine(points, self.freq, self.phasemodspeed) + if self.phaseoffsettype == 'manual': + self.phaseoffsetcurve = sconstant(points, self.phaseoffset) + + if self.phaseoffsettype == 'lfo1': + self.phaseoffsetcurve = lfo1.Curve() + + if self.phaseoffsettype == 'lfo2': + self.phaseoffsetcurve = lfo2.Curve() + + if self.phaseoffsettype == 'lfo3': + self.phaseoffsetcurve = lfo3.Curve() + + + # Phase mod curve : phasemodspeed is 'speed' of change + if self.phasemodtype == 'linear': + self.phasemodcurve = slinear(points, -PI*self.phasemodspeed, PI*self.phasemodspeed) + + if self.phasemodtype == 'lfo1': + self.phasemodcurve = lfo1.Curve() + + if self.phasemodtype == 'lfo2': + self.phasemodcurve = lfo2.Curve() + + if self.phasemodtype == 'lfo3': + self.phasemodcurve = lfo3.Curve() + + self.phasemodspeed = self.phasemodcurve[self.counter] + #print('counter', self.counter, 'phasemod',self.phasemodspeed) + + # Base values curve, trigo functions between -1 and + 1 + if self.curvetype == 'sine': + self.ampcurve = ssine(points, self.freq, self.phasemodspeed) + + if self.curvetype == 'saw': + self.ampcurve = ssawtooth(points, self.freq, self.phasemodspeed) + + if self.curvetype == 'square': + self.ampcurve = ssquare(points, self.freq, self.phasemodspeed) + + if self.curvetype == 'linear': + self.ampcurve = slinear(points, -1, 1) + + if self.curvetype == 'constant': + self.ampcurve = sconstant(points, self.freq) + + + for point in range(points): + + # curve points = base curve * amp + curve modifier + if self.amptype == 'constant': + self.values[point] = self.ampcurve[point] * self.amp + + if self.amptype == 'lfo1': + self.values[point] = (self.ampcurve[point] * self.amp) + (lfo1.values[point] * self.amp) + + if self.amptype == 'lfo2': + self.values[point] = (self.ampcurve[point] * self.amp) + (lfo2.values[point] * self.amp) + + if self.amptype == 'lfo3': + self.values[point] = (self.ampcurve[point] * self.amp) + (lfo3.values[point] * self.amp) + + if self.inversion == True: + self.values = self.values[::-1] + +# +# LFOs +# + +class LFObject: + + _instances = set() + counter = 0 + kind = 'fixed' + + def __init__(self, name): + + self.name = name + self.freqlimit = 4 + self.freq = 1 + self.curvetype = 'sine' + # -1 1 + self.phasemodspeed = 0 + self.inversion = False + self.values = ssine(points, self.freq, self.phasemodspeed) + + self._instances.add(weakref.ref(self)) + LFObject.counter += 1 + + #print(self.name, "type", self.curvetype, "freq", self.freq) + + @classmethod + def getinstances(cls): + dead = set() + for ref in cls._instances: + obj = ref() + if obj is not None: + yield obj + else: + dead.add(ref) + cls._instances -= dead + + def Curve(self): + + #print(self.name, "type", self.curvetype, "freq", self.freq) + self.values = [0]*points + if self.curvetype == 'sine': + self.values = ssine(points, self.freq, self.phasemodspeed) + + if self.curvetype == 'saw': + self.values = ssawtooth(points, self.freq, self.phasemodspeed) + + if self.curvetype == 'square': + self.values = ssquare(points, self.freq, self.phasemodspeed) + + if self.curvetype == 'linear': + self.values = slinear(points, self.freq) + + if self.curvetype == 'constant': + self.values = sconstant(points, self.freq) + + if self.inversion == True: + self.values = self.values[::-1] + + def __del__(self): + LFObject.counter -= 1 + +# +# Rotators +# + +class RotatorObject: + + ''' + # anim format (name, xpos, ypos, resize, currentframe, totalframe, count, speed) + # 0 1 2 3 4 5 6 7 + # total frames is fetched from directory by lengthPOSE() + #anims[0] = ['boredhh' , xy_center[0] - 100, xy_center[1] + 30, 550, 0, 0, 0, animspeed] + anim[4] = anim[4]+anim[7] + if anim[4] >= anim[5]: + anim[4] = 0 + ''' + + _instances = set() + counter = 0 + kind = 'fixed' + + def __init__(self, name): + + self.name = name + self.curvetype = 'constant' + self.speed = 0 + self.lfo = False + self.direction = 0 + + self._instances.add(weakref.ref(self)) + RotatorObject.counter += 1 + + + @classmethod + def getinstances(cls): + dead = set() + for ref in cls._instances: + obj = ref() + if obj is not None: + yield obj + else: + dead.add(ref) + cls._instances -= dead + + def __del__(self): + RotatorObject.counter -= 1 + + def Curve(self): + + self.values = [0]*points + + if self.curvetype == 'sine': + self.values = ssine(points, self.direction, self.phasemodspeed) + + if self.curvetype == 'saw': + self.values = ssawtooth(points, self.direction, self.phasemodspeed) + + if self.curvetype == 'square': + self.values = ssquare(points, self.direction, self.phasemodspeed) + + if self.curvetype == 'linear': + self.values = slinear(points, self.direction) + + if self.curvetype == 'constant': + self.values = sconstant(points, 0) + +# +# Translators +# + +class TranslatorObject: + + _instances = set() + counter = 0 + kind = 'fixed' + + def __init__(self, name, amt, speed): + + self.name = name + self.curvetype = 'constant' + self.speed = speed + self.lfo = False + self.amt = amt + #self.values = ssine(points, self.amt, self.speed) + self.values = sconstant(points, self.amt) + + self._instances.add(weakref.ref(self)) + TranslatorObject.counter += 1 + + @classmethod + def getinstances(cls): + dead = set() + for ref in cls._instances: + obj = ref() + if obj is not None: + yield obj + else: + dead.add(ref) + cls._instances -= dead + + def __del__(self): + TranslatorObject.counter -= 1 + + def Curve(self): + + self.values = [0]*points + + if self.curvetype == 'sine': + self.values = ssine(points, self.amt, self.speed) + + if self.curvetype == 'saw': + self.values = ssawtooth(points, self.amt, self.speed) + + if self.curvetype == 'square': + self.values = ssquare(points, self.amt, self.speed) + + if self.curvetype == 'linear': + self.values = slinear(points, self.amt) + + if self.curvetype == 'constant': + self.values = sconstant(points, self.amt) + +# +# Scaler +# + +Scalercurvetype = 'constant' +Scalercurve = [0.05] * points +Scalerspeed = 0 +Scalerbutton = False +Scalerwidth = 0 +Scaleramt = 0 + + +def ScalerCurve(): + + Scalercurve = [0]*points + + if Scalercurvetype == 'sine': + Scalercurve = ssine(points, Scaleramt, Scalerspeed) + + if Scalercurvetype == 'saw': + Scalercurve = ssawtooth(points, Scaleramt, Scalerspeed) + + if Scalercurvetype == 'square': + Scalercurve = ssquare(points, Scaleramt, Scalerspeed) + + if Scalercurvetype == 'linear': + Scalercurve = slinear(points, Scaleramt) + + if Scalercurvetype == 'constant': + Scalercurve = sconstant(points, 0.05) + +# +# Main +# + +def Run(): + + Left = [] + Right = [] + counter =0 + lj.WebStatus("Maxwellator") + lj.SendLJ("/maxw/start 1") + + # OSC + # OSC Server callbacks + print("Starting OSC server at",myIP," port",OSCinPort,"...") + oscserver.addMsgHandler( "/maxw/ljclient", OSCljclient ) + + # You will receive midi callbacks in OSC messages form if this plugin is in midi2OSC list in midi3.py and midi3.py is imported somewhere + oscserver.addMsgHandler( "/noteon", OSCnoteon) + oscserver.addMsgHandler( "/noteoff", OSCnoteoff) + oscserver.addMsgHandler( "/cc", OSCnoteon) + # Add OSC generic plugins commands : 'default", /ping, /quit, /pluginame/obj, /pluginame/var, /pluginame/adddest, /pluginame/deldest + lj.addOSCdefaults(oscserver) + + + # Drawing parameters + # LFOs + lfo1 = LFObject("lfo1") + lfo2 = LFObject("lfo2") + lfo3 = LFObject("lfo3") + + # Rotators + rotX = RotatorObject("rotX") + rotY = RotatorObject("rotY") + rotZ = RotatorObject("rotZ") + + # Translators : name amount speed + transX = TranslatorObject("transX",0,0) + transY = TranslatorObject("transY",0,0) + transZ = TranslatorObject("transZ",0,0) + + Scaler = ScalerCurve() + + # Left parameters : name, type, amp, inversion, base midi CC + leftX = OsciObject("leftX", "sine", 30, False, 0) + leftY = OsciObject("leftY", "sine", 30, True, 12) + leftZ = OsciObject("leftZ", "constant", 0, False, 24) + + # Right parameters : name, type, amp, inversion, base midi CC + rightX = OsciObject("rightX", "saw", 30, False, 36) + rightY = OsciObject("rightY", 'saw', 30, True, 48) + rightZ = OsciObject("rightZ", 'constant', 0, False, 60) + + try: + + while lj.oscrun: + + lj.OSCframe() + + Left = [] + Right = [] + + lfo1.Curve() + lfo2.Curve() + lfo3.Curve() + + transX.Curve() + transY.Curve() + transZ.Curve() + + rotX.Curve() + rotY.Curve() + rotZ.Curve() + + leftX.Curve() + leftY.Curve() + leftZ.Curve() + + rightX.Curve() + rightY.Curve() + rightZ.Curve() + + for point in range(points): + + if LRtype == 'add': + + CurveX = (leftX.values[point]*(100-LRmixer)/100) + (rightX.values[point]*LRmixer/100) + transX.values[point] + CurveY = (leftY.values[point]*(100-LRmixer)/100) + (rightY.values[point]*LRmixer/100) + transY.values[point] + CurveZ = (leftZ.values[point]*(100-LRmixer)/100) + (rightZ.values[point]*LRmixer/100) + transZ.values[point] + + if LRtype == 'minus': + + CurveX = (leftX.values[point]*(100-LRmixer)/100) - (rightX.values[point]*LRmixer/100) + transX.values[point] + CurveY = (leftY.values[point]*(100-LRmixer)/100) - (rightY.values[point]*LRmixer/100) + transY.values[point] + CurveZ = (leftZ.values[point]*(100-LRmixer)/100) - (rightZ.values[point]*LRmixer/100) + transZ.values[point] + + if LRtype == 'multiply': + + CurveX = (leftX.values[point]*(100-LRmixer)/100) * (rightX.values[point]*LRmixer/100) + transX.values[point] + CurveY = (leftY.values[point]*(100-LRmixer)/100) * (rightY.values[point]*LRmixer/100) + transY.values[point] + CurveZ = (leftZ.values[point]*(100-LRmixer)/100) * (rightZ.values[point]*LRmixer/100) + transZ.values[point] + + + Left.append(Proj(CurveX+LeftShift(CurveZ*25), CurveY, CurveZ, 0, 0, 0)) + #Right.append(Proj(CurveX+RightShift(CurveZ*25), CurveY, CurveZ, 0, 0, 0)) + + for clone in range(duplicators): + + # Drawing step, 2 possibilities + # Red and Green drawn by laser 0 + lj.rPolyLineOneColor(Left, c = Leftshape.color , PL = Leftshape.PL, closed = Leftshape.closed, xpos = 350, ypos = 350, resize = Scalercurve[0], rotx = rotX.values[0] + (clone * duplicatorsAngle), roty = rotY.values[0] + (clone * duplicatorsAngle), rotz = rotZ.values[0] + (clone * duplicatorsAngle)) + #lj.PolyLineOneColor(Right, c = Rightshape.color , PL = Rightshape.PL, closed = Rightshape.closed) + + #lj.DrawPL(PL) + lj.DrawDests() + + + time.sleep(0.01) + + counter += 1 + if counter > 360: + counter = 0 + + except KeyboardInterrupt: + pass + + # Gently stop on CTRL C + + finally: + + lj.ClosePlugin() + + +Run() diff --git a/plugins/planetarium/lj3.py b/plugins/planetarium/lj3.py deleted file mode 100644 index 22cb4af..0000000 --- a/plugins/planetarium/lj3.py +++ /dev/null @@ -1,347 +0,0 @@ -# coding=UTF-8 - -''' -LJ v0.8.1 in python3 -Some LJ functions useful for python clients (was framy.py) - -OSC functions commented, waiting working on OSC in python3 - -Config(redisIP, client number) -PolyLineOneColor -rPolyLineOneColor - -Text(word, color, PL, xpos, ypos, resize, rotx, roty, rotz) : Display a word -Send(adress,message) : remote control. See commands.py -WebStatus(message) : display message on webui -DrawPL(point list number) : once you stacked all wanted elements, like 2 polylines, send them to lasers. - -LICENCE : CC -Sam Neurohack - -''' - -import math -import redis - -# Import needed modules from osc4py3 -from osc4py3.as_eventloop import * -from osc4py3 import oscbuildparse - - -#redisIP = '127.0.0.1' -#r = redis.StrictRedis(host=redisIP, port=6379, db=0) - -ClientNumber = 0 - -point_list = [] -pl = [[],[],[],[]] - -# -# OSC interaction with LJ -# - -def OSCstart(): - # Start the system. - osc_startup() - #osc_udp_client(redisIP, 8002, "LJ 8002") - -def OSCframe(): - #print("OSCprocess") - osc_process() - -# Properly close the system. Todo -def OSCstop(): - osc_terminate() - - -def SendLJ(oscaddress,oscargs=''): - - try: - msg = oscbuildparse.OSCMessage(oscaddress, None, [oscargs]) - osc_send(msg, "LJ 8002") - OSCframe() - - except: - print ('Connection to LJ refused : died ?') - pass - -# Answer to LJ pings -def OSCping(value): - - print("I got /ping with value", value) - SendLJ("/pong",value) - -''' -def handlerfunction(s, x, y): - # Will receive message data unpacked in s, x, y - pass - -def handlerfunction2(address, s, x, y): - # Will receive message address, and message data flattened in s, x, y - pass - -# Make server channels to receive packets. -osc_udp_server("127.0.0.1", 3721, "localhost") -osc_udp_server("0.0.0.0", 3724, "anotherserver") -''' - - - -ASCII_GRAPHICS = [ - -# caracteres corrects - - [(-50,30), (-30,-30), (30,-30), (10,30), (-50,30)], #0 - [(-20,30), (0,-30), (-20,30)], #1 - [(-30,-10), (0,-30), (30,-10), (30,0), (-30,30), (30,30)], #2 - [(-30,-30), (0,-30), (30,-10), (0,0), (30,10), (0,30), (-30,30)], #3 - [(30,10), (-30,10), (0,-30), (0,30)], #4 - [(30,-30), (-30,-30), (-30,0), (0,0), (30,10), (0,30), (-30,30)], #5 - [(30,-30), (0,-30), (-30,-10), (-30,30), (0,30), (30,10), (30,0), (-30,0)], #6 - [(-30,-30), (30,-30), (-30,30)], #7 - [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30), (-30,0), (30,0)], #8 - [(30,0), (-30,0), (-30,-10), (0,-30), (30,-30), (30,10), (0,30), (-30,30)], #9 - -# caracteres a implementer - [(-30,10), (30,-10), (30,10), (0,30), (-30,10), (-30,-10), (0,-30), (30,-10)], #: - [(-30,-10), (0,-30), (0,30)], [(-30,30), (30,30)], #; - [(-30,-10), (0,-30), (30,-10), (30,0), (-30,30), (30,30)], #< - [(-30,-30), (0,-30), (30,-10), (0,0), (30,10), (0,30), (-30,30)], #= - [(30,10), (-30,10), (0,-30), (0,30)], #> - [(30,-30), (-30,-30), (-30,0), (0,0), (30,10), (0,30), (-30,30)], #? - [(30,-30), (0,-30), (-30,-10), (-30,30), (0,30), (30,10), (30,0), (-30,0)], #@ - -# Caracteres corrects - - - [(-30,30), (-30,-30), (30,-30), (30,30), (30,0), (-30,0)], #A - [(-30,30), (-30,-30), (30,-30), (30,30), (30,0), (-30,0)], #A - [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30), (-30,0), (30,0)], #B - [(30,30), (-30,30), (-30,-30), (30,-30)], #C - [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30)], #D - [(30,30), (-30,30), (-30,-0), (30,0), (-30,0), (-30,-30), (30,-30)], #E - [(-30,30), (-30,-0), (30,0), (-30,0), (-30,-30), (30,-30)], #F - [(0,0), (30,0), (30,30), (-30,30), (-30,-30),(30,-30)], #G - [(-30,-30), (-30,30), (-30,0), (30,0), (30,30), (30,-30)], #H - [(0,30), (0,-30)], #I - [(-30,30), (0,-30), (0,-30), (-30,-30), (30,-30)], #J - [(-30,-30), (-30,30), (-30,0), (30,-30), (-30,0), (30,30)], #K - [(30,30), (-30,30), (-30,-30)], #L - [(-30,30), (-30,-30), (0,0), (30,-30), (30,30)], #M - [(-30,30), (-30,-30), (30,30), (30,-30)], #N - [(-30,30), (-30,-30), (30,-30), (30,30), (-30,30)], #O - [(-30,0), (30,0), (30,-30), (-30,-30), (-30,30)], #P - [(30,30), (30,-30), (-30,-30), (-30,30), (30,30),(35,35)], #Q - [(-30,30), (-30,-30), (30,-30), (30,0), (-30,0), (30,30)], #R - [(30,-30), (-30,-30), (-30,0), (30,0), (30,30), (-30,30)], #S - [(0,30), (0,-30), (-30,-30), (30,-30)], #T - [(-30,-30), (-30,30), (30,30), (30,-30)], #U - [(-30,-30), (0,30), (30,-30)], #V - [(-30,-30), (-30,30), (0,0), (30,30), (30,-30)], #W - [(-30,30), (30,-30)], [(-30,-30), (30,30)], #X - [(0,30), (0,0), (30,-30), (0,0), (-30,-30)], #Y - [(30,30), (-30,30), (30,-30), (-30,-30)], #Z - - # A implementer - - [(-30,-10), (0,-30), (0,30)], [(-30,30), (30,30)], #[ - [(-30,-10), (0,-30), (30,-10), (30,0), (-30,30), (30,30)], #\ - [(-30,-30), (0,-30), (30,-10), (0,0), (30,10), (0,30), (-30,30)], #] - [(30,10), (-30,10), (0,-30), (0,30)], #^ - [(30,-30), (-30,-30), (-30,0), (0,0), (30,10), (0,30), (-30,30)], #_ - [(30,-30), (0,-30), (-30,-10), (-30,30), (0,30), (30,10), (30,0), (-30,0)], #` - - # Implementé - - [(-20,20), (-20,-20), (20,-20), (20,20), (20,0), (-20,0)], #a - [(-20,20), (-20,-20), (20,-20), (20,20), (-20,20), (-20,0), (20,0)], #b - [(20,20), (-20,20), (-20,-20), (20,-20)], #c - [(-20,20), (-20,-20), (20,-20), (20,20), (-20,20)], #d - [(20,20), (-20,20), (-20,-0), (20,0), (-20,0), (-20,-20), (20,-20)], #e - [(-20,20), (-20,-0), (20,0), (-20,0), (-20,-20), (20,-20)], #f - [(0,0), (20,0), (20,20), (-20,20), (-20,-20),(20,-20)], #g - [(-20,-20), (-20,20), (-20,0), (20,0), (20,20), (20,-20)], #H - [(0,20), (0,-20)], #I - [(-20,20), (0,-20), (0,-20), (-20,-20), (20,-20)], #J - [(-20,-20), (-20,20), (-20,0), (20,-20), (-20,0), (20,20)], #K - [(20,20), (-20,20), (-20,-20)], #L - [(-20,20), (-20,-20), (0,0), (20,-20), (20,20)], #M - [(-20,20), (-20,-20), (20,20), (20,-20)], #N - [(-20,20), (-20,-20), (20,-20), (20,20), (-20,20)], #O - [(-20,0), (20,0), (20,-20), (-20,-20), (-20,20)], #P - [(20,20), (20,-20), (-20,-20), (-20,20), (20,20),(25,25)], #Q - [(-20,20), (-20,-20), (20,-20), (20,0), (-20,0), (20,20)], #R - [(20,-20), (-20,-20), (-20,0), (20,0), (20,20), (-20,20)], #S - [(0,20), (0,-20), (-20,-20), (20,-20)], #T - [(-20,-20), (-20,20), (20,20), (20,-20)], #U - [(-20,-20), (0,20), (20,-20)], #V - [(-20,-20), (-20,20), (0,0), (20,20), (20,-20)], #W - [(-20,20), (20,-20)], [(-20,-20), (20,20)], #X - [(0,20), (0,0), (20,-20), (0,0), (-20,-20)], #Y - [(20,20), (-20,20), (20,-20), (-20,-20)], #Z - - [(-2,15), (2,15)] # Point a la place de { -] - -def Config(redisIP,client): - global ClientNumber - - r = redis.StrictRedis(host=redisIP, port=6379, db=0) - ClientNumber = client - osc_udp_client(redisIP, 8002, "LJ 8002") - -# If you want to use rgb for color : -def rgb2int(r,g,b): - return int('0x%02x%02x%02x' % (r,g,b),0) - -def LineTo(xy, c, PL): - - pl[PL].append((xy + (c,))) - -def Line(xy1, xy2, c, PL): - LineTo(xy1, 0, PL) - LineTo(xy2, c , PL) - - -def PolyLineOneColor(xy_list, c, PL , closed ): - #print "--" - #print "c",c - #print "xy_list",xy_list - #print "--" - xy0 = None - for xy in xy_list: - if xy0 is None: - xy0 = xy - #print "xy0:",xy0 - LineTo(xy0,0, PL) - LineTo(xy0,c, PL) - else: - #print "xy:",xy - LineTo(xy,c, PL) - if closed: - LineTo(xy0,c, PL) - - -# Computing points coordinates for rPolyline function from 3D and around 0,0 to pygame coordinates -def Pointransf(xy, xpos = 0, ypos =0, resize =1, rotx =0, roty =0 , rotz=0): - - x = xy[0] * resize - y = xy[1] * resize - z = 0 - - rad = rotx * math.pi / 180 - cosaX = math.cos(rad) - sinaX = math.sin(rad) - - y2 = y - y = y2 * cosaX - z * sinaX - z = y2 * sinaX + z * cosaX - - rad = roty * math.pi / 180 - cosaY = math.cos(rad) - sinaY = math.sin(rad) - - z2 = z - z = z2 * cosaY - x * sinaY - x = z2 * sinaY + x * cosaY - - rad = rotz * math.pi / 180 - cosZ = math.cos(rad) - sinZ = math.sin(rad) - - x2 = x - x = x2 * cosZ - y * sinZ - y = x2 * sinZ + y * cosZ - - #print xy, (x + xpos,y+ ypos) - return (x + xpos,y+ ypos) - ''' - to understand why it get negative Y - - # 3D to 2D projection - factor = 4 * gstt.cc[22] / ((gstt.cc[21] * 8) + z) - print xy, (x * factor + xpos, - y * factor + ypos ) - return (x * factor + xpos, - y * factor + ypos ) - ''' - -# Send 2D point list around 0,0 with 3D rotation resizing and reposition around xpos ypos -#def rPolyLineOneColor(self, xy_list, c, PL , closed, xpos = 0, ypos =0, resize =1, rotx =0, roty =0 , rotz=0): -def rPolyLineOneColor(xy_list, c, PL , closed, xpos = 0, ypos =0, resize =0.7, rotx =0, roty =0 , rotz=0): - xy0 = None - for xy in xy_list: - if xy0 is None: - xy0 = xy - LineTo(Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz),0, PL) - LineTo(Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz),c, PL) - else: - LineTo(Pointransf(xy, xpos, ypos, resize, rotx, roty, rotz),c, PL) - if closed: - LineTo(Pointransf(xy0, xpos, ypos, resize, rotx, roty, rotz),c, PL) - - -def LinesPL(PL): - print ("Stupido !! your code is to old : use DrawPL() instead of LinesPL()") - DrawPL(PL) - -def DrawPL(PL): - #print '/pl/0/'+str(PL), str(pl[PL]) - if r.set('/pl/'+str(ClientNumber)+'/'+str(PL), str(pl[PL])) == True: - pl[PL] = [] - return True - else: - return False - -def ResetPL(self, PL): - pl[PL] = [] - - - -def DigitsDots(number,color): - dots =[] - for dot in ASCII_GRAPHICS[number]: - #print dot - dots.append((gstt.xy_center[0]+dot[0],gstt.xy_center[1]+dot[1],color)) - #self.point_list.append((xy + (c,))) - return dots - -def CharDots(char,color): - - dots =[] - for dot in ASCII_GRAPHICS[ord(char)-46]: - dots.append((dot[0],dot[1],color)) - return dots - -def Text(message, c, PL, xpos, ypos, resize, rotx, roty, rotz): - - dots =[] - - l = len(message) - i= 0 - #print (message) - - for ch in message: - - #print "" - # texte centre en x automatiquement selon le nombre de lettres l - x_offset = 26 * (- (0.9*l) + 3*i) - #print i,x_offset - # if digit - if ord(ch)<58: - char_pl_list = ASCII_GRAPHICS[ord(ch) - 48] - else: - char_pl_list = ASCII_GRAPHICS[ord(ch) - 46 ] - - char_draw = [] - #dots.append((char_pl_list[0][0] + x_offset,char_pl_list[0][1],0)) - - for xy in char_pl_list: - char_draw.append((xy[0] + x_offset,xy[1],c)) - i += 1 - #print ch,char_pl_list,char_draw - rPolyLineOneColor(char_draw, c, PL , False, xpos, ypos, resize, rotx, roty, rotz) - # print ("laser",PL,"message",message) - #dots.append(char_draw) - - - - diff --git a/plugins/planetarium/main.py b/plugins/planetarium/main.py index 0a813be..29e4edb 100755 --- a/plugins/planetarium/main.py +++ b/plugins/planetarium/main.py @@ -29,6 +29,8 @@ Remember : LJ will automatically warp geometry according to alignement data. See ''' import redis +import sys +sys.path.append('../../libs') import lj3 import numpy as np import math,time @@ -48,7 +50,7 @@ from osc4py3 import oscbuildparse from osc4py3.oscmethod import * import json - +oscrun = True ''' is_py2 = sys.version[0] == '2' if is_py2: @@ -103,7 +105,7 @@ if args.redisIP != None: else: redisIP = '127.0.0.1' -lj3.Config(redisIP,ljclient) +lj3.Config(redisIP,ljclient,"planet") # # Inits Laser @@ -164,6 +166,42 @@ def Proj(x,y,z,angleX,angleY,angleZ): # All the coordinates base functions # +#Transforming coordinates functions by Povik martin.povik@gmail.com +import math +import datetime + +def cartesian_to_horizontal(x, y, z): + ele = math.degrees(math.atan2(z, math.sqrt(x * x + y * y))) + az = math.degrees(math.atan2(y, x)) + return math.sqrt(x * x + y * y + z * z), ele, az + +def horizontal_to_equatorial(az, ele, lat): + az, ele = math.radians(az), math.radians(ele) + lat = math.radians(lat) + cd_ct = math.cos(ele) * math.cos(az) * math.sin(lat) \ + + math.sin(ele) * math.cos(lat) + cd_st = math.cos(ele) * math.sin(az) + sd = -math.cos(ele) * math.cos(az) * math.cos(lat) + math.sin(ele) * math.sin(lat) + cd = math.sqrt(cd_ct * cd_ct + cd_st * cd_st) + return math.atan2(cd_st / cd, cd_ct / cd) * 12 / math.pi, \ + math.degrees(math.atan2(sd, cd)) + +def timestamp_to_jd(timestamp): + return float(timestamp) / 86400 + 2440587.5 + +def timestamp_to_lst(timestamp, lon): + timestamp = float(timestamp) + timeofday = timestamp % 86400 + jd0 = timestamp_to_jd(timestamp - timeofday) + T = (jd0 - 2451545) / 36525 + s0 = 6.697374558 + 2400.05133691 * T + 0.000025862 * T**2 \ + - 0.0000000017 * T**3 + return (s0 + 1.0027379093 * (timeofday / 86400) * 24.0 + float(lon) / 15) % 24 + +def horizontal_to_equatorial2(az, ele, lat, lon, timestamp): + t, d = horizontal_to_equatorial(az, ele, lat) + return d, (timestamp_to_lst(timestamp, lon) - t) % 24 + ''' To minize number of sky objects coordinates conversion : Change planetarium FOV in Ra Dec to select objects (planets, hipparcos,..). Then get only those objects in AltAz coordinates. @@ -586,13 +624,14 @@ def NewTime(timeshift): if DisplayAnything: UpdateAnything() -# /quit -def OSCquit(): +# /quit dummyvalue +def quit(value): + # don't do this at home (or it'll quit blender) + global oscrun - WebStatus("Planet stopping") - print("Stopping OSC...") - lj3.OSCstop() - sys.exit() + oscrun = False + print("Stopped by /quit.") + lj3.ClosePlugin() def OSCUI(value): # Will receive message address, and message data flattened in s, x, y @@ -615,7 +654,7 @@ try: osc_udp_server("127.0.0.1", OSCinPort, "InPort") osc_method("/planet/planetUI*", OSCUI) osc_method("/ping*", lj3.OSCping) - osc_method("/quit", OSCquit) + osc_method("/quit", quit) WebStatus("Loading Cities...") ts = load.timescale() @@ -654,7 +693,7 @@ try: DisplayAnything = False print("Start displaying on",lasernumber,"lasers") - while 1: + while oscrun: for laser in range(lasernumber): diff --git a/plugins/pysimu.py b/plugins/pysimu.py index 08ca8a7..57e9e4e 100755 --- a/plugins/pysimu.py +++ b/plugins/pysimu.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2.7 +#!/usr/bin/python3 # -*- coding: utf-8 -*- # -*- mode: Python -*- @@ -15,21 +15,42 @@ OSC server with : /simu/quit /simu/newpl new pl number to draw /simu/newclient new client number to draw +/ping ''' + #from __future__ import print_function import time import math import random import itertools +import traceback import sys import os #import thread import redis -import pygame + import pdb -import types, ast, argparse -from OSC import OSCServer, OSCClient, OSCMessage +import types, ast, argparse, struct +import numpy as np + +sys.path.append('../libs') + +# import from LJ +ljpath = r'%s' % os.getcwd().replace('\\','/') +sys.path.append(ljpath +'/libs/') +#print ljpath+'/libs/' + +import lj23 as lj + +is_py2 = sys.version[0] == '2' +if is_py2: + from OSC import OSCServer, OSCClient, OSCMessage + #print ("Importing lj23 and OSC from libs...") +else: + from OSC3 import OSCServer, OSCClient, OSCMessage + + screen_size = [750,750] pl = [[],[],[],[]] @@ -38,8 +59,11 @@ pl = [[],[],[],[]] print ("") print ("LJ v0.8.0 : Pygame simulator") print ("") +import pygame print ("Arguments parsing if needed...") + + # # Arguments parsing # @@ -77,7 +101,7 @@ else: redisIP = '127.0.0.1' r = redis.StrictRedis(host=redisIP, port=6379, db=0) - +lj.Config(redisIP,0,"simu") # myIP if args.myIP != None: @@ -101,7 +125,7 @@ print ("") print ("Receiving on ", oscIPin, ":",str(oscPORTin)) oscserver = OSCServer( (oscIPin, oscPORTin) ) oscserver.timeout = 0 -OSCRunning = True +oscrun = True def handle_timeout(self): self.timed_out = True @@ -109,10 +133,10 @@ def handle_timeout(self): oscserver.handle_timeout = types.MethodType(handle_timeout, oscserver) -def sendLJ(address, args): +def SendLJ(address, args): if debug >0: - print "Sending to LJ...", address, args + print("Sending to LJ...", address, args) osclientlj = OSCClient() osclientlj.connect((ljIP, ljPort)) @@ -127,9 +151,13 @@ def sendLJ(address, args): return True except: - print 'Connection to LJ IP', ljIP,'port', ljPort, 'refused : died ?' + print('Connection to LJ IP', ljIP,'port', ljPort, 'refused : died ?') return False +def WebStatus(message): + + SendLJ("/status",message) + # RAW OSC Frame available ? def osc_frame(): @@ -142,52 +170,42 @@ def osc_frame(): # /quit def quit(path, tags, args, source): + global oscrun + oscrun = False pygame.quit() - print "pySimu Stopped by /quit." - sys.exit() - -# /start : 0 exit -def start(path, tags, args, source): - - print args, type(args) - if args[0] == 0: - pygame.quit() - print "pySimu stopped by /start 0" - sys.exit() - -# Answer to LJ pings -def ping(path, tags, args, source): - # Will receive message address, and message data flattened in s, x, y - print "Simu got /ping with value", args[0] - print "Simu replied with /pong simu" - sendLJ("/pong","simu") - - + lj.ClosePlugin() + # /newPL pointlistnumber def newPL(path, tags, args, source): user = ''.join(path.split("/")) - print "" - print user,path,args - print "Simulator got a new point list number :", args[0] + print ("") + print (user,path,args) + print ("Simulator got a new point list number :", args[0]) simuPL = args[0] # /newClient clientnumber def newClient(path, tags, args, source): user = ''.join(path.split("/")) - print "" - print user,path,args - print "Simulator got a new client number : ", args[0] + print ("") + print (user,path,args) + print ("Simulator got a new client number : ", args[0]) ljclient = args[0] -oscserver.addMsgHandler( "/quit", quit ) -oscserver.addMsgHandler( "/ping", ping ) -oscserver.addMsgHandler( "/pysimu/start", start ) -oscserver.addMsgHandler( "/pysimu/newpl", newPL ) -oscserver.addMsgHandler( "/pysimu/newclient", newClient ) + +# Redis key 'n' -> numpy array a +# array 2 dimensions is also store in redis key : h time w values +def fromRedis(n): + + print ("get key", n) + encoded = r.get(n) + h, w = struct.unpack('>II',encoded[:8]) + print(h,w) + a = np.frombuffer(encoded, dtype=np.int16, offset=8).reshape(h,w) + return a # @@ -205,15 +223,26 @@ def RenderScreen(surface): c = int(xyc[2]) if c: pygame.draw.line(surface,c,xyc_prev[:2],xyc[:2],3) xyc_prev = xyc +# +# Startup +# - +SendLJ("/simu/start",1) +WebStatus("pysimu startup...") pygame.init() screen = pygame.display.set_mode(screen_size) pygame.display.set_caption("LJ Simulator") clock = pygame.time.Clock() update_screen = False +oscserver.addMsgHandler( "/quit", lj.OSCquit ) +oscserver.addMsgHandler( "/ping", lj.OSCping ) +#oscserver.addMsgHandler( "/simu/start", start ) +oscserver.addMsgHandler( "/simu/newpl", newPL ) +oscserver.addMsgHandler( "/simu/newclient", newClient ) + print ("Simulator displays client", ljclient, "point list", str(simuPL)) +WebStatus("pySimu "+ str(ljclient) + " " + str(simuPL)) # # Main @@ -221,7 +250,7 @@ print ("Simulator displays client", ljclient, "point list", str(simuPL)) try: - while True: + while lj.oscrun: # pending osc message ? osc_frame() @@ -232,8 +261,15 @@ try: break screen.fill(0) - pl[simuPL] = ast.literal_eval(r.get("/pl/"+ str(ljclient) + "/" + str(simuPL))) - + #print("/pl/"+ str(ljclient) + "/" + str(simuPL)) + #print r.get("/pl/"+ str(ljclient) + "/" + str(simuPL)) + if is_py2: + pl[simuPL] = ast.literal_eval(r.get("/pl/"+ str(ljclient) + "/" + str(simuPL))) + else: + pl[simuPL] = eval(r.get("/pl/"+ str(ljclient) + "/" + str(simuPL))) + + #pl[simuPL] = fromRedis("/pl/"+ str(ljclient) + "/" + str(simuPL)) + if update_screen: update_screen = False RenderScreen(screen) @@ -242,14 +278,19 @@ try: update_screen = True clock.tick(30) - # time.sleep(0.001) + time.sleep(0.001) except KeyboardInterrupt: pass +except Exception: + traceback.print_exc() + + finally: pygame.quit() - print "pySimu Stopped." + lj.ClosePlugin() + diff --git a/plugins/square.py b/plugins/square.py new file mode 100644 index 0000000..b7247ee --- /dev/null +++ b/plugins/square.py @@ -0,0 +1,331 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# -*- mode: Python -*- + + +''' + +Square +v0.1.0 + +Anaglyphed rotating square (for red and green glasses) + +This scene uses the drawing functions (polyline) provided by LJ in lj23.py + +LICENCE : CC + +by Sam Neurohack + + +''' +import sys +import os +print() +ljpath = r'%s' % os.getcwd().replace('\\','/') + +# import from shell + +sys.path.append(ljpath +'/../libs/') + +#import from LJ +sys.path.append(ljpath +'/libs/') +print (ljpath+'/../libs/') + +import lj23layers as lj + +sys.path.append('../libs') +from OSC3 import OSCServer, OSCClient, OSCMessage +import redis +import math +import time +import argparse + +OSCinPort = 8013 + +print ("") +print ("Arguments parsing if needed...") +argsparser = argparse.ArgumentParser(description="Square example for LJ") +argsparser.add_argument("-r","--redisIP",help="IP of the Redis server used by LJ (127.0.0.1 by default) ",type=str) +argsparser.add_argument("-s","--scene",help="LJ scene number (0 by default)",type=int) +#argsparser.add_argument("-l","--laser",help="Laser number to be displayed (0 by default)",type=int) +argsparser.add_argument("-v","--verbose",help="Verbosity level (0 by default)",type=int) +argsparser.add_argument("-m","--myIP",help="Local IP (127.0.0.1 by default) ",type=str) + +args = argsparser.parse_args() + +if args.scene: + ljscene = args.scene +else: + ljscene = 0 +''' +if args.laser: + plnumber = args.laser +else: + plnumber = 0 +''' + +# Redis Computer IP +if args.redisIP != None: + redisIP = args.redisIP +else: + redisIP = '127.0.0.1' + +print("redisIP",redisIP) + +# myIP +if args.myIP != None: + myIP = args.myIP +else: + myIP = '127.0.0.1' + +print("myIP",myIP) + +if args.verbose: + debug = args.verbose +else: + debug = 0 + +# Useful variables init. +white = lj.rgb2int(255,255,255) +red = lj.rgb2int(255,0,0) +blue = lj.rgb2int(0,0,255) +green = lj.rgb2int(0,255,0) + +width = 800 +height = 600 +centerX = width / 2 +centerY = height / 2 + +# 3D to 2D projection parameters +fov = 256 +viewer_distance = 2.2 + +# Anaglyph computation parameters for right and left eyes. +# algorythm come from anaglyph geo maps +eye_spacing = 100 +nadir = 0.5 +observer_altitude = 30000 +map_layerane_altitude = 0.0 + +# square coordinates : vertices that compose each of the square. +vertices = [ + (- 1.0, 1.0,- 1.0), + ( 1.0, 1.0,- 1.0), + ( 1.0,- 1.0,- 1.0), + (- 1.0,- 1.0,- 1.0) + ] + +face = [0,1,2,3] + +# +# LJ inits +# + +layer = 0 + +# Setup LJ library mandatory properties for this layerugin +lj.Config(redisIP, ljscene, "square") + +# Define properties for each drawn "element" : name, intensity, active, xy, color, red, green, blue, layer , closed +Leftsquare = lj.FixedObject('Leftsquare', True, 255, [], red, 255, 0, 0, layer , True) +Rightsquare = lj.FixedObject('Rightsquare', True, 255, [], green, 0, 255, 0, layer , True) + +# 'Destination' for given layer : name, number, active, layer , scene, laser +Dest0 = lj.DestObject('0', 0, True, 0 , 0, 0) # Dest0 will send layer 0 points to scene 0, laser 0 + +''' +TRICKS: + +1/ How to have another laser drawing the same thing ? + +That's a destination problem : just add another destination ! + +Dest1 = lj.DestObject('1', 1, True, 0 , 1, 1) # Dest1 will also send layer 0 points to scene 1, laser 1 + + +2/ Different layers to different lasers ? + +Say because of too much points you want Left element drawn by scene 0, laser 0 and right element by scene 0, laser 1 + +First define a different object/layer for each drawn element : + +Leftsquare = lj.FixedObject('Leftsquare', True, 255, [], red, 255, 0, 0, 0 , True) # Left goes to layer 0 +Rightsquare = lj.FixedObject('Rightsquare', True, 255, [], green, 0, 255, 0, 1 , True) # Right goes to layer 1 + +Define 2 destinations : + +Dest0 = lj.DestObject('0', 0, True, 0 , 0, 0) # Dest0 will send layer 0 points to scene 0, laser 0 +Dest1 = lj.DestObject('1', 1, True, 1 , 0, 1) # Dest1 will send layer 1 points to scene 0, laser 1 + + + +3/ Different layers to one laser ? + +You should consider adding all your points to one layer, but same as 1/ it's a destination problem, +just add another destination with the same scene/laser for each layer + +Dest1 = lj.DestObject('1', 1, True, 1 , 0, 0) # Dest1 will also send layer 1 points to scene 0, laser 0 + + +4/ I want to animate/modify anything on the fly : I'm doing a game and suddenly my hero change color. + +It's a declared object problem : say Hero is a Fixed Object, you can directly change value of + +Hero.name, Hero.active, Hero.intensity, Hero.xy, Hero.color, Hero.red, Hero.green, Hero.blue, Hero.layer, Hero.closed + +For a character vanishing in one point you can decrease it's size : RelativeObjects has graphics computation. +PNC.name, PNC.active, PNC.intensity, PNC.xy, PNC.color, PNC.red, PNC.green, PNC.blue, PNC.layer, PNC.closed, PNC.xpos, PNC.ypos, PNC.resize, PNC.rotx, PNC.roty, PNC.rotz + + +Same for Dest0 if it's a destObject : +Dest0.name, Dest0.number, Dest0.active, Dest0.layer, Dest0.scene, Dest0.laser + + +DrawDests() will take care of all your declared drawn elements and Destinations. + +''' + + +# +# Anaglyph computation : different X coordinate for each eye +# + +def LeftShift(elevation): + + diff = elevation - map_layerane_altitude + return nadir * eye_spacing * diff / (observer_altitude - elevation) + +def RightShift(elevation): + + diff = map_layerane_altitude - elevation + return (1 - nadir) * eye_spacing * diff / (observer_altitude - elevation) + +# +# OSC +# + +oscserver = OSCServer( (myIP, OSCinPort) ) +oscserver.timeout = 0 + + +# this method of reporting timeouts only works by convention +# that before calling handle_request() field .timed_out is +# set to False +def handle_timeout(self): + self.timed_out = True + +# funny python's way to add a method to an instance of a class +import types +oscserver.handle_timeout = types.MethodType(handle_timeout, oscserver) + + +# OSC callbacks + +# /square/ljscene +def OSCljscene(path, tags, args, source): + + print("Got /square/ljscene with value", args[0]) + lj.WebStatus("square to virtual "+ str(args[0])) + ljscene = args[0] + lj.Ljscene(ljscene) + + +def Proj(x,y,z,angleX,angleY,angleZ): + + rad = angleX * math.pi / 180 + cosa = math.cos(rad) + sina = math.sin(rad) + y2 = y + y = y2 * cosa - z * sina + z = y2 * sina + z * cosa + + rad = angleY * math.pi / 180 + cosa = math.cos(rad) + sina = math.sin(rad) + z2 = z + z = z2 * cosa - x * sina + x = z2 * sina + x * cosa + + rad = angleZ * math.pi / 180 + cosa = math.cos(rad) + sina = math.sin(rad) + x2 = x + x = x2 * cosa - y * sina + y = x2 * sina + y * cosa + + + """ Transforms this 3D point to 2D using a perspective projection. """ + factor = fov / (viewer_distance + z) + x = x * factor + centerX + y = - y * factor + centerY + return (x,y) + + +# +# Main +# + +def Run(): + + + Left = [] + Right = [] + counter =0 + lj.WebStatus("Square") + lj.SendLJ("/square/start 1") + + # OSC Server callbacks + print("Starting OSC server at",myIP," port",OSCinPort,"...") + oscserver.addMsgHandler( "/square/ljscene", OSCljscene ) + + # Add OSC generic layerugins commands : 'default", /ping, /quit, /pluginame/obj, /pluginame/var, /pluginame/adddest, /pluginame/deldest + lj.addOSCdefaults(oscserver) + + try: + + while lj.oscrun: + + lj.OSCframe() + Left = [] + Right = [] + + x = vertices[0][0] + y = vertices[0][1] + z = vertices[0][2] + + # LJ tracers will "move" the laser to this first point in black, then move to the next with second point color. + # For more accuracy in dac emulator, repeat this first point. + + # Generate all points in square. + for point in face: + x = vertices[point][0] + y = vertices[point][1] + z = vertices[point][2] + + Left.append(Proj(x+LeftShift(z*25),y,z,0,counter,0)) + Right.append(Proj(x+RightShift(z*25),y,z,0,counter,0)) + + + lj.PolyLineOneColor(Left, c = Leftsquare.color , layer = Leftsquare.layer, closed = Leftsquare.closed) + lj.PolyLineOneColor(Right, c = Rightsquare.color , layer = Rightsquare.layer, closed = Rightsquare.closed) + + lj.DrawDests() + + + time.sleep(0.1) + + counter += 1 + if counter > 360: + counter = 0 + + except KeyboardInterrupt: + pass + + # Gently stop on CTRL C + + finally: + + lj.ClosePlugin() + + +Run() diff --git a/plugins/textcycl.py b/plugins/textcycl.py index ce33b84..41a0d5c 100644 --- a/plugins/textcycl.py +++ b/plugins/textcycl.py @@ -8,11 +8,13 @@ Cycling text on one LJ laser. LICENCE : CC ''' - +import sys,time +sys.path.append('../libs') import redis import lj3 -import sys,time + import argparse +import traceback from osc4py3.as_eventloop import * from osc4py3 import oscbuildparse @@ -24,6 +26,8 @@ OSCinPort = 8007 duration = 300 lasertext = ["TEAMLASER","FANFAN","LOLOSTER","SAM"] +PL = 0 + ''' is_py2 = sys.version[0] == '2' if is_py2: @@ -73,9 +77,10 @@ else: myIP = '127.0.0.1' -lj3.Config(redisIP,ljclient) +lj3.Config(redisIP,ljclient,"cycl") #r = redis.StrictRedis(host=redisIP, port=6379, db=0) +oscrun = True # If you want to use rgb for color : def rgb2int(r,g,b): @@ -93,29 +98,38 @@ def OSCljclient(value): ljclient = value lj3.LjClient(ljclient) + def OSCpl(value): + global PL print("Cycl got /cycl/pl with value", value) lj3.WebStatus("Cycl to pl "+ str(value)) - lj3.LjPl(value) + PL = int(value) + lj3.LjPl(PL) +# /quit dummyvalue +def quit(value): + global oscrun + + lj3.ClosePlugin() + oscrun = False + +''' # /ping def OSCping(): lj3.OSCping("cycl") -# /quit -def OSCquit(): - - lj3.OSCquit("cycl") +''' print("Cycl starting its OSC server at", myIP, "port",OSCinPort,"...") osc_startup() osc_udp_server(myIP, OSCinPort, "InPort") - -osc_method("/ping*", OSCping) -osc_method("/quit", OSCquit) +osc_method("/ping*", lj3.OSCping) +osc_method("/quit", quit) +#osc_method("/ping*", OSCping) +#osc_method("/quit", OSCquit) osc_method("/cycl/ljclient*", OSCljclient) osc_method("/cycl/pl*", OSCpl) @@ -130,12 +144,13 @@ def Run(): color = rgb2int(255,255,255) try: - while 1: + while oscrun: if timing == duration or timing == -1: message = lasertext[step] - lj3.Text(message, color, PL = 0, xpos = 300, ypos = 300, resize = 1, rotx =0, roty =0 , rotz=0) - lj3.DrawPL(0) + #print ('PL',PL) + lj3.Text(message, color, PL = PL, xpos = 300, ypos = 300, resize = 1, rotx =0, roty =0 , rotz=0) + lj3.DrawPL(PL) timing = 0 else: @@ -143,7 +158,11 @@ def Run(): if step >3: step =0 timing += 1 + lj3.OSCframe() time.sleep(0.01) + + except Exception: + traceback.print_exc() except KeyboardInterrupt: pass @@ -151,13 +170,7 @@ def Run(): finally: - WebStatus("Textcycl stop") - print("Stopping OSC...") - lj3.OSCstop() - - print ("Textcycl Stopped.") - - + quit() Run() diff --git a/visualiser-linux-nomsaa b/visualiser-linux-nomsaa index 2d8dcca..b08ebc8 100755 Binary files a/visualiser-linux-nomsaa and b/visualiser-linux-nomsaa differ diff --git a/visualiserdarwin b/visualiserdarwin deleted file mode 100755 index c1bec5f..0000000 Binary files a/visualiserdarwin and /dev/null differ diff --git a/webui/LJ.js b/webui/LJ.js index 207f87d..6383324 100644 --- a/webui/LJ.js +++ b/webui/LJ.js @@ -252,6 +252,7 @@ break; case "/simul": pl = e.data.slice(7); + //console.log(pl) pl2 = eval(pl.replace(/[()]/g, '')); break; case "/plpoi": diff --git a/webui/LJgrid.css b/webui/LJgrid.css index 32ccc0d..be41f56 100644 --- a/webui/LJgrid.css +++ b/webui/LJgrid.css @@ -1,3 +1,11 @@ + + +main { + max-width: 38rem; + padding: 1.5rem; + margin: auto; +} + .maingrid { display: grid; grid-template-columns: 900px; @@ -74,7 +82,39 @@ height: 400px; width: 400px; grid-template-columns: 66px 66px 66px 66px 66px 66px; - grid-template-rows: 30px 67px 67px 67px 30px 30px; + grid-template-rows: 67px 67px 67px 30px 30px; + background-color: #000; + justify-items: center; + align-items: center; + border-color: #445; + border-style: groove; + border-width: 1px; + grid-gap: 1px; + transition: all .3s ease; + background-color: #151515; +} +.nozoidbuttons { + display: none; + height: 400px; + width: 400px; + grid-template-columns: 195px 195px; + grid-template-rows: 70px 40px 120px 40px 1fr; + background-color: #000; + justify-items: center; + align-items: center; + border-color: #445; + border-style: groove; + border-width: 1px; + grid-gap: 1px; + transition: all .3s ease; + background-color: #151515; +} +.destmatrix { + display: grid; + height: 250px; + width: 400px; + grid-template-columns: 76px 76px 76px 76px 76px; + grid-template-rows: 30px 30px 30px 30px 30px 1Fr; background-color: #000; justify-items: center; align-items: center; @@ -146,7 +186,7 @@ .topgrid { display: grid; height: 80px; - grid-template-columns: 60px 75px 40px 60px 75px 60px 75px 60px 75px; + grid-template-columns: 80px 80px 80px 80px 60px 75px 60px 75px; background-color: #000; justify-items: center; align-items: center; diff --git a/webui/blocks/ai.html b/webui/blocks/ai.html new file mode 100644 index 0000000..5a13ebb --- /dev/null +++ b/webui/blocks/ai.html @@ -0,0 +1,42 @@ + + + + + + \ No newline at end of file diff --git a/webui/blocks/align.html b/webui/blocks/align.html new file mode 100644 index 0000000..7f92b65 --- /dev/null +++ b/webui/blocks/align.html @@ -0,0 +1,258 @@ + + + +
+ + + +
+ +
+
+ +
+
+
+ + + + + + + + +
+ + +
+ +
+
+
kPPS
+
Points
+
+
+ + +
+
+
Offset X
+
Offset Y
+
+
+
+
+ + +
+
+
Scale X
+
Scale Y
+
+
+
+
+ + +
+
+
Angle
+
Intens.
+
+
+ + + +
+ +
+
+ +
+
+
+ + + + + + + + + + + +
+ + + +
+ + +
+
+
kPPS
+
Points
+
+
+ + +
+
+
Offset X
+
Offset Y
+
+
+
+
+ + +
+
+
Scale X
+
Scale Y
+
+
+
+
+ + +
+
+
Angle
+
Intens.
+
+
+ + + +
+ +
+
+ +
+
+
+ + + + + + + + + + + +
+ + +
+ + +
+
+
kPPS
+
Points
+
+
+ + +
+
+
Offset X
+
Offset Y
+
+
+
+
+ + +
+
+
Scale X
+
Scale Y
+
+
+
+
+ + +
+
+
Angle
+
Intens.
+
+
+ + + +
+ +
+
+ +
+
+
+ + + + + + + + + + + +
+ + +
+ + +
+
+
kPPS
+
Points
+
+
+ + +
+
+
Offset X
+
Offset Y
+
+
+
+
+ + +
+
+
Scale X
+
Scale Y
+
+
+
+
+ + +
+
+
Angle
+
Intens.
+
+
+
+ +
+ +
+ + \ No newline at end of file diff --git a/webui/blocks/bank0.html b/webui/blocks/bank0.html new file mode 100644 index 0000000..659a847 --- /dev/null +++ b/webui/blocks/bank0.html @@ -0,0 +1,32 @@ + + + +
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ +
+ + diff --git a/webui/blocks/footer.html b/webui/blocks/footer.html new file mode 100644 index 0000000..f57f4ac --- /dev/null +++ b/webui/blocks/footer.html @@ -0,0 +1,257 @@ + + + + + + +
+
+
+
+
+ +
+ + + + + + + + + + + + + + + diff --git a/webui/blocks/head.html b/webui/blocks/head.html new file mode 100644 index 0000000..51c9481 --- /dev/null +++ b/webui/blocks/head.html @@ -0,0 +1,46 @@ + + + + + + + + + + LJ + + + + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/webui/blocks/lissa.html b/webui/blocks/lissa.html new file mode 100644 index 0000000..70f3b08 --- /dev/null +++ b/webui/blocks/lissa.html @@ -0,0 +1,42 @@ + + + + + + \ No newline at end of file diff --git a/webui/blocks/live.html b/webui/blocks/live.html new file mode 100644 index 0000000..78c912b --- /dev/null +++ b/webui/blocks/live.html @@ -0,0 +1,101 @@ + + + +
+ + +
+
With AI +
+
+ + +
+
+
+
+
Velocity
+
Express.
+
+
+ + +
+
+
+
+
Sens.
+
Beauty
+
+
+ + +
+
+
+
+
CC 1
+
CC 2
+
+
+ + + +
+
LISSA +
+
+ + +
+
+
+
+
Select X
+
Select Y
+
+
+ + +
+
+
+
+
FOV
+
Dist
+
+
+ + +
+
+
+
+
Offset X
+
Offset Y
+
+
+ + +
+
+
3D ROT
+
+
+
+
+
+
+
+
+
+
+
X
+
Y
+
Z
+
+
+ + \ No newline at end of file diff --git a/webui/blocks/menu.html b/webui/blocks/menu.html new file mode 100644 index 0000000..2bed6b6 --- /dev/null +++ b/webui/blocks/menu.html @@ -0,0 +1,67 @@ + + + +
+
+ + + + + + +
+
+ + Scene + + + + PL + +
+ +
+
+ + + + + +
+
+ +
+ + diff --git a/webui/blocks/nozoids.html b/webui/blocks/nozoids.html new file mode 100644 index 0000000..f6e3ad2 --- /dev/null +++ b/webui/blocks/nozoids.html @@ -0,0 +1,176 @@ + + + + +
+ + + + diff --git a/webui/blocks/planetarium.html b/webui/blocks/planetarium.html new file mode 100644 index 0000000..8e9658d --- /dev/null +++ b/webui/blocks/planetarium.html @@ -0,0 +1,77 @@ + + + + + + + \ No newline at end of file diff --git a/webui/blocks/plugins.html b/webui/blocks/plugins.html new file mode 100644 index 0000000..844e0be --- /dev/null +++ b/webui/blocks/plugins.html @@ -0,0 +1,277 @@ + + +
+ + +
+ + + + + +
+ +
+ + + + +
+ +
+ + Scene + + + + Scene + +
+ +
+ + PL + + + + + PL + +
+ + +
+ + + + +
+
+ + + Scene + + + + Scene + +
+
+ + + PL + +
+ +
+ + +
+ +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+ +
+ + +
+ + +
+ +
+ + +
+
+ +
+
+ +
+ + +
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + + + +
Selection
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+ + \ No newline at end of file diff --git a/webui/blocks/pose.html b/webui/blocks/pose.html new file mode 100644 index 0000000..3a519f9 --- /dev/null +++ b/webui/blocks/pose.html @@ -0,0 +1,69 @@ + + + + + + diff --git a/webui/blocks/run.html b/webui/blocks/run.html new file mode 100644 index 0000000..8fd22f5 --- /dev/null +++ b/webui/blocks/run.html @@ -0,0 +1,80 @@ + + + +
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ \ No newline at end of file diff --git a/webui/blocks/simuheader.html b/webui/blocks/simuheader.html new file mode 100644 index 0000000..8b06ff5 --- /dev/null +++ b/webui/blocks/simuheader.html @@ -0,0 +1,14 @@ + + +
+ + +
+ +
+ + +
+ \ No newline at end of file diff --git a/webui/blocks/title.html b/webui/blocks/title.html new file mode 100644 index 0000000..8e4331c --- /dev/null +++ b/webui/blocks/title.html @@ -0,0 +1,151 @@ + + + +
+ + +
+
+ + +
+
/on
+
+
+ + +
+ +
+
+
S
+
C
+ + +
0
+
+
+
+ + +
1
+
+
+
+ + +
2
+
+
+
+ + +
3
+
+
+
+
+ + +
+
Emergy Black
+
+
+ +
+ +
+
+ + + + + +
+ +
+ + + + + +
+
+ + + + + +
+ + +
+
+
+
+
+ + diff --git a/webui/blocks/words.html b/webui/blocks/words.html new file mode 100644 index 0000000..dd839db --- /dev/null +++ b/webui/blocks/words.html @@ -0,0 +1,86 @@ + + + + + + diff --git a/webui/build.py b/webui/build.py new file mode 100644 index 0000000..2f928bd --- /dev/null +++ b/webui/build.py @@ -0,0 +1,39 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# -*- mode: Python -*- + +""" + +Web UI index html page builder +v0.2 for LJ v0.8 + + + +LICENCE : CC +by Sam Neurohack +from /team/laser + + +""" + +blocknames = [ +'blocks/head.html', +'blocks/title.html', +'blocks/menu.html', +'blocks/align.html', +'blocks/live.html', +'blocks/simuheader.html', +'blocks/planetarium.html', +'blocks/lissa.html', +'blocks/ai.html', +'blocks/plugins.html', +'blocks/bank0.html', +'blocks/pose.html', +'blocks/words.html', +'blocks/nozoids.html', +'blocks/run.html', +'blocks/footer.html' +] +with open('index.html', 'w') as outfile: + for block in blocknames: + with open(block) as infile: + outfile.write(infile.read()) \ No newline at end of file diff --git a/webui/index.html b/webui/index.html index d229ac2..20bde9b 100644 --- a/webui/index.html +++ b/webui/index.html @@ -1,6 +1,7 @@ - + + @@ -24,21 +25,27 @@ + + + +
+ +
@@ -50,7 +57,7 @@
/on
-
+
@@ -95,6 +102,58 @@
+
+ + + + + +
+ +
+ + + + + +
+
+ + + + + +
@@ -153,7 +213,7 @@
- Virtual + Scene @@ -755,7 +820,7 @@ - Virtual + Scene
-
+
PL @@ -802,7 +869,7 @@ - Virtual + Scene
+ +
+ + +
+ +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+ +
+ + +
+ + +
+ +
+ + +
+
+ +
+
+ +
+ + +
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + + + +
Selection
+
+ +
+
+ +
+
+ +
+
+ +
+ +
- - + +
@@ -854,65 +1096,78 @@
+ + - - -
- - -
IdiotIA
-
-
-
-
-
+ + - - + - - - - -
+
+
+
@@ -1176,14 +1444,14 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
@@ -1239,9 +1507,12 @@
+ + +
@@ -1491,5 +1762,5 @@
--> - + diff --git a/webui/knobs/artnet.png b/webui/knobs/artnet.png new file mode 100644 index 0000000..b09969d Binary files /dev/null and b/webui/knobs/artnet.png differ diff --git a/webui/knobs/audiogen.png b/webui/knobs/audiogen.png new file mode 100644 index 0000000..6749764 Binary files /dev/null and b/webui/knobs/audiogen.png differ diff --git a/webui/knobs/blank.png b/webui/knobs/blank.png new file mode 100644 index 0000000..312fb39 Binary files /dev/null and b/webui/knobs/blank.png differ diff --git a/webui/knobs/close.svg b/webui/knobs/close.svg new file mode 100755 index 0000000..749b17d --- /dev/null +++ b/webui/knobs/close.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webui/knobs/fft.png b/webui/knobs/fft.png new file mode 100644 index 0000000..6174f58 Binary files /dev/null and b/webui/knobs/fft.png differ diff --git a/webui/knobs/group0.png b/webui/knobs/group0.png new file mode 100644 index 0000000..a5ac3fb Binary files /dev/null and b/webui/knobs/group0.png differ diff --git a/webui/knobs/group1.png b/webui/knobs/group1.png new file mode 100644 index 0000000..849a6e0 Binary files /dev/null and b/webui/knobs/group1.png differ diff --git a/webui/knobs/group2.png b/webui/knobs/group2.png new file mode 100644 index 0000000..98062cb Binary files /dev/null and b/webui/knobs/group2.png differ diff --git a/webui/knobs/group3.png b/webui/knobs/group3.png new file mode 100644 index 0000000..9219737 Binary files /dev/null and b/webui/knobs/group3.png differ diff --git a/webui/knobs/laser0.png b/webui/knobs/laser0.png new file mode 100644 index 0000000..4d7adeb Binary files /dev/null and b/webui/knobs/laser0.png differ diff --git a/webui/knobs/laser1.png b/webui/knobs/laser1.png new file mode 100644 index 0000000..ffbb5bc Binary files /dev/null and b/webui/knobs/laser1.png differ diff --git a/webui/knobs/laser2.png b/webui/knobs/laser2.png new file mode 100644 index 0000000..a29e6e8 Binary files /dev/null and b/webui/knobs/laser2.png differ diff --git a/webui/knobs/laser3.png b/webui/knobs/laser3.png new file mode 100644 index 0000000..9d18148 Binary files /dev/null and b/webui/knobs/laser3.png differ diff --git a/webui/knobs/mmo3.png b/webui/knobs/m3.png similarity index 100% rename from webui/knobs/mmo3.png rename to webui/knobs/m3.png diff --git a/webui/knobs/maxw.png b/webui/knobs/maxw.png new file mode 100644 index 0000000..154356a Binary files /dev/null and b/webui/knobs/maxw.png differ diff --git a/webui/knobs/midigen.png b/webui/knobs/midigen.png new file mode 100644 index 0000000..3c97c40 Binary files /dev/null and b/webui/knobs/midigen.png differ diff --git a/webui/knobs/ocs2.png b/webui/knobs/o2.png similarity index 100% rename from webui/knobs/ocs2.png rename to webui/knobs/o2.png diff --git a/webui/knobs/onoff3.png b/webui/knobs/onoff3.png new file mode 100644 index 0000000..dd96441 Binary files /dev/null and b/webui/knobs/onoff3.png differ diff --git a/webui/knobs/play.png b/webui/knobs/play.png new file mode 100644 index 0000000..e5d1219 Binary files /dev/null and b/webui/knobs/play.png differ diff --git a/webui/knobs/scene0.png b/webui/knobs/scene0.png new file mode 100644 index 0000000..25d2305 Binary files /dev/null and b/webui/knobs/scene0.png differ diff --git a/webui/knobs/scene1.png b/webui/knobs/scene1.png new file mode 100644 index 0000000..061f014 Binary files /dev/null and b/webui/knobs/scene1.png differ diff --git a/webui/knobs/scene2.png b/webui/knobs/scene2.png new file mode 100644 index 0000000..96f55fc Binary files /dev/null and b/webui/knobs/scene2.png differ diff --git a/webui/knobs/scene3.png b/webui/knobs/scene3.png new file mode 100644 index 0000000..97e827a Binary files /dev/null and b/webui/knobs/scene3.png differ diff --git a/webui/knobs/square.png b/webui/knobs/square.png new file mode 100644 index 0000000..6f7145d Binary files /dev/null and b/webui/knobs/square.png differ diff --git a/webui/knobs/stop.png b/webui/knobs/stop.png new file mode 100644 index 0000000..e194ab8 Binary files /dev/null and b/webui/knobs/stop.png differ diff --git a/webui/knobs/viewgen.png b/webui/knobs/viewgen.png new file mode 100644 index 0000000..efbc322 Binary files /dev/null and b/webui/knobs/viewgen.png differ