summaryrefslogtreecommitdiff
path: root/dmx
diff options
context:
space:
mode:
Diffstat (limited to 'dmx')
-rw-r--r--dmx/dmxcontrol.py55
-rw-r--r--dmx/dmxeffects.py270
2 files changed, 325 insertions, 0 deletions
diff --git a/dmx/dmxcontrol.py b/dmx/dmxcontrol.py
new file mode 100644
index 0000000..e214aec
--- /dev/null
+++ b/dmx/dmxcontrol.py
@@ -0,0 +1,55 @@
+
+# rms = float(open('/tmp/rms', 'r').read()) / 20000.
+# prevrms = prevrms * 0.64 + rms * 0.4
+# print prevrms
+
+gamountmul = 0.2 # 1.4
+gmul = 0.4
+
+effects[0].camount = 0.22 * gamountmul
+effects[0].cphase = 0.0
+effects[0].jumpdecayset = 0.05 #0.49 # 0.19
+effects[0].jumpdecayfall = 0.90
+effects[0].jumpv = 0.12
+universe[0].mult = 0.55 * gmul
+
+effects[1].camount = 0.1 * gamountmul
+effects[1].cphase = 0.5
+effects[1].phasejump = 0.03 # 0.084
+effects[1].jumpdecayset = 0.1
+effects[1].jumpdecayfall = 0.90
+effects[1].jumpv = 0.2
+universe[1].mult = 0.55 * gmul
+
+effects[2].camount = 0.12 * gamountmul
+effects[2].cphase = 0.5
+effects[2].phasejump = 0.12
+effects[2].jumpdecayset = 0.36 # 0.19
+effects[2].jumpdecayfall = 0.955
+effects[2].jumpv = 0.3 #1.5
+universe[2].mult = 0.3 * gmul # + prevrms
+
+effects[3].camount = 0.10 * gamountmul
+effects[3].cphase = 0.0
+effects[3].phasejump = 0.12 # 0.084
+effects[3].jumpdecayset = 0.39 # 0.27
+effects[3].jumpdecayfall = 0.955
+effects[3].jumpv = 0.5 #4.23
+universe[3].mult = 0.3 * gmul # + prevrms
+
+effects[4].camount = 0.12 * gamountmul
+effects[4].phasejump = 0.22
+effects[4].cphase = 0.5
+effects[4].jumpdecayset = 0.6
+effects[4].jumpdecayfall = 0.95
+effects[4].jumpv = 0.9
+universe[4].mult = 0.32 * gmul
+
+effects[4].r_phasejump += 0.0004
+
+# static
+#for e in effects[:3]:
+# e.fadespeed = 0.0
+# e.r_phasejump = 0.09
+
+# bpm = 106.
diff --git a/dmx/dmxeffects.py b/dmx/dmxeffects.py
new file mode 100644
index 0000000..ba6c188
--- /dev/null
+++ b/dmx/dmxeffects.py
@@ -0,0 +1,270 @@
+#!/usr/bin/env python
+# vim: set expandtab ts=4:
+
+import sys
+import array, os, os.path
+import colorsys
+import traceback
+from time import time, sleep
+from math import sin, cos, pi
+from ola.ClientWrapper import ClientWrapper
+
+wrapper = None
+loop_count = 0
+TICK_INTERVAL = 10 # in ms
+universenum = 0
+
+enable = '--enable' in sys.argv[1:]
+
+phase = 0.0
+bpm = 120.0
+tl = time()
+beatsep = 0
+
+def ctlpath(path):
+ return os.path.join('/tmp', path)
+
+# seamless restart
+try:
+ phase = float(open(ctlpath('.dmxeffects'), 'r').read())
+except Exception, e:
+ traceback.print_exc()
+
+def DmxSent(state):
+ if not state.Succeeded():
+ wrapper.Stop()
+
+#
+# minimal abstraction... not really used much
+#
+class DMXTarget(object):
+ pass
+
+class DMXLEDPAR(DMXTarget):
+
+ # NB: relative color adjustments hardcoded here!
+ def __init__(self, baseaddr, mult = 1.0, max_r = 255, max_g = 144, max_b = 180):
+ self.baseaddr = baseaddr
+ self.r = 0.
+ self.g = 0.
+ self.b = 0.
+ self.max_r = max_r
+ self.max_g = max_g
+ self.max_b = max_b
+ self.mult = mult
+
+ def getdata(self):
+ return (self.baseaddr, [0,
+ max(min(int(self.r * self.mult * self.max_r), 255), 0),
+ max(min(int(self.g * self.mult * self.max_g), 255), 0),
+ max(min(int(self.b * self.mult * self.max_b), 255), 0),
+ 0])
+
+class DMXUniverse(list):
+ def __init__(self, uniid):
+ list.__init__(self)
+ self.uniid = uniid
+
+ def mergedata(self):
+ data = array.array('B')
+ for dev in self:
+ devdata = dev.getdata()
+ lastpos = devdata[0] + len(devdata[1])
+ if len(data) < lastpos:
+ data.extend([0] * (lastpos - len(data)))
+ for i in range(devdata[0], lastpos):
+ data[i] = devdata[1][i - devdata[0]]
+ return data[1:]
+
+ def send(self, wrapper):
+ md = self.mergedata()
+ if enable:
+ wrapper.Client().SendDmx(self.uniid, md, DmxSent)
+ else:
+ print ' '.join(['%02x' % i for i in md])
+
+#
+# actual effect code
+#
+class EffectRGBFadeJump(object):
+ def __init__(self, target, fadespeed = 1./120., phasejump = 0.0):
+ self.target = target
+ self.phasejump = phasejump
+ self.fadespeed = fadespeed
+ self.control = 1.0
+ self.camount = 0.0
+ self.cphase = 0.0
+ self.jumpv = 0.0
+ self.jumpdecayset = 0.0
+ self.jumpdecayfall = 0.9
+
+ self.r_lastphase = 0
+ self.r_phasejump = 0.0
+ self.r_jumpdecay = 0.0
+
+ def update(self, timestamp):
+ curphase = beatsep # int(timestamp)
+ if self.r_lastphase != curphase:
+ self.r_phasejump += self.phasejump
+ self.r_lastphase = curphase
+ self.r_jumpdecay = self.jumpdecayset
+
+ phase = (self.r_phasejump + timestamp * self.fadespeed) % 1
+
+ mixcorr = (cos((phase * 3) * 2*pi) - 1. / 2.) * 0.33
+
+ (self.target.r, self.target.g, self.target.b) = \
+ colorsys.hsv_to_rgb(phase,
+ 1.0 - self.r_jumpdecay,
+ (0.8 + mixcorr) * self.control + self.r_jumpdecay * self.jumpv)
+ self.r_jumpdecay *= self.jumpdecayfall
+
+
+class EffectEffectCross(object):
+ def __init__(self, group, fadespeed = 1./2., phasejump = 0.0):
+ self.group = group
+ self.phasejump = phasejump
+ self.fadespeed = fadespeed
+ self.control = 1.0
+
+ self.r_lastphase = 0
+ self.r_phasejump = 0.0
+
+ def update(self, timestamp):
+ curphase = beatsep # int(timestamp)
+ if self.r_lastphase != curphase:
+ self.r_phasejump += self.phasejump
+ self.r_lastphase = curphase
+
+ phase = (self.r_phasejump + timestamp * self.fadespeed) % 1
+ for e in self.group:
+ e.control = sin((phase + e.cphase) * 2 * pi) * e.camount + 1.0
+
+#
+# deployed setup
+#
+
+universe = DMXUniverse(universenum)
+universe.append(DMXLEDPAR( 1, 0.55))
+universe.append(DMXLEDPAR( 6, 0.55))
+universe.append(DMXLEDPAR(11, 0.55))
+universe.append(DMXLEDPAR(16, 0.55))
+universe.append(DMXLEDPAR(21, 0.55))
+
+effects = [
+ EffectRGBFadeJump(universe[0]),
+ EffectRGBFadeJump(universe[1]),
+ EffectRGBFadeJump(universe[2]), # , fadespeed = 1./12., phasejump = 0.1),
+ EffectRGBFadeJump(universe[3]),
+ EffectRGBFadeJump(universe[4]),
+]
+
+effects.append(
+ EffectEffectCross([effects[0], effects[1], effects[2], effects[3], effects[4]])
+)
+
+#
+# control loop
+#
+
+lastbeat = 0.0
+freerun = False
+nobeat = True
+lastphase = 0.0
+bpmhist = []
+prevrms = 0.0
+
+def SendDMXFrame():
+ global phase, tl, beatsep, lastbeat, bpm, bpmhist, freerun, nobeat, lastphase, prevrms
+ wrapper.AddEvent(TICK_INTERVAL, SendDMXFrame)
+
+ t = time()
+ dt = t - tl
+ tl = t
+ phase += dt * bpm / 60.
+
+ beat = False
+ try:
+ if os.access(ctlpath('.dmxbeat'), os.R_OK):
+ os.unlink(ctlpath('.dmxbeat'))
+ freerun = False
+ nobeat = False
+ beatsep += 1
+ dbeat = t - lastbeat
+ if 0.20 < dbeat < 0.90:
+ nowbpm = 60. / dbeat
+ bpmhist.insert(0, nowbpm)
+ bpmhist = bpmhist[:10]
+ bpmavg = sum(bpmhist) / len(bpmhist)
+ print 'bpm: %6.1f ' % (bpmavg), ', '.join(['%6.1f' % i for i in bpmhist])
+ bpm = bpmavg
+ lastbeat = t
+ lastphase = phase
+ beat = True
+
+ phasephase = phase - int(phase)
+ if phasephase > 0.5:
+ phasephase -= 1.0
+ print 'phph', phasephase
+ phase -= phasephase * 0.05
+ except Exception, e:
+ traceback.print_exc()
+
+ try:
+ if os.access(ctlpath('.dmxfreebeat'), os.R_OK):
+ os.unlink(ctlpath('.dmxfreebeat'))
+ freerun = True
+ except Exception, e:
+ traceback.print_exc()
+
+ try:
+ if os.access(ctlpath('.dmxnobeat'), os.R_OK):
+ os.unlink(ctlpath('.dmxnobeat'))
+ nobeat = True
+ freerun = False
+ except Exception, e:
+ traceback.print_exc()
+
+ if t - lastbeat > (60. / bpm) * 1.8 and not nobeat:
+ pass
+ #if not freerun:
+ # print 'freerun'
+ #freerun = True
+ if freerun:
+ if phase - lastphase >= 1.0:
+ lastphase = phase
+ beatsep += 1
+ beat = True
+ print 'freebeat', bpm
+
+ try:
+ execfile('dmxcontrol.py')
+ except Exception, e:
+ traceback.print_exc()
+
+ for e in effects:
+ e.update(phase)
+
+ universe.send(wrapper)
+
+class DummyVerse(object):
+ def __init__(self):
+ pass
+ def Client(self):
+ return self
+ def SendDmx(self, uniid, data, cb):
+ print uniid, data
+ def Run(self):
+ while True:
+ sleep(1./30.)
+ self.func()
+ def AddEvent(self, ignore, func):
+ self.func = func
+
+#wrapper = DummyVerse()
+wrapper = ClientWrapper()
+wrapper.AddEvent(TICK_INTERVAL, SendDMXFrame)
+try:
+ wrapper.Run()
+except KeyboardInterrupt:
+ file(ctlpath('.dmxeffects'), 'w').write(str(phase))