commit a5463094e22d33bd1d6dfa43bf24abb6d2403e31 Author: Trysdyn Black Date: Wed Sep 26 17:33:07 2018 -0700 Initial commit diff --git a/main.pyw b/main.pyw new file mode 100644 index 0000000..9a33666 --- /dev/null +++ b/main.pyw @@ -0,0 +1,222 @@ +import json +import os +import sys +import pygame +import time + + +class Visualizer: + def __init__(self, profile): + # Change to script dir to allow loading of configs relative to script + if getattr(sys, 'frozen', False): + # The application is frozen, figure out the path of the EXE + pathname = os.path.dirname(sys.executable) + else: + # The application is not frozen, get PWD + pathname = os.path.dirname(__file__) + if pathname: + os.chdir(pathname) + + # Load config + self.config = self.load_config(profile)["profile"] + + # initialize the screen + self.win_size = self.config["size"] + pygame.init() + self.screen = pygame.display.set_mode(self.config["size"], pygame.RESIZABLE) + pygame.display.set_caption("SRTK Gamepad Visualizer: %s" % profile) + + # Load and prep the background + background_file = os.path.join("profiles", profile, self.config["background_image"]) + self.background = pygame.image.load(background_file).convert_alpha() + + # Create the buffer image to draw on, set to the size of the background image + self.buffer = pygame.surface.Surface(self.background.get_size()) + + # Create the font + self.text_font = pygame.font.SysFont(self.config["font_face"], self.config["font_size"]) + self.font_color = self.config["font_color"] + self.text_pos = self.config["text_pos"] + + # APS counter + self.actions = [] + self.aps_min = self.config["aps_minimum"] + self.aps = 0 + self.aps_throttle = 3 + + # Set up our inputs + # Inputs are defined as key = (light position, light surface object) + self.active_inputs = set() + self.last_inputs = set() + self.inputs = {} + + inputs = self.config["inputs"] + for i in inputs: + if "enabled" in inputs[i] and not inputs[i]["enabled"]: + continue + + pos = inputs[i]["pos"] + size = inputs[i]["size"] + color = inputs[i]["color"] if "color" in inputs[i] else self.config["default_color"] + + img = pygame.Surface(size) + img.fill(color) + + try: + i = int(i) + except: + pass + + self.inputs[i] = (pos, img) + + def load_config(self, profile): + infile_name = os.path.join("profiles", profile, "config.txt") + infile = open(infile_name, "r") + config = json.load(infile) + + return config + + def button_down(self, button_id): + self.active_inputs.add(button_id) + self.actions.append(time.time()) + + def button_up(self, button_id): + try: + self.active_inputs.remove(button_id) + except: + pass + + def axis_change(self, axis, value): + if value > 0.1: + self.active_inputs.add("A%dP" % axis) + elif value < -0.1: + self.active_inputs.add("A%dM" % axis) + else: + for a in ["A%dM" % axis, "A%dP" % axis]: + try: + self.active_inputs.remove(a) + except: + pass + + def hat_change(self, hat, value): + # H0U, H0D, H0L, H0R + if value[0] < 0: + self.active_inputs.add("H%dL" % hat) + elif value[0] > 0: + self.active_inputs.add("H%dR" % hat) + else: + for a in ["H%dL" % hat, "H%dR" % hat]: + try: + self.active_inputs.remove(a) + except: + pass + + if value[1] < 0: + self.active_inputs.add("H%dD" % hat) + elif value[1] > 0: + self.active_inputs.add("H%dU" % hat) + else: + for a in ["H%dU" % hat, "H%dD" % hat]: + try: + self.active_inputs.remove(a) + except: + pass + + def update(self): + """ + Handles screen drawing. Blits images to a buffer image, resizes the + buffer to the size of the screen, then blits the buffer to the screen. + """ + if self.active_inputs == self.last_inputs: + return None + + self.last_inputs = set(self.active_inputs) + + buf = self.buffer + screen = self.screen + + # Blank the buffer, draw the button hilights + buf.fill(self.config["background_color"]) + + for i in self.active_inputs: + try: + loc, img = self.inputs[i] + buf.blit(img, loc) + except KeyError: + pass + + # Draw the background overtop as a mask + buf.blit(self.background, (0, 0)) + + # Draw APS + # Throttle keeps us from "Vibrating number syndrome" + self.aps_throttle -= 1 + if self.aps_throttle < 1: + self.aps_throttle = 3 + + # Only score APS off the last 30 actions and last 5s + while self.actions and self.actions[0] < (time.time() - 5): + self.actions.pop(0) + if len(self.actions) > 30: + self.actions = self.actions[-30:] + + # Time from last action to current time, to get APS + # We want at least 5 actions before we consider drawing APS + if len(self.actions) > 5: + aps_len = time.time() - self.actions[0] + 0.01 + self.aps = len(self.actions) / aps_len + else: + self.aps = 0 + + # We only care about APS if we're mashing, so only draw it if it's over aps_min + if self.aps > self.aps_min: + aps_surf = self.text_font.render("%0.1f" % self.aps, 1, self.font_color) + buf.blit(aps_surf, self.config["text_pos"]) + + # Scale the buffer to the screen size if necessary + if buf.get_size() != screen.get_size(): + buf = pygame.transform.scale(buf, screen.get_size()) + + # Blit buffer to screen + screen.blit(buf, (0, 0)) + pygame.display.flip() + +if __name__ == "__main__": + # Read in a profile name from commandline + if len(sys.argv) == 2: + profile = sys.argv[1] + else: + profile = "universal" + + # Initialize all our joysticks so we get the one we want + # Since this is a one-player app, this should be fine... + pygame.joystick.init() + joysticks = [pygame.joystick.Joystick(x) for x in range(pygame.joystick.get_count())] + for joy in joysticks: + joy.init() + + # Initialize our visualizer context + vis = Visualizer(profile) + + quit = False + + while not quit: + + # Note that we wait for pygame events before doing any redraws + # This limits redraws to when button states change, normally + event = pygame.event.wait() + if event.type == pygame.VIDEORESIZE: + vis.win_size = (event.w, event.h) + vis.screen = pygame.display.set_mode(vis.win_size, pygame.RESIZABLE) + elif event.type == pygame.QUIT: + quit = True + elif event.type == pygame.JOYBUTTONDOWN: + vis.button_down(event.button) + elif event.type == pygame.JOYBUTTONUP: + vis.button_up(event.button) + elif event.type == pygame.JOYAXISMOTION: + vis.axis_change(event.axis, event.value) + elif event.type == pygame.JOYHATMOTION: + vis.hat_change(event.hat, event.value) + + vis.update() diff --git a/profiles/genesis/base.png b/profiles/genesis/base.png new file mode 100644 index 0000000..f300267 Binary files /dev/null and b/profiles/genesis/base.png differ diff --git a/profiles/genesis/config.txt b/profiles/genesis/config.txt new file mode 100644 index 0000000..8581b4b --- /dev/null +++ b/profiles/genesis/config.txt @@ -0,0 +1,76 @@ +{ + "profile": { + "name": "SNES", + "background_image": "base.png", + "background_color": [0, 0, 0], + "size": [270, 159], + "color_key": [255, 255, 255], + "default_color": [255, 244, 61], + "inputs": { + "0": { + "comment": "Square (A)", + "size": [25, 25], + "pos": [172, 73] + }, + "1": { + "comment": "Cross (B)", + "size": [25, 25], + "pos": [198, 59] + }, + "2": { + "comment": "Circle (C)", + "size": [25, 25], + "pos": [226, 47] + }, + "9": { + "comment": "Start", + "size": [31, 20], + "pos": [178, 23] + }, + "6": { + "comment": "Split", + "size": [42, 20], + "pos": [114, 25], + "color": [0, 255, 0] + }, + "12": { + "comment": "Reset", + "size": [55, 15], + "pos": [107, 49], + "color": [255, 0, 0] + }, + "A0M": { + "size": [18, 11], + "pos": [34, 65] + }, + "A0P": { + "size": [18, 11], + "pos": [66, 65] + }, + "A1M": { + "size": [11, 18], + "pos": [54, 45] + }, + "A1P": { + "size": [11, 18], + "pos": [54, 77] + }, + "H0L": { + "size": [18, 11], + "pos": [34, 65] + }, + "H0R": { + "size": [18, 11], + "pos": [66, 65] + }, + "H0U": { + "size": [11, 18], + "pos": [54, 45] + }, + "H0D": { + "size": [11, 18], + "pos": [54, 77] + } + } + } +} diff --git a/profiles/nes/base.png b/profiles/nes/base.png new file mode 100644 index 0000000..67034c7 Binary files /dev/null and b/profiles/nes/base.png differ diff --git a/profiles/nes/config.txt b/profiles/nes/config.txt new file mode 100644 index 0000000..240b407 --- /dev/null +++ b/profiles/nes/config.txt @@ -0,0 +1,76 @@ +{ + "profile": { + "name": "NES", + "background_image": "base.png", + "background_color": [0, 0, 0], + "size": [197, 56], + "color_key": [255, 255, 255], + "default_color": [255, 244, 61], + "inputs": { + "1": { + "comment": "Square (B)", + "size": [25, 25], + "pos": [164, 27] + }, + "0": { + "comment": "Cross (A)", + "size": [25, 25], + "pos": [135, 27] + }, + "23": { + "comment": "Split", + "size": [40, 19], + "pos": [58, 3], + "color": [0, 255, 0] + }, + "22": { + "comment": "Reset", + "size": [51, 14], + "pos": [139, 4], + "color": [255, 0, 0] + }, + "6": { + "comment": "Select", + "size": [22, 10], + "pos": [65, 36] + }, + "7": { + "comment": "Start", + "size": [22, 10], + "pos": [98, 36] + }, + "A0M": { + "size": [10, 10], + "pos": [6, 22] + }, + "A0P": { + "size": [10, 10], + "pos": [35, 22] + }, + "A1M": { + "size": [10, 10], + "pos": [20, 8] + }, + "A1P": { + "size": [10, 10], + "pos": [20, 37] + }, + "H0L": { + "size": [10, 10], + "pos": [6, 22] + }, + "H0R": { + "size": [10, 10], + "pos": [35, 22] + }, + "H0U": { + "size": [10, 10], + "pos": [20, 8] + }, + "H0D": { + "size": [10, 10], + "pos": [20, 37] + } + } + } +} diff --git a/profiles/psx/base.png b/profiles/psx/base.png new file mode 100644 index 0000000..88535e8 Binary files /dev/null and b/profiles/psx/base.png differ diff --git a/profiles/psx/config.txt b/profiles/psx/config.txt new file mode 100644 index 0000000..b47d124 --- /dev/null +++ b/profiles/psx/config.txt @@ -0,0 +1,82 @@ +{ + "profile": { + "name": "PSX", + "background_image": "base.png", + "background_color": [0, 0, 0], + "size": [202, 86], + "color_key": [255, 255, 255], + "default_color": [255, 244, 61], + "inputs": { + "3": { + "comment": "Triangle", + "size": [21, 21], + "pos": [155, 17] + }, + "1": { + "comment": "Circle", + "size": [21, 21], + "pos": [177, 39] + }, + "0": { + "comment": "Cross", + "size": [21, 21], + "pos": [155, 61] + }, + "2": { + "comment": "Square", + "size": [21, 21], + "pos": [133, 39] + }, + "4": { + "comment": "L1", + "size": [16, 10], + "pos": [25, 2] + }, + "5": { + "comment": "R1", + "size": [17, 10], + "pos": [153, 2] + }, + "A2P": { + "comment": "L2", + "size": [17, 10], + "pos": [34, 2] + }, + "A2M": { + "comment": "R2", + "size": [18, 10], + "pos": [161, 2] + }, + "6": { + "comment": "select", + "size": [18, 12], + "pos": [79, 48] + }, + "7": { + "comment": "start", + "size": [18, 12], + "pos": [105, 48] + }, + "H0L": { + "comment": "left", + "size": [26, 15], + "pos": [5, 41] + }, + "H0R": { + "comment": "right", + "size": [26, 15], + "pos": [42, 41] + }, + "H0U": { + "comment": "up", + "size": [15, 26], + "pos": [29, 17] + }, + "H0D": { + "comment": "down", + "size": [15, 26], + "pos": [29, 54] + } + } + } +} diff --git a/profiles/snes/base.png b/profiles/snes/base.png new file mode 100644 index 0000000..34a9e1a Binary files /dev/null and b/profiles/snes/base.png differ diff --git a/profiles/snes/config.txt b/profiles/snes/config.txt new file mode 100644 index 0000000..7f28a52 --- /dev/null +++ b/profiles/snes/config.txt @@ -0,0 +1,96 @@ +{ + "profile": { + "name": "SNES", + "background_image": "base.png", + "background_color": [0, 0, 0], + "size": [270, 122], + "color_key": [255, 255, 255], + "default_color": [255, 244, 61], + "inputs": { + "2": { + "comment": "Square (Y)", + "size": [25, 25], + "pos": [176, 53] + }, + "0": { + "comment": "Cross (B)", + "size": [25, 25], + "pos": [201, 73] + }, + "1": { + "comment": "Circle (A)", + "size": [25, 25], + "pos": [225, 52] + }, + "3": { + "comment": "Triangle (X)", + "size": [25, 25], + "pos": [200, 32] + }, + "4": { + "comment": "L1 (L)", + "size": [55, 10], + "pos": [27, 3] + }, + "5": { + "comment": "R1 (R)", + "size": [55, 10], + "pos": [188, 3] + }, + "6": { + "comment": "Select", + "size": [25, 25], + "pos": [100, 64] + }, + "7": { + "comment": "Start", + "size": [25, 25], + "pos": [130, 64] + }, + "8": { + "comment": "Split", + "size": [40, 19], + "pos": [108, 15], + "color": [0, 255, 0] + }, + "12": { + "comment": "Reset", + "size": [51, 14], + "pos": [105, 42], + "color": [255, 0, 0] + }, + "A0M": { + "size": [10, 10], + "pos": [40, 58] + }, + "A0P": { + "size": [10, 10], + "pos": [66, 58] + }, + "A1M": { + "size": [10, 10], + "pos": [53, 45] + }, + "A1P": { + "size": [10, 10], + "pos": [53, 72] + }, + "H0L": { + "size": [10, 10], + "pos": [40, 58] + }, + "H0R": { + "size": [10, 10], + "pos": [66, 58] + }, + "H0U": { + "size": [10, 10], + "pos": [53, 45] + }, + "H0D": { + "size": [10, 10], + "pos": [53, 72] + } + } + } +} diff --git a/profiles/universal - sfc/base.png b/profiles/universal - sfc/base.png new file mode 100644 index 0000000..833744d Binary files /dev/null and b/profiles/universal - sfc/base.png differ diff --git a/profiles/universal - sfc/config.txt b/profiles/universal - sfc/config.txt new file mode 100644 index 0000000..5cbbade --- /dev/null +++ b/profiles/universal - sfc/config.txt @@ -0,0 +1,73 @@ +{ + "profile": { + "name": "Universal", + "font_face": "Golden Sun Regular", + "font_size": 28, + "font_color": [255, 255, 255], + "background_image": "base.png", + "background_color": [50, 50, 50], + "size": [320, 128], + "color_key": [0, 0, 0], + "default_color": [181, 166, 66], + "text_pos": [200, 10], + "aps_minimum": 100, + "inputs": { + "4": { + "comment": "Square (Y)", + "size": [46, 46], + "pos": [317, 86] + }, + "1": { + "comment": "Cross (B)", + "size": [46, 46], + "pos": [359, 127] + }, + "0": { + "comment": "Circle (A)", + "size": [46, 46], + "pos": [401, 85] + }, + "3": { + "comment": "Triangle (X)", + "size": [46, 46], + "pos": [359, 43] + }, + "6": { + "comment": "L1 (L)", + "size": [139, 16], + "pos": [3, 18] + }, + "7": { + "comment": "R1 (R)", + "size": [139, 16], + "pos": [308, 18] + }, + "10": { + "comment": "Select", + "size": [57, 40], + "pos": [157, 106] + }, + "11": { + "comment": "Start", + "size": [57, 40], + "pos": [236, 106] + }, + "A0M": { + "size": [50, 39], + "pos": [3, 88] + }, + "A0P": { + "size": [50, 39], + "pos": [92, 88] + }, + "A1M": { + "size": [39, 50], + "pos": [53, 38] + }, + "A1P": { + "size": [39, 50], + "pos": [53, 127] + } + } + } +} diff --git a/profiles/universal/base.png b/profiles/universal/base.png new file mode 100644 index 0000000..833744d Binary files /dev/null and b/profiles/universal/base.png differ diff --git a/profiles/universal/config.txt b/profiles/universal/config.txt new file mode 100644 index 0000000..bcf8668 --- /dev/null +++ b/profiles/universal/config.txt @@ -0,0 +1,103 @@ +{ + "profile": { + "name": "Universal", + "font_face": "Golden Sun Regular", + "font_size": 28, + "font_color": [255, 255, 255], + "background_image": "base.png", + "background_color": [50, 50, 50], + "size": [320, 128], + "color_key": [0, 0, 0], + "default_color": [153, 50, 204], + "text_pos": [200, 10], + "aps_minimum": 100, + "inputs": { + "2": { + "comment": "Square (Y)", + "size": [46, 46], + "pos": [317, 86] + }, + "0": { + "comment": "Cross (B)", + "size": [46, 46], + "pos": [359, 127] + }, + "1": { + "comment": "Circle (A)", + "size": [46, 46], + "pos": [401, 85] + }, + "3": { + "comment": "Triangle (X)", + "size": [46, 46], + "pos": [359, 43] + }, + "4": { + "comment": "L1 (L)", + "size": [139, 16], + "pos": [3, 18] + }, + "5": { + "comment": "R1 (R)", + "size": [139, 16], + "pos": [308, 18] + }, + "A2P": { + "comment": "L2", + "size": [139, 16], + "pos": [3, 1] + }, + "A2M": { + "comment": "R2", + "size": [139, 16], + "pos": [308, 1] + }, + "6": { + "comment": "Select", + "size": [57, 40], + "pos": [157, 106] + }, + "7": { + "comment": "Start", + "size": [57, 40], + "pos": [236, 106] + }, + "A0M": { + "enabled": false, + "size": [50, 39], + "pos": [3, 88] + }, + "A0P": { + "enabled": false, + "size": [50, 39], + "pos": [92, 88] + }, + "A1M": { + "enabled": false, + "size": [39, 50], + "pos": [53, 38] + }, + "A1P": { + "enabled": false, + "size": [39, 50], + "pos": [53, 127] + }, + "H0L": { + "size": [50, 39], + "pos": [3, 88] + }, + "H0R": { + "size": [50, 39], + "pos": [92, 88] + }, + "H0U": { + "size": [39, 50], + "pos": [53, 38] + }, + "H0D": { + "size": [39, 50], + "pos": [53, 127] + } + } + } +}