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

Source Code for Module Guitar

  1  #####################################################################
 
  2  # -*- coding: iso-8859-1 -*-                                        #
 
  3  #                                                                   #
 
  4  # Frets on Fire                                                     #
 
  5  # Copyright (C) 2006 Sami Kyostila                                  #
 
  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  import Player 
 24  from Song import Note, Tempo 
 25  from Mesh import Mesh 
 26  import Theme 
 27  
 
 28  from OpenGL.GL import * 
 29  import math 
 30  import numpy 
 31  
 
 32  KEYS = [Player.KEY1, Player.KEY2, Player.KEY3, Player.KEY4, Player.KEY5] 
 33  
 
34 -class Guitar:
35 - def __init__(self, engine, editorMode = False):
36 self.engine = engine 37 self.boardWidth = 4.0 38 self.boardLength = 12.0 39 self.beatsPerBoard = 5.0 40 self.strings = 5 41 self.fretWeight = [0.0] * self.strings 42 self.fretActivity = [0.0] * self.strings 43 self.fretColors = Theme.fretColors 44 self.playedNotes = [] 45 self.editorMode = editorMode 46 self.selectedString = 0 47 self.time = 0.0 48 self.pickStartPos = 0 49 self.leftyMode = False 50 self.currentBpm = 50.0 51 self.currentPeriod = 60000.0 / self.currentBpm 52 self.targetBpm = self.currentBpm 53 self.lastBpmChange = -1.0 54 self.baseBeat = 0.0 55 self.setBPM(self.currentBpm) 56 self.vertexCache = numpy.empty((8 * 4096, 3), numpy.float32) 57 self.colorCache = numpy.empty((8 * 4096, 4), numpy.float32) 58 59 engine.resource.load(self, "noteMesh", lambda: Mesh(engine.resource.fileName("note.dae"))) 60 engine.resource.load(self, "keyMesh", lambda: Mesh(engine.resource.fileName("key.dae"))) 61 engine.loadSvgDrawing(self, "glowDrawing", "glow.svg", textureSize = (128, 128)) 62 engine.loadSvgDrawing(self, "neckDrawing", "neck.svg", textureSize = (256, 256))
63
64 - def selectPreviousString(self):
65 self.selectedString = (self.selectedString - 1) % self.strings
66
67 - def selectString(self, string):
68 self.selectedString = string % self.strings
69
70 - def selectNextString(self):
71 self.selectedString = (self.selectedString + 1) % self.strings
72
73 - def setBPM(self, bpm):
74 self.earlyMargin = 60000.0 / bpm / 3.5 75 self.lateMargin = 60000.0 / bpm / 3.5 76 self.noteReleaseMargin = 60000.0 / bpm / 2 77 self.bpm = bpm 78 self.baseBeat = 0.0
79
80 - def renderNeck(self, visibility, song, pos):
81 if not song: 82 return 83 84 def project(beat): 85 return .5 * beat / beatsPerUnit
86 87 v = visibility 88 w = self.boardWidth 89 l = self.boardLength 90 beatsPerUnit = self.beatsPerBoard / self.boardLength 91 offset = (pos - self.lastBpmChange) / self.currentPeriod + self.baseBeat 92 93 glEnable(GL_TEXTURE_2D) 94 self.neckDrawing.texture.bind() 95 96 glBegin(GL_TRIANGLE_STRIP) 97 glColor4f(1, 1, 1, 0) 98 glTexCoord2f(0.0, project(offset - 2 * beatsPerUnit)) 99 glVertex3f(-w / 2, 0, -2) 100 glTexCoord2f(1.0, project(offset - 2 * beatsPerUnit)) 101 glVertex3f( w / 2, 0, -2) 102 103 glColor4f(1, 1, 1, v) 104 glTexCoord2f(0.0, project(offset - 1 * beatsPerUnit)) 105 glVertex3f(-w / 2, 0, -1) 106 glTexCoord2f(1.0, project(offset - 1 * beatsPerUnit)) 107 glVertex3f( w / 2, 0, -1) 108 109 glTexCoord2f(0.0, project(offset + l * beatsPerUnit * .7)) 110 glVertex3f(-w / 2, 0, l * .7) 111 glTexCoord2f(1.0, project(offset + l * beatsPerUnit * .7)) 112 glVertex3f( w / 2, 0, l * .7) 113 114 glColor4f(1, 1, 1, 0) 115 glTexCoord2f(0.0, project(offset + l * beatsPerUnit)) 116 glVertex3f(-w / 2, 0, l) 117 glTexCoord2f(1.0, project(offset + l * beatsPerUnit)) 118 glVertex3f( w / 2, 0, l) 119 glEnd() 120 121 glDisable(GL_TEXTURE_2D)
122
123 - def renderTracks(self, visibility):
124 w = self.boardWidth / self.strings 125 v = 1.0 - visibility 126 127 if self.editorMode: 128 x = (self.strings / 2 - self.selectedString) * w 129 s = 2 * w / self.strings 130 z1 = -0.5 * visibility ** 2 131 z2 = (self.boardLength - 0.5) * visibility ** 2 132 133 glColor4f(1, 1, 1, .15) 134 135 glBegin(GL_TRIANGLE_STRIP) 136 glVertex3f(x - s, 0, z1) 137 glVertex3f(x + s, 0, z1) 138 glVertex3f(x - s, 0, z2) 139 glVertex3f(x + s, 0, z2) 140 glEnd() 141 142 sw = 0.025 143 for n in range(self.strings - 1, -1, -1): 144 glBegin(GL_TRIANGLE_STRIP) 145 Theme.setBaseColor(0) 146 glVertex3f((n - self.strings / 2) * w - sw, -v, -2) 147 glVertex3f((n - self.strings / 2) * w + sw, -v, -2) 148 Theme.setBaseColor((1.0 - v) * .75) 149 glVertex3f((n - self.strings / 2) * w - sw, -v, -1) 150 glVertex3f((n - self.strings / 2) * w + sw, -v, -1) 151 Theme.setBaseColor((1.0 - v) * .75) 152 glVertex3f((n - self.strings / 2) * w - sw, -v, self.boardLength * .7) 153 glVertex3f((n - self.strings / 2) * w + sw, -v, self.boardLength * .7) 154 Theme.setBaseColor(0) 155 glVertex3f((n - self.strings / 2) * w - sw, -v, self.boardLength) 156 glVertex3f((n - self.strings / 2) * w + sw, -v, self.boardLength) 157 glEnd() 158 v *= 2
159 160
161 - def renderBars(self, visibility, song, pos):
162 if not song: 163 return 164 165 w = self.boardWidth 166 v = 1.0 - visibility 167 sw = 0.02 168 beatsPerUnit = self.beatsPerBoard / self.boardLength 169 pos -= self.lastBpmChange 170 offset = pos / self.currentPeriod * beatsPerUnit 171 currentBeat = pos / self.currentPeriod 172 beat = int(currentBeat) 173 174 glEnable(GL_BLEND) 175 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 176 177 glPushMatrix() 178 while beat < currentBeat + self.beatsPerBoard: 179 z = (beat - currentBeat) / beatsPerUnit 180 181 if z > self.boardLength * .8: 182 c = (self.boardLength - z) / (self.boardLength * .2) 183 elif z < 0: 184 c = max(0, 1 + z) 185 else: 186 c = 1.0 187 188 glRotate(v * 90, 0, 0, 1) 189 190 if (beat % 1.0) < 0.001: 191 Theme.setBaseColor(visibility * c * .75) 192 else: 193 Theme.setBaseColor(visibility * c * .5) 194 195 glBegin(GL_TRIANGLE_STRIP) 196 glVertex3f(-(w / 2), -v, z + sw) 197 glVertex3f(-(w / 2), -v, z - sw) 198 glVertex3f(w / 2, -v, z + sw) 199 glVertex3f(w / 2, -v, z - sw) 200 glEnd() 201 202 if self.editorMode: 203 beat += 1.0 / 4.0 204 else: 205 beat += 1 206 glPopMatrix() 207 208 Theme.setSelectedColor(visibility * .5) 209 glBegin(GL_TRIANGLE_STRIP) 210 glVertex3f(-w / 2, 0, sw) 211 glVertex3f(-w / 2, 0, -sw) 212 glVertex3f(w / 2, 0, sw) 213 glVertex3f(w / 2, 0, -sw) 214 glEnd()
215
216 - def renderNote(self, length, color, flat = False, tailOnly = False, isTappable = False):
217 if not self.noteMesh: 218 return 219 220 glColor4f(*color) 221 222 if flat: 223 glScalef(1, .1, 1) 224 225 size = (.1, length + 0.00001) 226 glBegin(GL_TRIANGLE_STRIP) 227 glVertex3f(-size[0], 0, 0) 228 glVertex3f( size[0], 0, 0) 229 glVertex3f(-size[0], 0, size[1]) 230 glVertex3f( size[0], 0, size[1]) 231 glEnd() 232 233 if tailOnly: 234 return 235 236 glPushMatrix() 237 glEnable(GL_DEPTH_TEST) 238 glDepthMask(1) 239 glShadeModel(GL_SMOOTH) 240 self.noteMesh.render("Mesh_001") 241 if isTappable: 242 self.noteMesh.render("Mesh_003") 243 glColor4f(.75 * color[0], .75 * color[1], .75 * color[2], color[3]) 244 self.noteMesh.render("Mesh") 245 glColor4f(.25 * color[0], .25 * color[1], .25 * color[2], color[3]) 246 self.noteMesh.render("Mesh_002") 247 glDepthMask(0) 248 glPopMatrix()
249
250 - def renderNotes(self, visibility, song, pos):
251 if not song: 252 return 253 254 # Update dynamic period 255 self.currentPeriod = 60000.0 / self.currentBpm 256 self.targetPeriod = 60000.0 / self.targetBpm 257 258 beatsPerUnit = self.beatsPerBoard / self.boardLength 259 w = self.boardWidth / self.strings 260 track = song.track 261 262 for time, event in track.getEvents(pos - self.currentPeriod * 2, pos + self.currentPeriod * self.beatsPerBoard): 263 if isinstance(event, Tempo): 264 if (pos - time > self.currentPeriod or self.lastBpmChange < 0) and time > self.lastBpmChange: 265 self.baseBeat += (time - self.lastBpmChange) / self.currentPeriod 266 self.targetBpm = event.bpm 267 self.lastBpmChange = time 268 continue 269 270 if not isinstance(event, Note): 271 continue 272 273 c = self.fretColors[event.number] 274 275 x = (self.strings / 2 - event.number) * w 276 z = ((time - pos) / self.currentPeriod) / beatsPerUnit 277 z2 = ((time + event.length - pos) / self.currentPeriod) / beatsPerUnit 278 279 if z > self.boardLength * .8: 280 f = (self.boardLength - z) / (self.boardLength * .2) 281 elif z < 0: 282 f = min(1, max(0, 1 + z2)) 283 else: 284 f = 1.0 285 286 color = (.1 + .8 * c[0], .1 + .8 * c[1], .1 + .8 * c[2], 1 * visibility * f) 287 length = event.length / self.currentPeriod / beatsPerUnit 288 flat = False 289 tailOnly = False 290 isTappable = event.tappable 291 292 # Clip the played notes to the origin 293 if z < 0: 294 if event.played: 295 tailOnly = True 296 length += z 297 z = 0 298 if length <= 0: 299 continue 300 else: 301 color = (.2 + .4, .2 + .4, .2 + .4, .5 * visibility * f) 302 flat = True 303 304 glPushMatrix() 305 glTranslatef(x, (1.0 - visibility) ** (event.number + 1), z) 306 self.renderNote(length, color = color, flat = flat, tailOnly = tailOnly, isTappable = isTappable) 307 glPopMatrix() 308 309 310 # Draw a waveform shape over the currently playing notes 311 vertices = self.vertexCache 312 colors = self.colorCache 313 314 glBlendFunc(GL_SRC_ALPHA, GL_ONE) 315 glEnableClientState(GL_VERTEX_ARRAY) 316 glEnableClientState(GL_COLOR_ARRAY) 317 glVertexPointer(3, GL_FLOAT, 0, vertices) 318 glColorPointer(4, GL_FLOAT, 0, colors) 319 320 for time, event in self.playedNotes: 321 t = time + event.length 322 dt = t - pos 323 proj = 1.0 / self.currentPeriod / beatsPerUnit 324 325 # Increase these values to improve performance 326 step1 = dt * proj * 25 327 step2 = 10.0 328 329 if dt < 1e-3: 330 continue 331 332 dStep = (step2 - step1) / dt 333 x = (self.strings / 2 - event.number) * w 334 c = self.fretColors[event.number] 335 s = t 336 step = step1 337 338 vertexCount = 0 339 340 def waveForm(t): 341 u = ((t - time) * -.1 + pos - time) / 64.0 + .0001 342 return (math.sin(event.number + self.time * -.01 + t * .03) + math.cos(event.number + self.time * .01 + t * .02)) * .1 + .1 + math.sin(u) / (5 * u)
343 344 i = 0 345 a1 = 0.0 346 zStep = step * proj 347 348 while t > time and t - step > pos and i < len(vertices) / 8: 349 z = (t - pos) * proj 350 a2 = waveForm(t - step) 351 352 colors[i ] = \ 353 colors[i + 1] = (c[0], c[1], c[2], .5) 354 colors[i + 2] = \ 355 colors[i + 3] = (1, 1, 1, .75) 356 colors[i + 4] = \ 357 colors[i + 5] = \ 358 colors[i + 6] = \ 359 colors[i + 7] = (c[0], c[1], c[2], .5) 360 vertices[i ] = (x - a1, 0, z) 361 vertices[i + 1] = (x - a2, 0, z - zStep) 362 vertices[i + 2] = (x, 0, z) 363 vertices[i + 3] = (x, 0, z - zStep) 364 vertices[i + 4] = (x + a1, 0, z) 365 vertices[i + 5] = (x + a2, 0, z - zStep) 366 vertices[i + 6] = (x + a2, 0, z - zStep) 367 vertices[i + 7] = (x - a2, 0, z - zStep) 368 369 i += 8 370 t -= step 371 a1 = a2 372 step = step1 + dStep * (s - t) 373 zStep = step * proj 374 375 glDrawArrays(GL_TRIANGLE_STRIP, 0, i) 376 glDisableClientState(GL_VERTEX_ARRAY) 377 glDisableClientState(GL_COLOR_ARRAY) 378 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 379
380 - def renderFrets(self, visibility, song, controls):
381 w = self.boardWidth / self.strings 382 v = 1.0 - visibility 383 384 glEnable(GL_DEPTH_TEST) 385 386 for n in range(self.strings): 387 f = self.fretWeight[n] 388 c = self.fretColors[n] 389 390 if f and (controls.getState(Player.ACTION1) or controls.getState(Player.ACTION2)): 391 f += 0.25 392 393 glColor4f(.1 + .8 * c[0] + f, .1 + .8 * c[1] + f, .1 + .8 * c[2] + f, visibility) 394 y = v + f / 6 395 x = (self.strings / 2 - n) * w 396 397 if self.keyMesh: 398 glPushMatrix() 399 glTranslatef(x, y + v * 6, 0) 400 glDepthMask(1) 401 glEnable(GL_LIGHTING) 402 glEnable(GL_LIGHT0) 403 glShadeModel(GL_SMOOTH) 404 glRotatef(90, 0, 1, 0) 405 glLightfv(GL_LIGHT0, GL_POSITION, (5.0, 10.0, -10.0, 0.0)) 406 glLightfv(GL_LIGHT0, GL_AMBIENT, (.2, .2, .2, 0.0)) 407 glLightfv(GL_LIGHT0, GL_DIFFUSE, (1.0, 1.0, 1.0, 0.0)) 408 glRotatef(-90, 1, 0, 0) 409 glRotatef(-90, 0, 0, 1) 410 self.keyMesh.render() 411 glDisable(GL_LIGHTING) 412 glDisable(GL_LIGHT0) 413 glDepthMask(0) 414 glPopMatrix() 415 416 f = self.fretActivity[n] 417 418 if f: 419 glBlendFunc(GL_ONE, GL_ONE) 420 s = 0.0 421 self.glowDrawing.texture.bind() 422 423 glEnable(GL_TEXTURE_2D) 424 glDisable(GL_DEPTH_TEST) 425 glPushMatrix() 426 glTranslate(x, y, 0) 427 glRotate(f + self.time * .1, 0, 1, 0) 428 size = (.22 * (f + 1.5), .22 * (f + 1.5)) 429 430 if self.playedNotes: 431 t = math.cos(math.pi + (self.time - self.playedNotes[0][0]) * 0.01) 432 else: 433 t = math.cos(self.time * 0.01) 434 435 while s < .5: 436 ms = (1 - s) * f * t * .25 + .75 437 glColor3f(c[0] * ms, c[1] * ms, c[2] * ms) 438 glBegin(GL_TRIANGLE_STRIP) 439 glTexCoord2f(0.0, 0.0) 440 glVertex3f(-size[0] * f, 0, -size[1] * f) 441 glTexCoord2f(1.0, 0.0) 442 glVertex3f( size[0] * f, 0, -size[1] * f) 443 glTexCoord2f(0.0, 1.0) 444 glVertex3f(-size[0] * f, 0, size[1] * f) 445 glTexCoord2f(1.0, 1.0) 446 glVertex3f( size[0] * f, 0, size[1] * f) 447 glEnd() 448 glTranslatef(0, ms * .2, 0) 449 glScalef(.8, 1, .8) 450 glRotate(ms * 20, 0, 1, 0) 451 s += 0.2 452 453 glPopMatrix() 454 glEnable(GL_DEPTH_TEST) 455 456 glDisable(GL_TEXTURE_2D) 457 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 458 459 v *= 1.5 460 glDisable(GL_DEPTH_TEST)
461
462 - def render(self, visibility, song, pos, controls):
463 glEnable(GL_BLEND) 464 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 465 glEnable(GL_COLOR_MATERIAL) 466 if self.leftyMode: 467 glScale(-1, 1, 1) 468 469 self.renderNeck(visibility, song, pos) 470 self.renderTracks(visibility) 471 self.renderBars(visibility, song, pos) 472 self.renderNotes(visibility, song, pos) 473 self.renderFrets(visibility, song, controls) 474 if self.leftyMode: 475 glScale(-1, 1, 1)
476
477 - def getMissedNotes(self, song, pos):
478 if not song: 479 return 480 481 m1 = self.lateMargin 482 m2 = self.lateMargin * 2 483 track = song.track 484 notes = [(time, event) for time, event in track.getEvents(pos - m1, pos - m2) if isinstance(event, Note)] 485 notes = [(time, event) for time, event in notes if (time >= (pos - m2)) and (time <= (pos - m1))] 486 notes = [(time, event) for time, event in notes if not event.played] 487 488 return notes
489
490 - def getRequiredNotes(self, song, pos):
491 track = song.track 492 notes = [(time, event) for time, event in track.getEvents(pos - self.lateMargin, pos + self.earlyMargin) if isinstance(event, Note)] 493 notes = [(time, event) for time, event in notes if not event.played] 494 notes = [(time, event) for time, event in notes if (time >= (pos - self.lateMargin)) and (time <= (pos + self.earlyMargin))] 495 if notes: 496 t = min([time for time, event in notes]) 497 notes = [(time, event) for time, event in notes if time - t < 1e-3] 498 return notes
499
500 - def controlsMatchNotes(self, controls, notes):
501 result = True 502 503 # no notes? 504 if not notes: 505 result = False 506 507 # check each valid chord 508 chords = {} 509 for time, note in notes: 510 if not time in chords: 511 chords[time] = [] 512 chords[time].append((time, note)) 513 514 for notes in chords.values(): 515 # matching keys? 516 requiredKeys = [note.number for time, note in notes] 517 518 for n, k in enumerate(KEYS): 519 if n in requiredKeys and not controls.getState(k): 520 result = False 521 break 522 if not n in requiredKeys and controls.getState(k): 523 # The lower frets can be held down 524 if n > max(requiredKeys): 525 result = False 526 break 527 return result
528
529 - def areNotesTappable(self, notes):
530 if not notes: 531 return 532 for time, note in notes: 533 if not note.tappable: 534 return False 535 return True
536
537 - def startPick(self, song, pos, controls):
538 if not song: 539 return False 540 541 self.playedNotes = [] 542 notes = self.getRequiredNotes(song, pos) 543 match = self.controlsMatchNotes(controls, notes) 544 545 """ 546 if match: 547 print "\033[0m", 548 else: 549 print "\033[31m", 550 print "MATCH?", 551 n = [note.number for time, note in notes] 552 for i, k in enumerate(KEYS): 553 if i in n: 554 if controls.getState(k): 555 print " [#] ", 556 else: 557 print " # ", 558 else: 559 if controls.getState(k): 560 print " [.] ", 561 else: 562 print " . ", 563 print 564 """ 565 566 if match: 567 self.pickStartPos = pos 568 for time, note in notes: 569 self.pickStartPos = max(self.pickStartPos, time) 570 note.played = True 571 self.playedNotes = notes 572 return True 573 return False
574
575 - def endPick(self, pos):
576 for time, note in self.playedNotes: 577 if time + note.length > pos + self.noteReleaseMargin: 578 self.playedNotes = [] 579 return False 580 581 self.playedNotes = [] 582 return True
583
584 - def getPickLength(self, pos):
585 if not self.playedNotes: 586 return 0.0 587 588 # The pick length is limited by the played notes 589 pickLength = pos - self.pickStartPos 590 for time, note in self.playedNotes: 591 pickLength = min(pickLength, note.length) 592 return pickLength
593
594 - def run(self, ticks, pos, controls):
595 self.time += ticks 596 597 # update frets 598 if self.editorMode: 599 if (controls.getState(Player.ACTION1) or controls.getState(Player.ACTION2)): 600 activeFrets = [i for i, k in enumerate(KEYS) if controls.getState(k)] or [self.selectedString] 601 else: 602 activeFrets = [] 603 else: 604 activeFrets = [note.number for time, note in self.playedNotes] 605 606 for n in range(self.strings): 607 if controls.getState(KEYS[n]) or (self.editorMode and self.selectedString == n): 608 self.fretWeight[n] = 0.5 609 else: 610 self.fretWeight[n] = max(self.fretWeight[n] - ticks / 64.0, 0.0) 611 if n in activeFrets: 612 self.fretActivity[n] = min(self.fretActivity[n] + ticks / 32.0, 1.0) 613 else: 614 self.fretActivity[n] = max(self.fretActivity[n] - ticks / 64.0, 0.0) 615 616 for time, note in self.playedNotes: 617 if pos > time + note.length: 618 return False 619 620 # update bpm 621 diff = self.targetBpm - self.currentBpm 622 self.currentBpm += diff * .03 623 624 return True
625