More plugins, more doc,...

This commit is contained in:
leduc 2019-08-06 03:08:54 +02:00
parent 4a2d1a5773
commit 0bb0049f02
105 changed files with 15152 additions and 2757 deletions

82
LJ.conf
View File

@ -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
View File

@ -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,...

View File

@ -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):

View File

@ -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

Binary file not shown.

289
libs/LPD8.py Normal file
View 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

File diff suppressed because it is too large Load Diff

0
libs/__init__.py Normal file
View File

BIN
libs/__init__.pyc Normal file

Binary file not shown.

364
libs/artnet.py Normal file
View 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
View 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(