diff --git a/pgfw/Audio.py b/pgfw/Audio.py index 7c0b4c2..62cdecd 100644 --- a/pgfw/Audio.py +++ b/pgfw/Audio.py @@ -12,10 +12,12 @@ class Audio(GameChild): UP, DOWN = .1, -.1 BASE_VOLUME = 1.0 + CONFIG_SEPARATOR = "," def __init__(self, game): GameChild.__init__(self, game) # self.original_volumes = {} + self.current_bgm = None self.volume = self.BASE_VOLUME if self.check_command_line("-mute"): self.volume = 0 @@ -30,11 +32,7 @@ class Audio(GameChild): if mute: self.volume = 0 elif increment: - self.volume += increment - if self.volume > 1: - self.volume = 1.0 - elif self.volume < 0: - self.volume = 0 + self.volume = clamp(self.volume + increment, 0, 1.0) else: self.volume = volume @@ -58,7 +56,7 @@ class Audio(GameChild): # def load_sfx(self, sfx_location=None): for name, sfx_definition in self.get_configuration("sfx").items(): - sfx_definition_members = sfx_definition.split(",") + sfx_definition_members = sfx_definition.split(self.CONFIG_SEPARATOR) path, volume, fade_out, loops, maxtime = sfx_definition_members[0], 1.0, 0, 0, 0 for ii, member in enumerate(sfx_definition_members[1:]): if ii == 0: @@ -96,7 +94,7 @@ class Audio(GameChild): def load_sfx_file(self, path, name=None, replace=False, prefix="", volume=1.0, fade_out=0, loops=0, maxtime=0): path = self.get_resource(path) - if path and self.is_sound_file(path): + if path and self.is_loadable(path): if name is None: name = prefix + re.sub("\.[^.]*$", "", os.path.basename(path)) if not replace and name in self.sfx: @@ -117,8 +115,13 @@ class Audio(GameChild): # written to config file # def load_bgm(self): - for name, path in self.get_configuration("bgm").items(): - self.set_bgm(path, name) + for name, bgm_definition in self.get_configuration("bgm").items(): + bgm_definition_members = bgm_definition.split(self.CONFIG_SEPARATOR) + path, volume = bgm_definition_members[0], 1.0 + for ii, member in enumerate(bgm_definition_members[1:]): + if ii == 0: + volume = float(member) + self.set_bgm(path, name, volume=volume) for root in self.get_configuration("audio", "bgm-project-path"): if os.path.exists(root): print("checking {} for music".format(root)) @@ -133,7 +136,7 @@ class Audio(GameChild): prefix = re.sub("/", "_", prefix) + "_" self.set_bgm(os.path.join(node, leaf), prefix=prefix) - def set_bgm(self, path, name=None, prefix=""): + def set_bgm(self, path, name=None, prefix="", volume=1.0): print("setting {} music to {}".format(name, path)) try: pygame.mixer.music.load(path) @@ -142,12 +145,22 @@ class Audio(GameChild): return False if name is None: name = os.path.basename(path).split(".")[0] - self.bgm[prefix + name] = path + self.bgm[prefix + name] = BGM(self, path, volume) return True - def play_bgm(self, name): - pygame.mixer.music.load(self.bgm[name]) - pygame.mixer.music.play(-1) + def play_bgm(self, name=None, store_as_current=True, start=0): + if name is None: + bgm = self.current_bgm + else: + bgm = self.bgm[name] + pygame.mixer.music.load(bgm.get_path()) + try: + pygame.mixer.music.play(-1, start) + except pygame.error: + pygame.mixer.music.play(-1) + pygame.mixer.music.set_volume(bgm.get_volume()) + if store_as_current: + self.current_bgm = bgm def is_sound_file(self, path): return path.split(".")[-1] in self.get_configuration("audio", "sfx-extensions") @@ -159,6 +172,13 @@ class Audio(GameChild): return False return True + def is_streamable(self, path): + try: + pygame.mixer.music.load(path) + except: + return False + return True + def update(self): # for ii in range(pygame.mixer.get_num_channels()): # channel = pygame.mixer.Channel(ii) @@ -170,6 +190,26 @@ class Audio(GameChild): self.audio_panel.update() +class BGM(GameChild): + + def __init__(self, parent, path, volume=1.0): + GameChild.__init__(self, parent) + self.path = path + self.volume = volume + + def get_path(self): + return self.path + + def adjust_volume(self, increment): + self.volume = clamp(self.volume + increment, 0, 1.0) + if self.parent.current_bgm == self: + pygame.mixer.music.set_volume(self.volume) + return self.volume + + def get_volume(self): + return self.volume + + class SoundEffect(GameChild, pygame.mixer.Sound): def __init__(self, parent, path, volume=1.0, loops=0, fade_out_length=0, @@ -256,6 +296,7 @@ class AudioPanel(Animation): def __init__(self, parent): Animation.__init__(self, parent) self.rows = [] + self.bgm_elapsed = None font_path = self.get_resource(self.get_configuration("audio", "panel-font")) self.font_large = pygame.font.Font(font_path, 15) self.font_medium = pygame.font.Font(font_path, 12) @@ -277,11 +318,17 @@ class AudioPanel(Animation): def activate(self): pygame.mouse.set_visible(True) self.active = True + if pygame.mixer.music.get_busy(): + self.bgm_elapsed = pygame.mixer.music.get_pos() / 1000 + pygame.mixer.music.stop() + pygame.mixer.stop() # self.build() def deactivate(self): pygame.mouse.set_visible(self.get_configuration("mouse", "visible")) self.active = False + if self.bgm_elapsed is not None: + self.get_audio().play_bgm(start=self.bgm_elapsed) def respond(self, event): if self.get_delegate().compare(event, "toggle-audio-panel") and self.get_audio().sfx: @@ -315,6 +362,7 @@ class AudioPanel(Animation): Animation.update(self) ds = self.get_display_surface() dsr = ds.get_rect() + ds.fill((0, 0, 0)) corner = Vector(self.MARGIN, self.MARGIN) index = self.row_offset for row in self.rows: @@ -331,8 +379,8 @@ class AudioPanel(Animation): class AudioPanelRow(BlinkingSprite): - BACKGROUND = pygame.Color(255, 255, 255, 210) - FOREGROUND = pygame.Color(0, 0, 0, 210) + BACKGROUND = pygame.Color(128, 192, 255, 255) + FOREGROUND = pygame.Color(0, 0, 0, 255) WIDTH = .5 HEIGHT = 30 INDENT = 4 @@ -347,14 +395,18 @@ class AudioPanelRow(BlinkingSprite): self.font = self.parent.font_large self.is_bgm = is_bgm self.build() + font_medium = self.parent.font_medium + font_small = self.parent.font_small + if self.is_bgm: + volume = self.get_bgm().volume + volume_function = self.get_bgm().adjust_volume + else: + volume = self.get_sound_effect().local_volume + volume_function = self.get_sound_effect().adjust_volume + self.volume_spinner = AudioPanelSpinner( + self, font_medium, font_small, self.SLIDER_W, self.location.h, .05, + volume, volume_function, self.FOREGROUND, self.BACKGROUND, 2, "vol") if not self.is_bgm: - font_medium = self.parent.font_medium - font_small = self.parent.font_small - self.volume_spinner = AudioPanelSpinner( - self, font_medium, font_small, self.SLIDER_W, self.location.h, .05, - self.get_sound_effect().local_volume, - self.get_sound_effect().adjust_volume, self.FOREGROUND, - self.BACKGROUND, 2, "vol") self.fade_out_spinner = AudioPanelSpinner( self, font_medium, font_small, self.SLIDER_W, self.location.h, .1, self.get_sound_effect().fade_out_length, @@ -371,7 +423,7 @@ class AudioPanelRow(BlinkingSprite): self.get_sound_effect().adjust_maxtime, self.FOREGROUND, self.BACKGROUND, 1, "cutoff") if self.is_bgm: - callback, kwargs = self.get_game().get_audio().play_bgm, {"name": self.key} + callback, kwargs = self.get_game().get_audio().play_bgm, {"name": self.key, "store_as_current": False} else: callback, kwargs = self.get_sound_effect().play, {} self.play_button = AudioPanelButton(self, callback, kwargs) @@ -441,7 +493,7 @@ class AudioPanelRow(BlinkingSprite): file_sprite.display_surface = surface file_name_sprite = Sprite(self) if self.is_bgm: - file_name = self.get_bgm() + file_name = self.get_bgm().path else: file_name = self.get_sound_effect().path file_name_text = self.font.render(file_name, True, self.FOREGROUND) @@ -465,7 +517,8 @@ class AudioPanelRow(BlinkingSprite): if not self.get_configuration().has_section(section_name): self.get_configuration().add_section(section_name) if self.is_bgm: - config_value = self.get_bgm() + bgm = self.get_bgm() + config_value = "{}, {:.2f}".format(bgm.path, bgm.volume) else: sound_effect = self.get_sound_effect() config_value = "{}, {:.2f}, {:.2f}, {}, {:.2f}".format( @@ -480,14 +533,14 @@ class AudioPanelRow(BlinkingSprite): def update(self): self.play_button.location.midleft = self.location.move(5, 0).midright self.stop_button.location.midleft = self.play_button.location.midright + self.volume_spinner.location.midleft = self.stop_button.location.move(5, 0).midright if not self.is_bgm: - self.volume_spinner.location.midleft = self.stop_button.location.move(5, 0).midright self.fade_out_spinner.location.midleft = self.volume_spinner.location.midright self.loops_spinner.location.midleft = self.fade_out_spinner.location.midright self.maxtime_spinner.location.midleft = self.loops_spinner.location.midright Sprite.update(self) + self.volume_spinner.update() if not self.is_bgm: - self.volume_spinner.update() self.fade_out_spinner.update() self.loops_spinner.update() self.maxtime_spinner.update() @@ -507,6 +560,7 @@ class AudioPanelFileBrowser(Sprite): self.rows = [] self.font = self.parent.font_large self.previewing_sound = None + self.previewing_sound_row = None ds = self.get_display_surface() dsr = ds.get_rect() surface = pygame.Surface((dsr.w * self.WIDTH - 2, dsr.h * self.HEIGHT - 2), SRCALPHA) @@ -610,7 +664,7 @@ class AudioPanelFileBrowser(Sprite): row.path = path text = self.font.render(path, True, self.COLORS[1]) surface = pygame.Surface((self.location.w, text.get_height()), SRCALPHA) - surface.blit(text, (0, 0)) + surface.blit(text, (8, 0)) surface.fill(self.COLORS[1], (0, surface.get_height() - 1, self.location.w, 1)) row.add_frame(surface) row.display_surface = self.get_current_frame() @@ -618,7 +672,7 @@ class AudioPanelFileBrowser(Sprite): self.rows.append(row) full_path = os.path.join(os.path.sep.join(self.trail), path) if self.get_audio().is_sound_file(full_path): - button = AudioPanelButton(self, self.preview, {"path": full_path}, [row, self]) + button = AudioPanelButton(self, self.preview, {"path": full_path, "row": row}, [row, self]) row.set_child("button", button) frame = pygame.Surface([text.get_height()] * 2, SRCALPHA) w, h = frame.get_size() @@ -626,14 +680,22 @@ class AudioPanelFileBrowser(Sprite): frame, self.COLORS[1], ((w * .25, h * .25), (w * .25, h * .75), (w * .75, h * .5))) button.add_frame(frame) button.display_surface = row.get_current_frame() - button.location.right = self.location.w + button.location.right = self.location.w - 10 - def preview(self, path): - if self.get_audio().is_sound_file(path): + def preview(self, path, row): + is_bgm = self.parent.get_selected().is_bgm + audio = self.get_audio() + if is_bgm and audio.is_streamable(path) or not is_bgm and audio.is_loadable(path): if self.previewing_sound is not None: self.previewing_sound.stop() - self.previewing_sound = SoundEffect(self, path) - self.previewing_sound.play() + pygame.mixer.music.stop() + if is_bgm: + pygame.mixer.music.load(path) + pygame.mixer.music.play(-1) + else: + self.previewing_sound = SoundEffect(self, path) + self.previewing_sound.play() + self.previewing_sound_row = row def update(self): self.get_current_frame().blit(self.background, (0, 0)) @@ -653,6 +715,14 @@ class AudioPanelFileBrowser(Sprite): index += 1 for row in self.rows: row.update() + for location in row.locations: + if location.collidepoint(*Vector(*pygame.mouse.get_pos()).get_moved( + -self.location.left, -self.location.top)) or \ + row == self.previewing_sound_row: + self.get_current_frame().fill(self.COLORS[1], ( + location.topleft, (6, location.h))) + self.get_current_frame().fill(self.COLORS[1], ( + location.move(-8, 0).topright, (6, location.h))) Sprite.update(self) @@ -741,12 +811,13 @@ class AudioPanelSpinner(Sprite): class AudioPanelButton(Sprite): - def __init__(self, parent, callback, callback_kwargs={}, containers=[]): + def __init__(self, parent, callback, callback_kwargs={}, containers=[], pass_mods=False): Sprite.__init__(self, parent) self.callback = callback self.callback_kwargs = callback_kwargs self.containers = containers self.clickable = True + self.pass_mods = pass_mods self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN) def unsubscribe(self, callback=None, kind=None): @@ -761,7 +832,11 @@ class AudioPanelButton(Sprite): for container in self.containers: pos.move(-container.location.left, -container.location.top) if self.collide(pos): - self.callback(**self.callback_kwargs) + if self.pass_mods: + kwargs = dict(**self.callback_kwargs, **{"mods": pygame.key.get_mods()}) + else: + kwargs = self.callback_kwargs + self.callback(**kwargs) def set_clickable(self, clickable=True): self.clickable = clickable diff --git a/pgfw/Game.py b/pgfw/Game.py index 76685c7..93d2ac5 100644 --- a/pgfw/Game.py +++ b/pgfw/Game.py @@ -59,7 +59,7 @@ class Game(Animation): def frame(self): self.time_filter.update() self.delegate.dispatch() - if not self.interpolator.is_gui_active(): + if not self.interpolator.is_gui_active() and not self.get_audio().audio_panel.active: Animation.update(self) if self.confirming_quit: self.time_filter.close() @@ -67,7 +67,7 @@ class Game(Animation): else: self.time_filter.open() self.update() - else: + elif self.interpolator.is_gui_active(): self.interpolator.gui.update() self.audio.update() if self.video_recorder.requested: