diff --git a/bin/effectsequencer b/bin/effectsequencer --- a/bin/effectsequencer +++ b/bin/effectsequencer @@ -12,7 +12,7 @@ from greplin import scales import optparse, sys, logging import cyclone.web from rdflib import URIRef -from light9.effect.sequencer import Sequencer, sendToCollector +from light9.effect.sequencer import Sequencer, sendToCollector, Updates from light9 import clientsession class App(object): @@ -38,9 +38,13 @@ class App(object): settings)) self.cycloneApp = cyclone.web.Application(handlers=[ + (r'/()', cyclone.web.StaticFileHandler, + {"path" : "light9/effect/", "default_filename" : "sequencer.html"}), + (r'/updates', Updates), (r'/stats', StatsForCyclone), ], debug=True, + seq=self.seq, graph=self.graph, stats=self.stats) reactor.listenTCP(networking.effectSequencer.port, self.cycloneApp) diff --git a/light9/effect/sequencer.html b/light9/effect/sequencer.html new file mode 100644 --- /dev/null +++ b/light9/effect/sequencer.html @@ -0,0 +1,167 @@ + + + + effect sequencer + + + + + + + + + + + + + + + + + + + + + + + + + + + list notes of song, time with note, eff attrs, function stuff + update rates, stutters from reloads. + + + diff --git a/light9/effect/sequencer.py b/light9/effect/sequencer.py --- a/light9/effect/sequencer.py +++ b/light9/effect/sequencer.py @@ -12,6 +12,7 @@ import math import time from twisted.internet.inotify import INotify from twisted.python.filepath import FilePath +from louie import dispatcher from light9 import networking from light9.namespaces import L9, RDF @@ -131,15 +132,21 @@ class Note(object): def outputSettings(self, t): """ - list of (device, attr, value) + list of (device, attr, value), and a report for web """ + report = {'note': str(self.uri)} effectSettings = self.baseEffectSettings.copy() effectSettings[L9['strength']] = self.evalCurve(t) - return self.effectEval.outputFromEffect( + report['effectSettings'] = dict( + (str(k), str(v)) + for k,v in sorted(effectSettings.items())) + out = self.effectEval.outputFromEffect( effectSettings.items(), songTime=t, # note: not using origin here since it's going away noteTime=t - self.points[0][0]) + print 'out', out.asList() + return out, report class CodeWatcher(object): @@ -207,19 +214,54 @@ class Sequencer(object): self.recentUpdateTimes = self.recentUpdateTimes[-20:] + [now] stats.recentFps = len(self.recentUpdateTimes) / (self.recentUpdateTimes[-1] - self.recentUpdateTimes[0] + .0001) if now > self.lastStatLog + 10: - log.info("%.2f fps", stats.recentFps) + dispatcher.send('state', update={'recentFps': stats.recentFps}) self.lastStatLog = now reactor.callLater(1 / self.fps, self.update) musicState = self.music.getLatest() song = URIRef(musicState['song']) if musicState.get('song') else None + dispatcher.send('state', update={'song': str(song)}) if 't' not in musicState: return t = musicState['t'] settings = [] + songNotes = sorted(self.notes.get(song, [])) + noteReports = [] + for note in songNotes: + s, report = note.outputSettings(t) + noteReports.append(report) + settings.append(s) + dispatcher.send('state', update={'songNotes': noteReports}) + self.sendToCollector(DeviceSettings.fromList(self.graph, settings)) + +import cyclone.sse +class Updates(cyclone.sse.SSEHandler): + def __init__(self, application, request, **kwargs): + cyclone.sse.SSEHandler.__init__(self, application, request, + **kwargs) + self.state = {} + dispatcher.connect(self.updateState, 'state') + self.numConnected = 0 + + def updateState(self, update): + self.state.update(update) - for note in self.notes.get(song, []): - settings.append(note.outputSettings(t)) - self.sendToCollector(DeviceSettings.fromList(self.graph, settings)) + def bind(self): + print 'new client', self.settings.seq + self.numConnected += 1 + + if self.numConnected == 1: + self.loop() + + def loop(self): + if self.numConnected == 0: + return + self.sendEvent(self.state) + reactor.callLater(2, self.loop) + + def unbind(self): + self.numConnected -= 1 + print 'bye', self.numConnected +