1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
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
65 self.selectedString = (self.selectedString - 1) % self.strings
66
68 self.selectedString = string % self.strings
69
71 self.selectedString = (self.selectedString + 1) % self.strings
72
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
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
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
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
251 if not song:
252 return
253
254
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
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
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
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
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
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
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
501 result = True
502
503
504 if not notes:
505 result = False
506
507
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
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
524 if n > max(requiredKeys):
525 result = False
526 break
527 return result
528
530 if not notes:
531 return
532 for time, note in notes:
533 if not note.tappable:
534 return False
535 return True
536
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
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
585 if not self.playedNotes:
586 return 0.0
587
588
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
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
621 diff = self.targetBpm - self.currentBpm
622 self.currentBpm += diff * .03
623
624 return True
625