1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import pygame
24 from OpenGL.GL import *
25 from OpenGL.GLU import *
26 import math
27 import colorsys
28
29 from View import Layer
30 from Input import KeyListener
31 from Song import loadSong, createSong, Note, difficulties, DEFAULT_LIBRARY
32 from Guitar import Guitar, KEYS
33 from Camera import Camera
34 from Menu import Menu, Choice
35 from Language import _
36 import MainMenu
37 import Dialogs
38 import Player
39 import Theme
40 import Log
41 import shutil, os, struct, wave, tempfile
42 from struct import unpack
43
44 -class Editor(Layer, KeyListener):
45 """Song editor layer."""
47 self.engine = engine
48 self.time = 0.0
49 self.guitar = Guitar(self.engine, editorMode = True)
50 self.controls = Player.Controls()
51 self.camera = Camera()
52 self.pos = 0.0
53 self.snapPos = 0.0
54 self.scrollPos = 0.0
55 self.scrollSpeed = 0.0
56 self.newNotes = None
57 self.newNotePos = 0.0
58 self.song = None
59 self.engine.loadSvgDrawing(self, "background", "editor.svg")
60 self.modified = False
61 self.songName = songName
62 self.libraryName = libraryName
63 self.heldFrets = set()
64
65 mainMenu = [
66 (_("Save Song"), self.save),
67 (_("Set Song Name"), self.setSongName),
68 (_("Set Artist Name"), self.setArtistName),
69 (_("Set Beats per Minute"), self.setBpm),
70 (_("Estimate Beats per Minute"), self.estimateBpm),
71 (_("Set A/V delay"), self.setAVDelay),
72 (_("Set Cassette Color"), self.setCassetteColor),
73 (_("Set Cassette Label"), self.setCassetteLabel),
74 (_("Editing Help"), self.help),
75 (_("Quit to Main Menu"), self.quit),
76 ]
77 self.menu = Menu(self.engine, mainMenu)
78
80 if not self.modified:
81 Dialogs.showMessage(self.engine, _("There are no changes to save."))
82 return
83
84 def save():
85 self.song.save()
86 self.modified = False
87
88 self.engine.resource.load(function = save)
89 Dialogs.showLoadingScreen(self.engine, lambda: not self.modified, text = _("Saving..."))
90 Dialogs.showMessage(self.engine, _("'%s' saved.") % self.song.info.name)
91
93 Dialogs.showMessage(self.engine, _("Editing keys: ") +
94 _("Arrows - Move cursor, ") +
95 _("Space - Play/pause song, ") +
96 _("Enter - Make note (hold and move for long notes), ") +
97 _("Delete - Delete note, ") +
98 _("Page Up/Down - Change difficulty"))
99
100
106
112
121
123 bpm = Dialogs.getText(self.engine, _("Enter Beats per Minute Value"), unicode(self.song.bpm))
124 if bpm:
125 try:
126 self.song.setBpm(float(bpm))
127 self.modified = True
128 except ValueError:
129 Dialogs.showMessage(self.engine, _("That isn't a number."))
130
132 bpm = Dialogs.estimateBpm(self.engine, self.song, _("Tap the Space bar to the beat of the song. Press Enter when done or Escape to cancel."))
133 if bpm is not None:
134 self.song.setBpm(bpm)
135 self.modified = True
136
149
151 label = Dialogs.chooseFile(self.engine, masks = ["*.png"], prompt = _("Choose a 256x128 PNG format label image."))
152 if label:
153 songPath = self.engine.resource.fileName("songs", self.songName, writable = True)
154 shutil.copyfile(label, os.path.join(songPath, "label.png"))
155 self.modified = True
156
158 self.engine.input.addKeyListener(self)
159
160 if not self.songName:
161 self.libraryName, self.songName = Dialogs.chooseSong(self.engine)
162
163 if not self.songName:
164 self.engine.view.popLayer(self)
165 return
166
167 self.engine.resource.load(self, "song", lambda: loadSong(self.engine, self.songName, seekable = True, library = self.libraryName))
168 Dialogs.showLoadingScreen(self.engine, lambda: self.song, text = _("Loading song..."))
169
175
177 if not self.song:
178 return
179
180 if control == Player.UP:
181 self.guitar.selectPreviousString()
182 elif control == Player.DOWN:
183 self.guitar.selectNextString()
184 elif control == Player.LEFT:
185 self.pos = self.snapPos - self.song.period / 4
186 elif control == Player.RIGHT:
187 self.pos = self.snapPos + self.song.period / 4
188 elif control in KEYS:
189 self.heldFrets.add(KEYS.index(control))
190 elif control in [Player.ACTION1, Player.ACTION2]:
191 self.newNotePos = self.snapPos
192
193 if self.heldFrets:
194 self.newNotes = [Note(f, self.song.period / 4) for f in self.heldFrets]
195 else:
196 self.newNotes = [Note(self.guitar.selectedString, self.song.period / 4)]
197 self.modified = True
198
200 if not self.song:
201 return
202
203 if control in [Player.ACTION1, Player.ACTION2] and self.newNotes and not self.heldFrets:
204 self.newNotes = []
205 elif control in KEYS:
206 self.heldFrets.remove(KEYS.index(control))
207 if not self.heldFrets and self.newNotes:
208 self.newNotes = []
209
213
215 c = self.engine.input.controls.getMapping(key)
216 if c == Player.CANCEL:
217 self.engine.view.pushLayer(self.menu)
218 elif key == pygame.K_PAGEDOWN and self.song:
219 d = self.song.difficulty
220 v = difficulties.values()
221 self.song.difficulty = v[(v.index(d) + 1) % len(v)]
222 elif key == pygame.K_PAGEUP and self.song:
223 d = self.song.difficulty
224 v = difficulties.values()
225 self.song.difficulty = v[(v.index(d) - 1) % len(v)]
226 elif key == pygame.K_DELETE and self.song:
227
228 t1 = self.snapPos
229 t2 = self.snapPos + self.song.period / 4
230 e = [(time, event) for time, event in self.song.track.getEvents(t1, t2) if isinstance(event, Note)]
231 for time, event in e:
232 if event.number == self.guitar.selectedString:
233 self.song.track.removeEvent(time, event)
234 self.modified = True
235 elif key == pygame.K_SPACE and self.song:
236 if self.song.isPlaying():
237 self.song.stop()
238 else:
239 self.song.play(start = self.pos)
240 c = self.controls.keyPressed(key)
241 if c:
242 self.controlPressed(c)
243 return True
244
250
251 - def run(self, ticks):
252 self.time += ticks / 50.0
253
254 if not self.song:
255 return
256
257 self.guitar.run(ticks, self.scrollPos, self.controls)
258
259 if not self.song.isPlaying():
260 if self.controls.getState(Player.RIGHT) and not self.controls.getState(Player.LEFT):
261 self.pos += self.song.period * self.scrollSpeed
262 self.scrollSpeed += ticks / 4096.0
263 elif self.controls.getState(Player.LEFT) and not self.controls.getState(Player.RIGHT):
264 self.pos -= self.song.period * self.scrollSpeed
265 self.scrollSpeed += ticks / 4096.0
266 else:
267 self.scrollSpeed = 0
268 else:
269 self.pos = self.song.getPosition() - self.song.info.delay
270
271 self.pos = max(0, self.pos)
272
273 quarterBeat = int(self.pos / (self.song.period / 4) + .5)
274 self.snapPos = quarterBeat * (self.song.period / 4)
275
276
277 if self.newNotes:
278 if self.snapPos < self.newNotePos:
279 self.newNotes = []
280 for note in self.newNotes:
281 self.song.track.removeEvent(self.newNotePos, note)
282 note.length = max(self.song.period / 4, self.snapPos - self.newNotePos)
283
284 oldNotes = [(time, event) for time, event in self.song.track.getEvents(self.newNotePos, self.newNotePos + note.length) if isinstance(event, Note)]
285 for time, event in oldNotes:
286 if event.number == note.number:
287 self.song.track.removeEvent(time, event)
288 if time < self.newNotePos:
289 event.length = self.newNotePos - time
290 self.song.track.addEvent(time, event)
291 self.song.track.addEvent(self.newNotePos, note)
292
293 if self.song.isPlaying():
294 self.scrollPos = self.pos
295 else:
296 self.scrollPos = (self.scrollPos + self.snapPos) / 2.0
297
298 - def render(self, visibility, topMost):
299 if not self.song:
300 return
301
302 v = 1.0 - ((1 - visibility) ** 2)
303
304
305 t = self.time / 100 + 34
306 w, h, = self.engine.view.geometry[2:4]
307 r = .5
308 self.background.transform.reset()
309 self.background.transform.translate(w / 2 + math.sin(t / 2) * w / 2 * r, h / 2 + math.cos(t) * h / 2 * r)
310 self.background.transform.rotate(-t)
311 self.background.transform.scale(math.sin(t / 8) + 2, math.sin(t / 8) + 2)
312 self.background.draw()
313
314 self.camera.target = ( 2, 0, 5.5)
315 self.camera.origin = (-2, 9, 5.5)
316
317 glMatrixMode(GL_PROJECTION)
318 glLoadIdentity()
319 gluPerspective(60, 4.0 / 3.0, 0.1, 1000)
320 glMatrixMode(GL_MODELVIEW)
321 glLoadIdentity()
322 self.camera.apply()
323 self.guitar.render(v, self.song, self.scrollPos, self.controls)
324
325 self.engine.view.setOrthogonalProjection(normalize = True)
326 font = self.engine.data.font
327
328 try:
329 Theme.setSelectedColor()
330
331 w, h = font.getStringSize(" ")
332
333 if self.song.isPlaying():
334 status = _("Playing")
335 else:
336 status = _("Stopped")
337
338 t = "%d.%02d'%03d" % (self.pos / 60000, (self.pos % 60000) / 1000, self.pos % 1000)
339 font.render(t, (.05, .05 - h / 2))
340 font.render(status, (.05, .05 + h / 2))
341 font.render(unicode(self.song.difficulty), (.05, .05 + 3 * h / 2))
342
343 Theme.setBaseColor()
344 text = self.song.info.name + (self.modified and "*" or "")
345 Dialogs.wrapText(font, (.5, .05 - h / 2), text)
346 finally:
347 self.engine.view.resetProjection()
348
350 """
351 Song importer.
352
353 This importer needs two OGG tracks for the new song; one is the background track
354 and the other is the guitar track. The importer will create a blank note and info files
355 and copy the tracks under the data directory.
356 """
358 self.engine = engine
359 self.wizardStarted = False
360 self.song = None
361 self.songName = None
362
368
369 - def run(self, ticks):
370 if self.wizardStarted:
371 return
372 self.wizardStarted = True
373
374 name = ""
375 while True:
376 masks = ["*.ogg"]
377 name = Dialogs.getText(self.engine, prompt = _("Enter a name for the song."), text = name)
378
379 if not name:
380 self.engine.view.popLayer(self)
381 return
382
383 path = os.path.abspath(self.engine.resource.fileName("songs", name))
384 if os.path.isdir(path):
385 Dialogs.showMessage(self.engine, _("That song already exists."))
386 else:
387 break
388
389 guitarTrack = Dialogs.chooseFile(self.engine, masks = masks, prompt = _("Choose the Instrument Track (OGG format)."))
390
391 if not guitarTrack:
392 self.engine.view.popLayer(self)
393 return
394
395 backgroundTrack = Dialogs.chooseFile(self.engine, masks = masks, prompt = _("Choose the Background Track (OGG format) or press Escape to skip."))
396
397
398 loader = self.engine.resource.load(self, "song", lambda: createSong(self.engine, name, guitarTrack, backgroundTrack))
399 Dialogs.showLoadingScreen(self.engine, lambda: self.song or loader.exception, text = _("Importing..."))
400
401 if not loader.exception:
402 self.songName = name
403 self.engine.view.popLayer(self)
404
406 """
407 An interface to the ARK file format of Guitar Hero.
408
409 The format of the archive and the index file was studied from
410 Game Extractor by WATTO Studios.
411 """
412 - def __init__(self, indexFileName, dataFileName):
413 self.dataFileName = dataFileName
414
415
416 f = open(indexFileName, "rb")
417 magic, version1, version2, arkSize, length = unpack("IIIII", f.read(5 * 4))
418
419 Log.debug("Reading HDR file v%d.%d. Main archive is %d bytes." % (version1, version2, arkSize))
420
421
422 fileNameData = f.read(length)
423 fileNameCount, = unpack("I", f.read(4))
424 fileNameOffsets = [unpack("I", f.read(4))[0] for i in range(fileNameCount)]
425
426
427 names = []
428 for i, offset in enumerate(fileNameOffsets):
429 length = fileNameData[offset:].find("\x00")
430 fileName = fileNameData[offset:offset + length]
431 names.append(fileName)
432
433
434 fileCount, = unpack("I", f.read(4))
435
436 self.files = {}
437 for i in range(fileCount):
438 offset, fileIndex, dirIndex, length, null = unpack("IIIII", f.read(5 * 4))
439 fullName = "%s/%s" % (names[dirIndex], names[fileIndex])
440 self.files[fullName] = offset, length
441 Log.debug("File: %s at offset %d, length %d bytes." % (fullName, offset, length))
442 Log.debug("Archive contains %d files." % len(self.files))
443 f.close()
444
446 offset, length = self.files[name]
447 f = open(self.dataFileName, mode)
448 f.seek(offset)
449 return f
450
452 offset, length = self.files[name]
453 return length
454
456 f = self.openFile(name)
457 length = self.fileLength(name)
458
459 if type(outputFile) == str:
460 out = open(outputFile, "wb")
461 else:
462 out = outputFile
463
464 while length > 0:
465 data = f.read(4096)
466 data = data[:min(length, len(data))]
467 length -= len(data)
468 out.write(data)
469 f.close()
470
471 if type(outputFile) == str:
472 out.close()
473
475 """
476 Guitar Hero(tm) song importer.
477
478 This importer takes the original Guitar Hero PS2 DVD and extracts the songs from it.
479 Thanks to Sami Vaarala for the initial implementation!
480 """
482 self.engine = engine
483 self.wizardStarted = False
484 self.done = False
485 self.songs = None
486 self.statusText = ""
487 self.stageInfoText = ""
488 self.stageProgress = 0.0
489
493
495 Log.notice("Decompressing %d byte VGS file." % (length))
496
497
498 f = vgsFile
499
500 c = [[ 0.0, 0.0 ],
501 [ 60.0 / 64.0, 0.0 ],
502 [ 115.0 / 64.0, -52.0 / 64.0 ],
503 [ 98.0 / 64.0, -55.0 / 64.0 ],
504 [ 122.0 / 64.0, -60.0 / 64.0 ],
505 [ 0.0, 0.0 ],
506 [ 0.0, 0.0 ],
507 [ 0.0, 0.0 ]]
508
509 class StreamDecoder:
510 """XA ADPCM decoder"""
511 def __init__(self):
512 self.s_1 = 0.0
513 self.s_2 = 0.0
514 self.samples = [0.0] * 28
515
516 def decode(self, block, predict_nr, shift_factor):
517 for i in range(0, 28, 2):
518 d = block[i >> 1]
519 s = (d & 0xf) << 12
520 if s & 0x8000:
521 s = (s & 0xffff) - 0x10000
522 self.samples[i] = float(s >> shift_factor)
523 s = (d & 0xf0) << 8
524 if s & 0x8000:
525 s = (s & 0xffff) - 0x10000
526 self.samples[i + 1] = float(s >> shift_factor)
527
528 for i in range(28):
529 self.samples[i] += self.s_1 * c[predict_nr][0] + self.s_2 * c[predict_nr][1]
530 self.s_2 = self.s_1
531 self.s_1 = self.samples[i]
532
533 return self.samples
534
535 maxStreams = 8
536 streams = [StreamDecoder() for i in range(maxStreams)]
537
538 def byte(d):
539 if len(d):
540 return struct.unpack("b", d)[0]
541 return 0
542
543 startPos = f.tell()
544
545 while True:
546 self.stageProgress = float(f.tell() - startPos) / length
547
548 d = f.read(1)
549 if not d:
550 break
551 predict_nr = byte(d)
552 shift_factor = predict_nr & 0xf
553 predict_nr >>= 4
554 flags = byte(f.read(1))
555
556 if flags == 7 or flags < 0:
557 break
558
559 streamId = flags % maxStreams
560 block = [byte(f.read(1)) for i in range(14)]
561 samples = streams[streamId].decode(block, predict_nr, shift_factor)
562 yield (streamId, struct.pack("28h", *[max(min(int(d + .5), 32767), -32768) for d in samples]))
563
564 f.close()
565
566 - def joinPcmFiles(self, pcmLeft, pcmRight, waveOut, sampleRate = 44100):
567 pcmLeft = open(pcmLeft, "rb")
568 pcmRight = open(pcmRight, "rb")
569 w = wave.open(waveOut, "w")
570 w.setnchannels(2)
571 w.setsampwidth(2)
572 w.setframerate(sampleRate)
573
574 pcmLeft.seek(0, 2)
575 length = pcmLeft.tell()
576 pcmLeft.seek(0)
577
578 while length:
579 self.stageProgress = float(pcmLeft.tell()) / length
580 l = pcmLeft.read(2)
581 r = pcmRight.read(2)
582 if not l or not r:
583 break
584 w.writeframesraw(l + r)
585 w.close()
586 pcmLeft.close()
587 pcmRight.close()
588
589 - def decodeVgsFile(self, vgsFile, length, outputSongOggFile, outputGuitarOggFile, outputRhythmOggFile, workPath):
590
591 self.stageInfoText = _("Stage 1/8: Splitting VGS file")
592
593
594
595
596
597
598
599 f = vgsFile
600 blockSize = 16
601
602 header = f.read(0x80)
603 magic, version = unpack("4si", header[:4 + 4])
604 assert magic == "VgS!"
605 header = header[4 + 4:]
606
607 Log.debug("VGS version %d" % (version))
608
609 streams = []
610 for channels in range(16):
611 rate, blocks = unpack("ii", header[:4 + 4])
612 header = header[4 + 4:]
613 if not rate or not blocks:
614 break
615 Log.debug("Stream %d: %d blocks at %d Hz" % (len(streams), blocks, rate))
616 streams.append((rate, blocks))
617
618 out = [open(os.path.join(workPath, "chan%d.pcm") % c, "wb") for c in range(channels)]
619
620 for channel, data in self.decodeVgsStreams(vgsFile, length):
621 if channel >= 0 and channel < len(out):
622 out[channel].write(data)
623
624 [o.close() for o in out]
625
626
627 Log.notice("Joining song and guitar tracks")
628 songWaveFile = os.path.join(workPath, "song.wav")
629 guitarWaveFile = os.path.join(workPath, "guitar.wav")
630 rhythmWaveFile = os.path.join(workPath, "rhythm.wav")
631
632 self.stageInfoText = _("Stage 6/8: Joining song stereo tracks")
633 self.joinPcmFiles(os.path.join(workPath, "chan0.pcm"),
634 os.path.join(workPath, "chan1.pcm"),
635 songWaveFile, sampleRate = streams[0][0])
636 self.stageInfoText = _("Stage 7/8: Joining guitar stereo tracks")
637 self.joinPcmFiles(os.path.join(workPath, "chan2.pcm"),
638 os.path.join(workPath, "chan3.pcm"),
639 guitarWaveFile, sampleRate = streams[2][0])
640
641 if channels == 5:
642 self.joinPcmFiles(os.path.join(workPath, "chan4.pcm"),
643 os.path.join(workPath, "chan4.pcm"),
644 rhythmWaveFile, sampleRate = streams[4][0])
645 elif channels == 6:
646 self.joinPcmFiles(os.path.join(workPath, "chan4.pcm"),
647 os.path.join(workPath, "chan5.pcm"),
648 rhythmWaveFile, sampleRate = streams[4][0])
649
650
651 self.stageInfoText = _("Stage 8/8: Compressing tracks")
652 self.stageProgress = 0.0 / channels
653 Log.notice("Compressing song and guitar tracks")
654 self.compressWaveFileToOgg(songWaveFile, outputSongOggFile)
655 self.stageProgress = 2.0 / channels
656 self.compressWaveFileToOgg(guitarWaveFile, outputGuitarOggFile)
657 self.stageProgress = 4.0 / channels
658 if channels in [5, 6]:
659 self.compressWaveFileToOgg(rhythmWaveFile, outputRhythmOggFile)
660 self.stageProgress = 6.0 / channels
661
662
663 for chan in range(channels):
664 os.unlink(os.path.join(workPath, "chan%d.pcm" % chan))
665 os.unlink(songWaveFile)
666 os.unlink(guitarWaveFile)
667 if channels in [5, 6]:
668 os.unlink(rhythmWaveFile)
669
671 os.system('oggenc "%s" --resample 44100 -q 6 -o "%s"' % (waveFile, oggFile))
672
674 if os.name == "nt":
675 return os.system("oggenc -h > NUL:") == 0
676 return os.system("oggenc > /dev/null 2>&1") == 256
677
678 - def importSongs(self, headerPath, archivePath, workPath):
679 try:
680 try:
681 os.mkdir(workPath)
682 except:
683 pass
684
685
686 self.statusText = _("Reading the song list.")
687 songMap = {}
688 vgsMap = {}
689 library = DEFAULT_LIBRARY
690 for line in open(self.engine.resource.fileName("ghmidimap.txt")):
691 fields = map(lambda s: s.strip(), line.strip().split(";"))
692 if fields[0] == "$library":
693 library = os.path.join(DEFAULT_LIBRARY, fields[1])
694 else:
695 songName, fullName, artist = fields
696 songMap[songName] = (library, fullName, artist)
697
698 self.statusText = _("Reading the archive index.")
699 archive = ArkFile(headerPath, archivePath)
700 songs = []
701
702
703 for songName, data in songMap.items():
704 library, fullName, artist = data
705 songPath = self.engine.resource.fileName(library, songName, writable = True)
706
707 vgsMap[songName] = "songs/%s/%s.vgs" % (songName, songName)
708 if not vgsMap[songName] in archive.files:
709 vgsMap[songName] = "songs/%s/%s_sp.vgs" % (songName, songName)
710 if not vgsMap[songName] in archive.files:
711 Log.warn("VGS file for song '%s' not found in this archive." % songName)
712 del songMap[songName]
713 continue
714
715 if os.path.exists(songPath):
716 Log.warn("Song '%s' already exists." % songName)
717 del songMap[songName]
718 continue
719
720 for songName, data in songMap.items():
721 library, fullName, artist = data
722 songPath = self.engine.resource.fileName(library, songName, writable = True)
723 print songPath
724
725 Log.notice("Extracting song '%s'" % songName)
726 self.statusText = _("Extracting %s by %s. %d of %d songs imported. Yeah, this is going to take forever.") % (fullName, artist, len(songs), len(songMap))
727
728 archiveMidiFile = "songs/%s/%s.mid" % (songName, songName)
729 archiveVgsFile = vgsMap[songName]
730
731
732 if not archiveMidiFile in archive.files:
733 Log.warn("MIDI file for song '%s' not found." % songName)
734 continue
735
736 if not archiveVgsFile in archive.files:
737 Log.warn("VGS file for song '%s' not found." % songName)
738 continue
739
740
741
742
743
744
745 vgsFile = archive.openFile(archiveVgsFile)
746 vgsFileLength = archive.fileLength(archiveVgsFile)
747 guitarOggFile = os.path.join(workPath, "guitar.ogg")
748 songOggFile = os.path.join(workPath, "song.ogg")
749 rhythmOggFile = os.path.join(workPath, "rhythm.ogg")
750 self.decodeVgsFile(vgsFile, vgsFileLength, songOggFile, guitarOggFile, rhythmOggFile, workPath)
751 vgsFile.close()
752
753
754 if not os.path.isfile(rhythmOggFile):
755 rhythmOggFile = None
756
757 song = createSong(self.engine, songName, guitarOggFile, songOggFile, rhythmOggFile, library = library)
758 song.info.name = fullName.strip()
759 song.info.artist = artist.strip()
760 song.save()
761
762
763 archive.extractFile(archiveMidiFile, os.path.join(songPath, "notes.mid"))
764
765
766 songs.append(songName)
767
768
769 shutil.rmtree(workPath)
770
771 self.stageInfoText = _("Ready")
772 self.stageProgress = 1.0
773 Log.debug("Songs imported: " + ", ".join(songs))
774 return songs
775 except:
776 self.done = True
777 raise
778
779 - def run(self, ticks):
780 if self.done == True or self.songs is not None:
781 songs = self.songs
782 self.songs = None
783 self.done = None
784 self.statusText = ""
785 if songs:
786 Dialogs.showMessage(self.engine, _("All done! %d songs imported. Have fun!") % (len(songs)))
787 else:
788 Dialogs.showMessage(self.engine, _("No songs could be imported, sorry. Check the log files for more information."))
789 self.engine.view.popLayer(self)
790 if self.wizardStarted:
791 return
792 self.wizardStarted = True
793
794
795 if not self.isOggEncoderPresent():
796 if os.name == "nt":
797 Dialogs.showMessage(self.engine, _("Ogg Vorbis encoder (oggenc.exe) not found. Please install it to the game directory and try again."))
798 else:
799 Dialogs.showMessage(self.engine, _("Ogg Vorbis encoder (oggenc) not found. Please install it and try again."))
800 self.engine.view.popLayer(self)
801 return
802
803 Dialogs.showMessage(self.engine, _("Make sure you have at least 500 megabytes of free disk space before using this importer."))
804
805 path = ""
806 while True:
807 path = Dialogs.getText(self.engine, prompt = _("Enter the path to the mounted Guitar Hero (tm) I/II/Encore DVD"), text = path)
808
809 if not path:
810 self.engine.view.popLayer(self)
811 return
812
813 headerPath = os.path.join(path, "gen", "main.hdr")
814 archivePath = os.path.join(path, "gen", "main_0.ark")
815 if not os.path.isfile(headerPath) or not os.path.isfile(archivePath):
816 Dialogs.showMessage(self.engine, _("That's not it. Try again."))
817 else:
818 break
819
820 workPath = tempfile.mkdtemp("fretsonfire")
821 self.engine.boostBackgroundThreads(True)
822 self.engine.resource.load(self, "songs", lambda: self.importSongs(headerPath, archivePath, workPath))
823
824 - def render(self, visibility, topMost):
825 v = (1 - visibility) ** 2
826
827 self.engine.view.setOrthogonalProjection(normalize = True)
828 font = self.engine.data.font
829
830 try:
831 w, h = font.getStringSize(" ")
832
833 Theme.setSelectedColor()
834 font.render(_("Importing Songs"), (.05, .05 - v))
835 if self.stageInfoText:
836 font.render("%s (%d%%)" % (self.stageInfoText, 100 * self.stageProgress), (.05, .7 + v), scale = 0.001)
837
838 Theme.setBaseColor()
839 Dialogs.wrapText(font, (.1, .3 + v), self.statusText)
840 finally:
841 self.engine.view.resetProjection()
842