forked from protonphoton/LJ
3dsines experiments
This commit is contained in:
parent
4065b17303
commit
9b4800aa7b
60
README.md
60
README.md
@ -7,31 +7,37 @@ LICENCE : CC BY
|
||||
|
||||
![LJ](http://www.teamlaser.fr/thsf/images/fulls/THSF9-33.jpg)
|
||||
|
||||
A software server with gui for up to 4 lasers live actions. Think creative like Laser "battles", planetarium,...
|
||||
A software laser server with GUI for up to 4 lasers live actions. Think creative like Laser "battles", planetarium,...
|
||||
|
||||
This software is in python 2.7 but you run and write your clients separatly in any redis capable programming langage (50+ : https://redis.io/clients).
|
||||
LJ has 3 main components :
|
||||
|
||||
- A tracer per etherdream/laser that take current client point list, correct geometry, recompute in etherdreams coordinates, send it to its controller,... and report etherdream 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,...
|
||||
- Up to ten clients, that simultaneously send one point list per laser.
|
||||
|
||||
You run and write your clients in any redis capable programming langage (50+ : https://redis.io/clients).
|
||||
|
||||
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)
|
||||
|
||||
Nozosc : Semi modular synthetizers from Nozoids can send a lot of their inner sound curves and be displayed in many ways, i.e VCO 1 on X axis and LFO 2 on Y axis.
|
||||
|
||||
The server approach is based on redis. One process per etherdream is spawn : to retrieve the given point list from redis, warp, resample and manage the given etherdream dialog.
|
||||
The server approach is based on redis.
|
||||
|
||||
LJ supports Linux and OS X. Windows is unkown but welcome, if someone want to jump in and care about it.
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Features among many others.
|
||||
#
|
||||
|
||||
(Doc in progress)
|
||||
|
||||
- Automatically hook to Midi devices IN & OUT seen by OS. Very cool : LJ can script or be scripted by a midi device : Triggering different musics at given moments,... or in opposite, you can make a midi file with an external midi sequencer to script/trigger laser effects.
|
||||
- OSC and websocket commands. Very cool : LJ can script or be scripted.
|
||||
- Interactive (mouse style) warp correction for each laser.
|
||||
- Web ui : In your browser open webui/index.html. Javascript is needed.
|
||||
- Status every 0.5 seconds : every etherdream DAC state, number of buffer points sent,...
|
||||
- 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 of nannou.org etherdream+laser emulator is included. For more information https://github.com/nannou-org/ether-dream
|
||||
- A 3D anaglyph client example
|
||||
|
||||
#
|
||||
# External devices
|
||||
@ -42,36 +48,30 @@ LJ supports Linux and OS X. Windows is unkown but welcome, if someone want to ju
|
||||
|
||||
|
||||
#
|
||||
# Introduction
|
||||
# Networking
|
||||
#
|
||||
|
||||
|
||||
LJ is meant for Live, so a lot of parameters can be changed via OSC/midi, webUI,...
|
||||
LJ is network based and this is *critical and flickering reason #1* if not managed properly, especially you have several lasers.
|
||||
|
||||
This is *critical and flickering reason #1* if not managed properly, especially you have several lasers.
|
||||
Our "always working solution", as we regularly move our gear for different venues :
|
||||
|
||||
Our "always working solution" :
|
||||
We use static network configuration. Our Etherdreams controllers have static IPs defined in their SDcard from 192.168.1.1 to 192.168.1.9. Because wifi will always finally sucks for many reasons, our computers (laser server and clients) are *gigabits wired connected* with 192.168.1.10 and after. Don't trust end user gear marketing on wifi, we have a big gigabits switch for laser only stuff. We provide Internet through wifi on different network like 192.168.2.x
|
||||
|
||||
We use static network configuration, as we regularly move our gear for different venues.
|
||||
|
||||
Our Etherdreams controllers have static IPs defined in their SDcard from 192.168.1.1 to 192.168.1.9. Because wifi will always finally sucks for many reasons, our computers are *gigabits wired connected* with 192.168.1.10 and after. Don't trust end user gear marketing on wifi.
|
||||
|
||||
We have a big *laser dedicated gigabit switch*. We provide Internet through wifi on a different network address like 192.168.2.x
|
||||
|
||||
Even if etherdreams are 100 Mbits, use gigabits gear. Use gigabits gear. USE GIGABITS GEAR :)
|
||||
Even if etherdreams are 100 Mbits, we use gigabits gear.
|
||||
|
||||
|
||||
By default LJ uses on 127.0.0.1 (localhost) :
|
||||
|
||||
- A websocket on port 9001 for WebUI interaction.
|
||||
- The redis server on port 6379 ('ljayserverip')
|
||||
- An OSC server on port 8002. Incoming commands are transfered to webUI.
|
||||
- An OSC client on 'bhoroscIP' port 8001.
|
||||
- An OSC server on port 8002.
|
||||
- An OSC client for 'bhoroscIP' port 8001.
|
||||
- An OSC client for Nozoids support on 'nozoscIP', port 8003.
|
||||
|
||||
You need to update LJ.conf to your network/etherdreams IPs and be sure to check command arguments : python main.py --help
|
||||
|
||||
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.
|
||||
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,...
|
||||
|
||||
|
||||
|
||||
@ -81,7 +81,7 @@ Program your own "Client" :
|
||||
- Read the Introduction part in this readme.
|
||||
- Carefully read all comments in clients examples.
|
||||
- Generate at least one point list array (say a square).
|
||||
- Feed your point list string to redis server
|
||||
- Feed your point list array in string format to redis server.
|
||||
|
||||
|
||||
|
||||
@ -111,15 +111,14 @@ In webui/index.html change the ws ip adress to the server IP or 127.0.0.1 if cli
|
||||
|
||||
Using the same idea check all ip address in LJ.conf.
|
||||
|
||||
For network Gurus : bind to all network interface scheme is not working yet.
|
||||
|
||||
|
||||
|
||||
#
|
||||
# To run
|
||||
#
|
||||
|
||||
Always start the laser server first.
|
||||
A typical start is python main.py -L numberoflasers. Use -h to display all possible arguments.
|
||||
Always start the redis server first, then laser server, maybe those two should run on the same computer, then your client.
|
||||
|
||||
Case 1 : the laser server computer is the same that the computer running a client :
|
||||
|
||||
@ -127,7 +126,7 @@ python main.py
|
||||
|
||||
Open/reload in browser webui/index.html. (javascript must be enabled)
|
||||
|
||||
Check in your client if the server IP is the good one
|
||||
Check in your client code if the laser server IP is the good one
|
||||
|
||||
Run your client
|
||||
|
||||
@ -143,17 +142,16 @@ Say the laser server computer (running LJ) IP is 192.138.1.13, the client comput
|
||||
|
||||
On the server computer :
|
||||
edit /etc/redis/redis.conf
|
||||
use -r argument :
|
||||
python main.py -r 192.168.1.13
|
||||
|
||||
on the client computer for all features :
|
||||
run the client on client computer, like :
|
||||
|
||||
to just generate and send list points
|
||||
node testredis.js
|
||||
|
||||
to monitor redis server :
|
||||
|
||||
|
||||
redis-cli -h monitor
|
||||
redis-cli -h redisserverIP monitor
|
||||
|
||||
|
||||
|
||||
|
254
clients/3dsines.py
Normal file
254
clients/3dsines.py
Normal file
@ -0,0 +1,254 @@
|
||||
# coding=UTF-8
|
||||
|
||||
'''
|
||||
Anaglyphed cube
|
||||
|
||||
LICENCE : CC
|
||||
'''
|
||||
|
||||
import redis
|
||||
import framy
|
||||
import math
|
||||
import time
|
||||
import numpy as np
|
||||
|
||||
# IP defined in /etd/redis/redis.conf
|
||||
redisIP = '127.0.0.1'
|
||||
r = redis.StrictRedis(host=redisIP, port=6379, db=0)
|
||||
|
||||
|
||||
width = 800
|
||||
height = 600
|
||||
centerX = width / 2
|
||||
centerY = height / 2
|
||||
|
||||
fov = 256
|
||||
viewer_distance = 2.2
|
||||
|
||||
eye_spacing = 100
|
||||
nadir = 0.5
|
||||
observer_altitude = 30000
|
||||
# elevation = z coordinate
|
||||
|
||||
# 0.0 or -2000 pop out)
|
||||
map_plane_altitude = 0.0
|
||||
|
||||
samparray = [0] * 100
|
||||
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),
|
||||
(- 1.0, 1.0, 1.0),
|
||||
( 1.0, 1.0, 1.0),
|
||||
( 1.0,- 1.0, 1.0),
|
||||
(- 1.0,- 1.0, 1.0)
|
||||
]
|
||||
|
||||
# Define the vertices that compose each of the 6 faces. These numbers are
|
||||
# indices to the vertices list defined above.
|
||||
#faces = [(0,1,2,3),(1,5,6,2),(5,4,7,6),(4,0,3,7),(0,4,5,1),(3,2,6,7)]
|
||||
faces = [(0,1,2,3),(1,5,6,2),(5,4,7,6),(4,0,3,7),(0,4,5,1),(3,2,6,7)]
|
||||
|
||||
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)
|
||||
|
||||
# If you want to use rgb for color :
|
||||
def rgb2int(r,g,b):
|
||||
return int('0x%02x%02x%02x' % (r,g,b),0)
|
||||
|
||||
|
||||
def ssawtooth(samples,freq,phase):
|
||||
|
||||
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):
|
||||
|
||||
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):
|
||||
|
||||
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 shader2scrX(s):
|
||||
a1, a2 = -1,1
|
||||
b1, b2 = -width/2, width/2
|
||||
return b1 + ((s - a1) * (b2 - b1) / (a2 - a1))
|
||||
|
||||
def shader2scrY(s):
|
||||
a1, a2 = -1,1
|
||||
b1, b2 = -heigth/2, heigth/2
|
||||
return b1 + ((s - a1) * (b2 - b1) / (a2 - a1))
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
def Draw2PL():
|
||||
|
||||
Shape = []
|
||||
Left = []
|
||||
Right = []
|
||||
counter =0
|
||||
|
||||
while 1:
|
||||
Shape = []
|
||||
Left = []
|
||||
Right = []
|
||||
for fa in faces:
|
||||
#print ""
|
||||
#print "face",fa
|
||||
|
||||
for point in fa:
|
||||
#print ""
|
||||
#print "point ", point
|
||||
x = vertices[point][0]
|
||||
y = vertices[point][1]
|
||||
z = vertices[point][2]
|
||||
#print x,y,z
|
||||
#print "left",x+LeftShift(z*25),y,z, Proj(x+LeftShift(z*25),y,z)
|
||||
#print "right",x+RightShift(z*25),y,z, Proj(x+RightShift(z*25),y,z)
|
||||
|
||||
|
||||
#Shape.append(Proj(x,y,z,0,0,counter))
|
||||
Left.append( Proj(x+LeftShift(z*5),y,z,0,0,counter))
|
||||
Right.append(Proj(x+RightShift(z*5),y,z,0,0,counter))
|
||||
|
||||
#framy.PolyLineOneColor(Shape, c = white, PL = 0, closed = False)
|
||||
framy.PolyLineOneColor(Left, c = red, PL = 1, closed = False)
|
||||
framy.PolyLineOneColor(Right, c = green, PL = 2, closed = False)
|
||||
'''
|
||||
framy.rPolyLineOneColor(Shape, c = white, PL = 0, closed = False, xpos = 200, ypos = 250, resize = 1.5, rotx =0, roty =0 , rotz=0)
|
||||
framy.rPolyLineOneColor(Left, c = red, PL = 1, closed = False, xpos = 200, ypos = 250, resize = 1.5, rotx =0, roty =0 , rotz=0)
|
||||
framy.rPolyLineOneColor(Right, c = blue, PL = 2, closed = False, xpos = 200, ypos = 250, resize = 1.5, rotx =0, roty =0 , rotz=0)
|
||||
'''
|
||||
#print framy.LinesPL(0)
|
||||
#print framy.LinesPL(1)
|
||||
#print framy.LinesPL(2)
|
||||
|
||||
#counter -= 1
|
||||
#if counter >360:
|
||||
# counter =0
|
||||
|
||||
|
||||
def Draw1PL():
|
||||
|
||||
Shape = []
|
||||
Left = []
|
||||
Right = []
|
||||
counter =0
|
||||
|
||||
while 1:
|
||||
|
||||
yfactor = 10
|
||||
Left = []
|
||||
Right = []
|
||||
x = -1
|
||||
z = -0.1
|
||||
for step in y0:
|
||||
|
||||
Left.append( Proj(x+LeftShift(z*25),step/yfactor,z,0,0,0))
|
||||
Right.append(Proj(x+RightShift(z*25),step/yfactor,z,0,0,0))
|
||||
x += 0.02
|
||||
|
||||
framy.rPolyLineOneColor(Left, c = red, PL = 0, closed = False, xpos = 0, ypos = 10, resize = 1.5, rotx =0, roty =0 , rotz=0)
|
||||
framy.rPolyLineOneColor(Right, c = green, PL = 0, closed = False, xpos = 0, ypos = 10, resize = 1.5, rotx =0, roty =0 , rotz=0)
|
||||
|
||||
|
||||
|
||||
Left = []
|
||||
Right = []
|
||||
x = -1
|
||||
z = 0
|
||||
for step in y1:
|
||||
|
||||
Left.append( Proj(x+LeftShift(z*25),step/yfactor,z,0,0,0))
|
||||
Right.append(Proj(x+RightShift(z*25),step/yfactor,z,0,0,0))
|
||||
x += 0.02
|
||||
|
||||
framy.rPolyLineOneColor(Left, c = red, PL = 0, closed = False, xpos = 0, ypos = 25, resize = 1.5, rotx =0, roty =0 , rotz=0)
|
||||
framy.rPolyLineOneColor(Right, c = green, PL = 0, closed = False, xpos = 0, ypos = 25, resize = 1.5, rotx =0, roty =0 , rotz=0)
|
||||
|
||||
|
||||
|
||||
Left = []
|
||||
Right = []
|
||||
x = -1
|
||||
z = 0.1
|
||||
for step in y2:
|
||||
|
||||
Left.append( Proj(x+LeftShift(z*25),step/yfactor,z,0,0,0))
|
||||
Right.append(Proj(x+RightShift(z*25),step/yfactor,z,0,0,0))
|
||||
x += 0.02
|
||||
|
||||
framy.rPolyLineOneColor(Left, c = red, PL = 0, closed = False, xpos = 0, ypos = 50, resize = 1.5, rotx =0, roty =0 , rotz=0)
|
||||
framy.rPolyLineOneColor(Right, c = green, PL = 0, closed = False, xpos = 0, ypos = 50, resize = 1.5, rotx =0, roty =0 , rotz=0)
|
||||
|
||||
|
||||
|
||||
|
||||
'''
|
||||
framy.rPolyLineOneColor(Shape, c = white, PL = 0, closed = False, xpos = 200, ypos = 250, resize = 1.5, rotx =0, roty =0 , rotz=0)
|
||||
framy.rPolyLineOneColor(Left, c = red, PL = 1, closed = False, xpos = 200, ypos = 250, resize = 1.5, rotx =0, roty =0 , rotz=0)
|
||||
framy.rPolyLineOneColor(Right, c = blue, PL = 2, closed = False, xpos = 200, ypos = 250, resize = 1.5, rotx =0, roty =0 , rotz=0)
|
||||
'''
|
||||
framy.LinesPL(0)
|
||||
time.sleep(0.1)
|
||||
|
||||
white = rgb2int(255,255,255)
|
||||
red = rgb2int(255,0,0)
|
||||
blue = rgb2int(0,0,255)
|
||||
green = rgb2int(0,255,0)
|
||||
|
||||
y0 = ssine(100,5,-0.5)
|
||||
y1 = ssine(100,5,0)
|
||||
y2 = ssine(100,5,0.5)
|
||||
|
||||
Draw1PL()
|
62
commands.py
62
commands.py
@ -9,6 +9,68 @@ LICENCE : CC
|
||||
by Sam Neurohack, Loloster,
|
||||
from /team/laser
|
||||
|
||||
Commands reference. Use commands from websocket (webUI) or OSC, do not set values in redis directly except for /pl.
|
||||
|
||||
/scale/X/lasernumber value
|
||||
/scale/Y/lasernumber value
|
||||
|
||||
/client or note on < 8 : change client displayed for Current Laser
|
||||
23 < /noteon < 32 : PL number displayed on webUI simulator
|
||||
|
||||
/grid/lasernumber value (0 or 1) : switch given laser with grid display on or off
|
||||
|
||||
/black/lasernumber value (0 or 1) : set given laser to black on or off
|
||||
|
||||
/ip/lasernumber value : change given laser IP i.e '192.168.1.1'
|
||||
|
||||
/kpps/lasernumber value
|
||||
Live change of kpps is not implemented in newdac.py. Change will effect next startup.
|
||||
|
||||
/angle/lasernumber value : increase/decrease angle correction for given laser by value
|
||||
|
||||
/intens/lasernumber value : increase/decrease intensity for given laser by value
|
||||
|
||||
/resampler/lasernumber lsteps : change resampling strategy (glitch art) for given laser
|
||||
lsteps is a string like "[ (1.0, 8),(0.25, 3), (0.75, 3), (1.0, 10)]"
|
||||
|
||||
/mouse/lasernumber value (0 or 1)
|
||||
|
||||
/swap/X/lasernumber value (0 or 1)
|
||||
/swap/Y/lasernumber value (0 or 1)
|
||||
|
||||
/loffset/X/lasernumber value : change X offset of given laser by value
|
||||
/loffset/Y/lasernumber value : change Y offset of given laser by value
|
||||
|
||||
/order value : instruct tracer what to do.
|
||||
|
||||
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
|
||||
3 : display grid
|
||||
4 : resampler
|
||||
5 : pull in redis a new client key
|
||||
|
||||
|
||||
/pl/clientnumber/lasernumber value : value is the pointlist to draw as string type. For string format see code in clients directory.
|
||||
|
||||
Example : client 0 send 2 point lists one for laser 0 and one for laser 1 by sending in redis :
|
||||
/pl/0/0 and /pl/0/1
|
||||
The "client key" when client 0 is selected to be displayed by lasers is "/pl/0/".
|
||||
Each tracer pull its pointlist by using the current client key "/pl/0/"
|
||||
and add its laser number at startup : /pl0/0 ant /pl/0/1
|
||||
|
||||
"Client" is a concept. Imagine in a demoparty there is 4 lasers.
|
||||
John and Paul want to draw on all lasers.
|
||||
Let's give John client 0, he will send points to /pl/0/0, /pl/0/1, /pl/0/2 and /pl/0/3.
|
||||
|
||||
Paul is client 1, so he will use /pl/1/0, /pl/1/1, /pl/1/2 and /pl/1/3.
|
||||
|
||||
Both can send their pointlists to redis server.
|
||||
When John get the lasers switch to client 0, when it's Paul turn switch to client 1.
|
||||
|
||||
But say Bob and Lisa needs only 2 lasers each. Give them client 2.
|
||||
Bob could use /pl/2/0 and /pl/2/1 and Lisa could use /pl/2/2 and /pl/2/3.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
@ -16,7 +16,7 @@ Init call with a laser number and which point list to draw. Etherdream IP is fou
|
||||
Uses redis keys value for live inputs/outputs
|
||||
These redis keys are read and set at each main loop.
|
||||
|
||||
Live inputs :
|
||||
Redis keys pulled to draw things :
|
||||
/order select some change to adjust
|
||||
/pl/lasernumber [(x,y,color),(x1,y1,color),...] A string of list of pygame points list.
|
||||
/resampler/lasernumber [(1.0,8), (0.25,3),(0.75,3),(1.0,10)] : a string for resampling rules.
|
||||
@ -24,10 +24,11 @@ Live inputs :
|
||||
(0.25,3),(0.75,3),(1.0,10) for long line > 4000
|
||||
i.e (0.25,3) means go at 25% position on the line, send 3 times this position to etherdream
|
||||
|
||||
Live ouputs :
|
||||
Etherdream status reports in redis keys:
|
||||
/lstt/lasernumber value etherdream last_status.playback_state (0: idle 1: prepare 2: playing)
|
||||
/cap/lasernumber number of empty points sent to fill etherdream buffer (up to 1799)
|
||||
/cap/lasernumber value number of empty points sent to fill etherdream buffer (up to 1799)
|
||||
/lack/lasernumber value "a": ACK "F": Full "I": invalid. 64 or 35 for no connection.
|
||||
|
||||
Geometric corrections :
|
||||
|
||||
'''
|
||||
|
Loading…
Reference in New Issue
Block a user