More plugins, more doc,...
This commit is contained in:
parent
4a2d1a5773
commit
0bb0049f02
82
LJ.conf
82
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"}
|
||||
}
|
||||
|
||||
|
128
README.md
128
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,...
|
||||
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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)
|
BIN
libs/.DS_Store
vendored
Normal file
BIN
libs/.DS_Store
vendored
Normal file
Binary file not shown.
289
libs/LPD8.py
Normal file
289
libs/LPD8.py
Normal file
@ -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()
|
2873
libs/OSC3.py
Executable file
2873
libs/OSC3.py
Executable file
File diff suppressed because it is too large
Load Diff
0
libs/__init__.py
Normal file
0
libs/__init__.py
Normal file
BIN
libs/__init__.pyc
Normal file
BIN
libs/__init__.pyc
Normal file
Binary file not shown.
364
libs/artnet.py
Normal file
364
libs/artnet.py
Normal file
@ -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)
|
||||
'''
|
292
libs/audio.py
Executable file
292
libs/audio.py
Executable file
@ -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( |