Module Stage
[hide private]
[frames] | no frames]

Source Code for Module Stage

  1  ##################################################################### 
  2  # -*- coding: iso-8859-1 -*-                                        # 
  3  #                                                                   # 
  4  # Frets on Fire                                                     # 
  5  # Copyright (C) 2006 Sami Kyöstilä                                  # 
  6  #                                                                   # 
  7  # This program is free software; you can redistribute it and/or     # 
  8  # modify it under the terms of the GNU General Public License       # 
  9  # as published by the Free Software Foundation; either version 2    # 
 10  # of the License, or (at your option) any later version.            # 
 11  #                                                                   # 
 12  # This program is distributed in the hope that it will be useful,   # 
 13  # but WITHOUT ANY WARRANTY; without even the implied warranty of    # 
 14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     # 
 15  # GNU General Public License for more details.                      # 
 16  #                                                                   # 
 17  # You should have received a copy of the GNU General Public License # 
 18  # along with this program; if not, write to the Free Software       # 
 19  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,        # 
 20  # MA  02110-1301, USA.                                              # 
 21  ##################################################################### 
 22   
 23  from ConfigParser import ConfigParser 
 24  from OpenGL.GL import * 
 25  import math 
 26  import Log 
 27  import Theme 
 28   
29 -class Layer(object):
30 """ 31 A graphical stage layer that can have a number of animation effects associated with it. 32 """
33 - def __init__(self, stage, drawing):
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
51 - def render(self, visibility):
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 # Blend in all the effects 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
79 -class Effect(object):
80 """ 81 An animationn effect that can be attached to a Layer. 82 """
83 - def __init__(self, layer, options):
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
105 - def apply(self):
106 pass
107
108 - def triggerNone(self):
109 return 0.0
110
111 - def triggerBeat(self):
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
117 - def triggerQuarterbeat(self):
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
123 - def triggerPick(self):
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
129 - def triggerMiss(self):
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
138 - def linstep(self, min, max, x):
139 if x < min: 140 return 0 141 if x > max: 142 return 1 143 return (x - min) / (max - min)
144
145 - def smoothstep(self, min, max, x):
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
154 - def sinstep(self, min, max, x):
155 return math.cos(math.pi * (1.0 - self.linstep(min, max, x)))
156
157 - def getNoteColor(self, note):
158 if note >= len(Theme.fretColors) - 1: 159 return Theme.fretColors[-1] 160 elif note <= 0: 161 return Theme.fretColors[0] 162 f2 = note % 1.0 163 f1 = 1.0 - f2 164 c1 = Theme.fretColors[int(note)] 165 c2 = Theme.fretColors[int(note) + 1] 166 return (c1[0] * f1 + c2[0] * f2, \ 167 c1[1] * f1 + c2[1] * f2, \ 168 c1[2] * f1 + c2[2] * f2)
169
170 -class LightEffect(Effect):
171 - def __init__(self, layer, options):
172 Effect.__init__(self, layer, options) 173 self.lightNumber = int(options.get("light_number", 0)) 174 self.ambient = float(options.get("ambient", 0.5)) 175 self.contrast = float(options.get("contrast", 0.5))
176
177 - def apply(self):
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
187 -class RotateEffect(Effect):
188 - def __init__(self, layer, options):
189 Effect.__init__(self, layer, options) 190 self.angle = math.pi / 180.0 * float(options.get("angle", 45))
191
192 - def apply(self):
193 if not self.stage.lastMissPos: 194 return 195 196 t = self.trigger() 197 self.layer.drawing.transform.rotate(t * self.angle)
198
199 -class WiggleEffect(Effect):
200 - def __init__(self, layer, options):
201 Effect.__init__(self, layer, options) 202 self.freq = float(options.get("frequency", 6)) 203 self.xmag = float(options.get("xmagnitude", 0.1)) 204 self.ymag = float(options.get("ymagnitude", 0.1))
205
206 - def apply(self):
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
214 -class ScaleEffect(Effect):
215 - def __init__(self, layer, options):
216 Effect.__init__(self, layer, options) 217 self.xmag = float(options.get("xmagnitude", .1)) 218 self.ymag = float(options.get("ymagnitude", .1))
219
220 - def apply(self):
221 t = self.trigger() 222 self.layer.drawing.transform.scale(1.0 + self.xmag * t, 1.0 + self.ymag * t)
223
224 -class Stage(object):
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 # Build the layers 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 # Load any effects 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
291 - def reset(self):
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
303 - def triggerPick(self, pos, notes):
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
309 - def triggerMiss(self, pos):
310 self.lastMissPos = pos
311
312 - def triggerQuarterBeat(self, pos, quarterBeat):
313 self.lastQuarterBeatPos = pos 314 self.quarterBeat = quarterBeat
315
316 - def triggerBeat(self, pos, beat):
317 self.lastBeatPos = pos 318 self.beat = beat 319 self.averageNotes = self.averageNotes[-4:] + self.averageNotes[-1:]
320
321 - def _renderLayers(self, layers, visibility):
322 self.engine.view.setOrthogonalProjection(normalize = True) 323 try: 324 for layer in layers: 325 layer.render(visibility) 326 finally: 327 self.engine.view.resetProjection()
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):
343 self._renderLayers(self.backgroundLayers, visibility) 344 self.scene.renderGuitar() 345 self._renderLayers(self.foregroundLayers, visibility)
346