import os import math import pygame import fontfx import colors import ui import records import grades import dance import random import options import error from constants import * from interface import * from pygame.mixer import music SORTS = { "subtitle": (lambda x, y: cmp(str(x.info["subtitle"]).lower(), str(y.info["subtitle"]).lower())), "title": (lambda x, y: (cmp(x.info["title"].lower(), y.info["title"].lower()) or SORTS["subtitle"](x, y))), "artist": (lambda x, y: (cmp(x.info["artist"].lower(), y.info["artist"].lower()) or SORTS["title"](x, y))), "bpm": (lambda x, y: (cmp(x.info["bpm"], y.info["bpm"]) or SORTS["title"](x, y))), "mix": (lambda x, y: (cmp(str(x.info["mix"]).lower(), str(y.info["mix"]).lower()) or SORTS["title"](x, y))) } SORT_NAMES = ["mix", "title", "artist", "bpm"] NUM_SORTS = len(SORT_NAMES) SS_HELP = [ "Up / Down: Change song selection", "Left / Right: Change difficulty setting", "Enter / Up Right: Open a folder or start a song", "Escape / Up Left: Closes folder or exit", "Tab / Select: Go to a random song", "Start: Go to the options screen", "F11: Toggle fullscreen - S: Change the sort mode", ] class FolderDisplay(object): def __init__(self, name, type, count): self.name = name self._name = folder_name(name, type) self._type = type self.isfolder = True self.banner = None self.clip = None self.info = {} self.info["title"] = self._name self.info["artist"] = "%d songs" % count self.info["subtitle"] = None def render(self): if self.banner: return name = self.name.encode("ascii", "ignore") for path in [rc_path, pydance_path]: filename = os.path.join(path, "banners", self._type, name+".png") if os.path.exists(filename): self.banner, self.clip = load_banner(filename) break else: if self._type == "mix": for dir in mainconfig["songdir"].split(os.pathsep): dir = os.path.expanduser(dir) fn = os.path.join(dir, name, "banner.png") if os.path.exists(fn): self.banner, self.clip = load_banner(fn) if self.banner == None: self.banner = SongItemDisplay.no_banner self.cdtitle = pygame.Surface([0, 0]) class SongPreview(object): def __init__(self): self._playing = False self._filename = None self._end_time = self._start_time = 0 if not mainconfig["previewmusic"]: music.load(os.path.join(sound_path, "menu.ogg")) music.play(4, 0.0) def preview(self, song): if mainconfig["previewmusic"] and not song.isfolder: if (song.info["filename"].lower().endswith("mp3") and mainconfig["previewmusic"] == 2): music.stop() self._playing = False return if len(song.info["preview"]) == 2: # A DWI/SM/dance-style preview, an offset in the song and a length # to play starting at the offset. self._start, length = song.info["preview"] self._filename = song.info["filename"] else: # KSF-style preview, a separate filename to play. self._start, length = 0, 100 self._filename = song.info["preview"] if self._playing: music.fadeout(500) self._playing = False self._start_time = pygame.time.get_ticks() + 500 self._end_time = int(self._start_time + length * 1000) elif song.isfolder: music.fadeout(500) def update(self, time): if self._filename is None: pass elif time < self._start_time: pass elif not self._playing: try: music.stop() music.load(self._filename) music.set_volume(0.01) # 0.0 stops pygame.mixer.music. music.play(0, self._start) self._playing = True except: # Filename not found? Song is too short? SMPEG blows? music.stop() self.playing = False elif time < self._start_time + 1000: # mixer.music can't fade in music.set_volume((time - self._start_time) / 1000.0) elif time > self._end_time - 1000: music.fadeout(1000) self._playing = False self._filename = None class SongSelect(InterfaceWindow): def __init__(self, songs, courses, screen, game): InterfaceWindow.__init__(self, screen, "newss-bg.png") songs = [s for s in songs if s.difficulty.has_key(game)] if len(songs) == 0: error.ErrorMessage(screen, "You don't have any songs for the game mode (" + game + ") that you selected.") return self._songs = [SongItemDisplay(s) for s in songs] self._index = 0 self._game = game self._config = dict(game_config) self._all_songs = self._songs self._all_valid_songs = [s for s in self._songs if s.info["valid"]] self._list = ListBox(pygame.font.Font(None, 26), [255, 255, 255], 26, 16, 220, [408, 56]) if len(self._songs) > 60 and mainconfig["folders"]: self._create_folders() name = SORT_NAMES[mainconfig["sortmode"]] self._create_folder_list() else: self._folders = None self._base_text = "All Songs" self._songs.sort(SORTS[SORT_NAMES[mainconfig["sortmode"] % NUM_SORTS]]) self._list.set_items([s.info["title"] for s in self._songs]) self._preview = SongPreview() self._preview.preview(self._songs[self._index]) self._song = self._songs[self._index] # Both players must have the same difficulty in locked modes. self._locked = games.GAMES[self._game].couple players = games.GAMES[game].players self._diffs = [] # Current difficulty setting self._diff_widgets = [] # Difficulty widgets self._configs = [] self._diff_names = [] # Last manually selected difficulty name for i in range(players): self._diffs.append(0) self._configs.append(dict(player_config)) self._diff_names.append(" ") d = DifficultyBox([84 + (233 * i), 434]) if not self._song.isfolder: diff_name = self._song.diff_list[game][self._diffs[i]] rank = records.get(self._song.info["recordkey"], diff_name, self._game)[0] grade = grades.grades[self._config["grade"]].grade_by_rank(rank) d.set(diff_name, DIFF_COLORS.get(diff_name, [127, 127, 127]), self._song.difficulty[game][diff_name], grade) else: d.set("None", [127, 127, 127], 0, "?") self._diff_widgets.append(d) ActiveIndicator([405, 259], width = 230).add(self._sprites) self._banner = BannerDisplay([205, 230]) self._banner.set_song(self._song) self._sprites.add(HelpText(SS_HELP, [255, 255, 255], [0, 0, 0], pygame.font.Font(None, 22), [206, 20])) self._title = TextDisplay(30, [210, 28], [414, 27]) self._sprites.add(self._diff_widgets + [self._banner, self._list, self._title]) self._screen.blit(self._bg, [0, 0]) pygame.display.update() self.loop() music.fadeout(500) pygame.time.wait(500) # FIXME Does this belong in the menu code? Probably. music.load(os.path.join(sound_path, "menu.ogg")) music.set_volume(1.0) music.play(4, 0.0) player_config.update(self._configs[0]) # Save p1's settings def loop(self): pid, ev = ui.ui.poll() root_idx = 0 self._list.set_index(self._index) self._title.set_text(self._base_text + " - %d/%d" % (self._index + 1, len(self._songs))) while not (ev == ui.CANCEL and (not self._folders or self._song.isfolder)): # Inactive player. If the event isn't set to ui.PASS, we try to use # the pid later, which will be bad. if pid >= len(self._diffs): ev = ui.PASS elif ev == ui.UP: self._index -= 1 elif ev == ui.DOWN: self._index += 1 elif ev == ui.PGUP: self._index -= 7 ev = ui.UP elif ev == ui.PGDN: self._index += 7 ev = ui.DOWN elif ev == ui.LEFT: if not self._song.isfolder: didx = (self._diffs[pid] - 1) % len(self._song.diff_list[self._game]) self._diffs[pid] = didx name = self._song.diff_list[self._game][didx] self._diff_names[pid] = name elif ev == ui.RIGHT: if not self._song.isfolder: didx = (self._diffs[pid] + 1) % len(self._song.diff_list[self._game]) self._diffs[pid] = didx name = self._song.diff_list[self._game][didx] self._diff_names[pid] = name elif ev == ui.SELECT: if self._song.isfolder: if len(self._all_valid_songs) > 0: self._song = random.choice(self._all_valid_songs) root_idx = [fol.name for fol in self._songs].index(self._song.folder[SORT_NAMES[mainconfig["sortmode"]]]) fol = self._song.folder[SORT_NAMES[mainconfig["sortmode"]]] self._create_song_list(fol) self._index = self._songs.index(self._song) else: error.ErrorMessage(screen, "You don't have any songs that are marked " + "\"valid\" for random selection.") else: valid_songs = [s for s in self._songs if s.info["valid"]] if len(valid_songs) > 0: self._song = random.choice(valid_songs) self._index = self._songs.index(self._song) else: error.ErrorMessage(screen, "You don't have any songs here that " + "are marked \"valid\" for random selection.") elif ev == ui.START: options.OptionScreen(self._configs, self._config, self._screen) self._screen.blit(self._bg, [0, 0]) self.update() pygame.display.update() elif ev == ui.SORT: s = self._songs[self._index] mainconfig["sortmode"] = (mainconfig["sortmode"] + 1) % NUM_SORTS if self._folders: if s.isfolder: self._create_folder_list() else: self._create_song_list(s.folder[SORT_NAMES[mainconfig["sortmode"]]]) self._index = self._songs.index(s) else: self._songs.sort(SORTS[SORT_NAMES[mainconfig["sortmode"]]]) self._index = self._songs.index(s) self._list.set_items([s.info["title"] for s in self._songs]) elif ev == ui.CONFIRM: if self._song.isfolder: self._create_song_list(self._song.name) root_idx = self._index self._index = 0 else: music.fadeout(500) diffs = [self._song.diff_list[self._game][d] for i, d in enumerate(self._diffs)] dance.play(self._screen, [(self._song.filename, diffs)], self._configs, self._config, self._game) music.fadeout(500) # The just-played song self._screen.blit(self._bg, [0, 0]) pygame.display.update() ui.ui.clear() elif ev == ui.CANCEL: self._create_folder_list() for d in self._diff_widgets: d.set("None", [127, 127, 127], 0, "?") self._index = root_idx elif ev == ui.FULLSCREEN: mainconfig["fullscreen"] ^= 1 pygame.display.toggle_fullscreen() self._index %= len(self._songs) self._song = self._songs[self._index] if self._locked and ev in [ui.LEFT, ui.RIGHT]: for i in range(len(self._diffs)): self._diffs[i] = self._diffs[pid] self._diff_names[i] = self._diff_names[pid] if ev in [ui.CANCEL, ui.UP, ui.DOWN, ui.SELECT, ui.CONFIRM]: self._preview.preview(self._song) self._banner.set_song(self._song) if ev in [ui.CANCEL, ui.UP, ui.DOWN, ui.SELECT, ui.CONFIRM, ui.SORT]: if ev == ui.UP: self._list.set_index(self._index, -1) elif ev == ui.DOWN: self._list.set_index(self._index, 1) else: self._list.set_index(self._index, 0) # don't animate self._title.set_text(self._base_text + " - %d/%d" % (self._index + 1, len(self._songs))) if ev in [ui.UP, ui.DOWN, ui.SELECT, ui.SORT, ui.CONFIRM]: if not self._song.isfolder: for i in range(len(self._diffs)): name = self._diff_names[i] if name in self._song.diff_list[self._game]: self._diffs[i] = self._song.diff_list[self._game].index(name) else: self._diffs[i] %= len(self._song.diff_list[self._game]) if ev in [ui.UP, ui.DOWN, ui.LEFT, ui.RIGHT, ui.SELECT, ui.CONFIRM]: if not self._song.isfolder: for i in range(len(self._diffs)): name = self._song.diff_list[self._game][self._diffs[i]] rank = records.get(self._song.info["recordkey"], name, self._game)[0] grade = grades.grades[self._config["grade"]].grade_by_rank(rank) self._diff_widgets[i].set(name, DIFF_COLORS.get(name, [127,127,127]), self._song.difficulty[self._game][name], grade) self.update() pid, ev = ui.ui.poll() def update(self): InterfaceWindow.update(self) self._preview.update(pygame.time.get_ticks()) def _create_folders(self): mixes = {} artists = {} titles = {} bpms = {} for s in self._all_songs: if s.info["mix"] not in mixes: mixes[s.info["mix"]] = [] mixes[s.info["mix"]].append(s) s.folder["mix"] = s.info["mix"] label = s.info["title"][0].capitalize() if label not in titles: titles[label] = [] titles[label].append(s) s.folder["title"] = label label = s.info["artist"][0].capitalize() if label not in artists: artists[label] = [] artists[label].append(s) s.folder["artist"] = label for rng in ((0, 50), (50, 100), (100, 121), (110, 120), (120, 130), (130, 140), (140, 150), (150, 160), (160, 170), (170, 180), (180, 190), (190, 200), (200, 225), (225, 250), (250, 275), (275, 299.99999999)): if rng[0] < s.info["bpm"] <= rng[1]: label = "%3d - %3d" % rng if not label in bpms: bpms[label] = [] bpms[label].append(s) s.folder["bpm"] = label if s.info["bpm"] >= 300: if "300+" not in bpms: bpms["300+"] = [] bpms["300+"].append(s) s.folder["bpm"] = "300+" self._folders = { "mix": mixes, "title": titles, "artist": artists, "bpm": bpms } def _create_folder_list(self): sort = SORT_NAMES[mainconfig["sortmode"]] lst = self._folders[sort].keys() lst.sort(lambda x, y: cmp(x.lower(), y.lower())) new_songs = [FolderDisplay(folder, sort, len(self._folders[sort][folder])) for folder in lst] self._songs = new_songs self._list.set_items([s.info["title"] for s in self._songs]) self._base_text = "Sort by %s" % sort.capitalize() def _create_song_list(self, folder): sort = SORT_NAMES[mainconfig["sortmode"]] songlist = self._folders[sort][folder] songlist.sort(SORTS[sort]) self._songs = songlist self._list.set_items([s.info["title"] for s in self._songs]) self._base_text = folder_name(folder, sort)