1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 from ConfigParser import ConfigParser
24 from OpenGL.GL import *
25 import math
26 import Log
27 import Theme
28
30 """
31 A graphical stage layer that can have a number of animation effects associated with it.
32 """
34 """
35 Constructor.
36
37 @param stage: Containing Stage
38 @param drawing: SvgDrawing for this layer. Make sure this drawing is rendered to
39 a texture for performance reasons.
40 """
41 self.stage = stage
42 self.drawing = drawing
43 self.position = (0.0, 0.0)
44 self.angle = 0.0
45 self.scale = (1.0, 1.0)
46 self.color = (1.0, 1.0, 1.0, 1.0)
47 self.srcBlending = GL_SRC_ALPHA
48 self.dstBlending = GL_ONE_MINUS_SRC_ALPHA
49 self.effects = []
50
52 """
53 Render the layer.
54
55 @param visibility: Floating point visibility factor (1 = opaque, 0 = invisibile)
56 """
57 w, h, = self.stage.engine.view.geometry[2:4]
58 v = 1.0 - visibility ** 2
59 self.drawing.transform.reset()
60 self.drawing.transform.translate(w / 2, h / 2)
61 if v > .01:
62 self.color = (self.color[0], self.color[1], self.color[2], visibility)
63 if self.position[0] < -.25:
64 self.drawing.transform.translate(-v * w, 0)
65 elif self.position[0] > .25:
66 self.drawing.transform.translate(v * w, 0)
67 self.drawing.transform.scale(self.scale[0], -self.scale[1])
68 self.drawing.transform.translate(self.position[0] * w / 2, -self.position[1] * h / 2)
69 self.drawing.transform.rotate(self.angle)
70
71
72 for effect in self.effects:
73 effect.apply()
74
75 glBlendFunc(self.srcBlending, self.dstBlending)
76 self.drawing.draw(color = self.color)
77 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
78
80 """
81 An animationn effect that can be attached to a Layer.
82 """
84 """
85 Constructor.
86
87 @param layer: Layer to attach this effect to.
88 @param options: Effect options (default in parens):
89 intensity - Floating point effect intensity (1.0)
90 trigger - Effect trigger, one of "none", "beat",
91 "quarterbeat", "pick", "miss" ("none")
92 period - Trigger period in ms (200.0)
93 delay - Trigger delay in periods (0.0)
94 profile - Trigger profile, one of "step", "linstep",
95 "smoothstep"
96 """
97 self.layer = layer
98 self.stage = layer.stage
99 self.intensity = float(options.get("intensity", 1.0))
100 self.trigger = getattr(self, "trigger" + options.get("trigger", "none").capitalize())
101 self.period = float(options.get("period", 500.0))
102 self.delay = float(options.get("delay", 0.0))
103 self.triggerProf = getattr(self, options.get("profile", "linstep"))
104
107
110
112 if not self.stage.lastBeatPos:
113 return 0.0
114 t = self.stage.pos - self.delay * self.stage.beatPeriod - self.stage.lastBeatPos
115 return self.intensity * (1.0 - self.triggerProf(0, self.stage.beatPeriod, t))
116
118 if not self.stage.lastQuarterBeatPos:
119 return 0.0
120 t = self.stage.pos - self.delay * (self.stage.beatPeriod / 4) - self.stage.lastQuarterBeatPos
121 return self.intensity * (1.0 - self.triggerProf(0, self.stage.beatPeriod / 4, t))
122
124 if not self.stage.lastPickPos:
125 return 0.0
126 t = self.stage.pos - self.delay * self.period - self.stage.lastPickPos
127 return self.intensity * (1.0 - self.triggerProf(0, self.period, t))
128
130 if not self.stage.lastMissPos:
131 return 0.0
132 t = self.stage.pos - self.delay * self.period - self.stage.lastMissPos
133 return self.intensity * (1.0 - self.triggerProf(0, self.period, t))
134
135 - def step(self, threshold, x):
136 return (x > threshold) and 1 or 0
137
139 if x < min:
140 return 0
141 if x > max:
142 return 1
143 return (x - min) / (max - min)
144
146 if x < min:
147 return 0
148 if x > max:
149 return 1
150 def f(x):
151 return -2 * x**3 + 3*x**2
152 return f((x - min) / (max - min))
153
155 return math.cos(math.pi * (1.0 - self.linstep(min, max, x)))
156
169
176
178 if len(self.stage.averageNotes) < self.lightNumber + 2:
179 self.layer.color = (0.0, 0.0, 0.0, 0.0)
180 return
181
182 t = self.trigger()
183 t = self.ambient + self.contrast * t
184 c = self.getNoteColor(self.stage.averageNotes[self.lightNumber])
185 self.layer.color = (c[0] * t, c[1] * t, c[2] * t, self.intensity)
186
191
193 if not self.stage.lastMissPos:
194 return
195
196 t = self.trigger()
197 self.layer.drawing.transform.rotate(t * self.angle)
198
205
207 t = self.trigger()
208
209 w, h = self.stage.engine.view.geometry[2:4]
210 p = t * 2 * math.pi * self.freq
211 s, c = t * math.sin(p), t * math.cos(p)
212 self.layer.drawing.transform.translate(self.xmag * w * s, self.ymag * h * c)
213
223
225 - def __init__(self, guitarScene, configFileName):
226 self.scene = guitarScene
227 self.engine = guitarScene.engine
228 self.config = ConfigParser()
229 self.backgroundLayers = []
230 self.foregroundLayers = []
231 self.textures = {}
232 self.reset()
233
234 self.config.read(configFileName)
235
236
237 for i in range(32):
238 section = "layer%d" % i
239 if self.config.has_section(section):
240 def get(value, type = str, default = None):
241 if self.config.has_option(section, value):
242 return type(self.config.get(section, value))
243 return default
244
245 xres = get("xres", int, 256)
246 yres = get("yres", int, 256)
247 texture = get("texture")
248
249 try:
250 drawing = self.textures[texture]
251 except KeyError:
252 drawing = self.engine.loadSvgDrawing(self, None, texture, textureSize = (xres, yres))
253 self.textures[texture] = drawing
254
255 layer = Layer(self, drawing)
256
257 layer.position = (get("xpos", float, 0.0), get("ypos", float, 0.0))
258 layer.scale = (get("xscale", float, 1.0), get("yscale", float, 1.0))
259 layer.angle = math.pi * get("angle", float, 0.0) / 180.0
260 layer.srcBlending = globals()["GL_%s" % get("src_blending", str, "src_alpha").upper()]
261 layer.dstBlending = globals()["GL_%s" % get("dst_blending", str, "one_minus_src_alpha").upper()]
262 layer.color = (get("color_r", float, 1.0), get("color_g", float, 1.0), get("color_b", float, 1.0), get("color_a", float, 1.0))
263
264
265 fxClasses = {
266 "light": LightEffect,
267 "rotate": RotateEffect,
268 "wiggle": WiggleEffect,
269 "scale": ScaleEffect,
270 }
271
272 for j in range(32):
273 fxSection = "layer%d:fx%d" % (i, j)
274 if self.config.has_section(fxSection):
275 type = self.config.get(fxSection, "type")
276
277 if not type in fxClasses:
278 continue
279
280 options = self.config.options(fxSection)
281 options = dict([(opt, self.config.get(fxSection, opt)) for opt in options])
282
283 fx = fxClasses[type](layer, options)
284 layer.effects.append(fx)
285
286 if get("foreground", int):
287 self.foregroundLayers.append(layer)
288 else:
289 self.backgroundLayers.append(layer)
290
292 self.lastBeatPos = None
293 self.lastQuarterBeatPos = None
294 self.lastMissPos = None
295 self.lastPickPos = None
296 self.beat = 0
297 self.quarterBeat = 0
298 self.pos = 0.0
299 self.playedNotes = []
300 self.averageNotes = [0.0]
301 self.beatPeriod = 0.0
302
304 if notes:
305 self.lastPickPos = pos
306 self.playedNotes = self.playedNotes[-3:] + [sum(notes) / float(len(notes))]
307 self.averageNotes[-1] = sum(self.playedNotes) / float(len(self.playedNotes))
308
310 self.lastMissPos = pos
311
313 self.lastQuarterBeatPos = pos
314 self.quarterBeat = quarterBeat
315
317 self.lastBeatPos = pos
318 self.beat = beat
319 self.averageNotes = self.averageNotes[-4:] + self.averageNotes[-1:]
320
328
329 - def run(self, pos, period):
330 self.pos = pos
331 self.beatPeriod = period
332 quarterBeat = int(4 * pos / period)
333
334 if quarterBeat > self.quarterBeat:
335 self.triggerQuarterBeat(pos, quarterBeat)
336
337 beat = quarterBeat / 4
338
339 if beat > self.beat:
340 self.triggerBeat(pos, beat)
341
342 - def render(self, visibility):
346