def reapply_character_mappings(cmd, config, player_config): global FORWARDING_DIRECTION global SELECTED_CHARACTER if SELECTED_CHARACTER is None: Debug.println("FAIL", "You must select a character first") return if FORWARDING_DIRECTION is None: FORWARDING_DIRECTION = player_config.get('default-character-forwarding') # Get all the config game_mappings = config.get("Mappings") player_mappings = player_config.get("Mappings") character_mappings = game_mappings.get(SELECTED_CHARACTER) Debug.println("NOTICE", "Applying forwarding direction: %s" % FORWARDING_DIRECTION) forwarded_character_mappings = { n: PianetteCmd.unpack_console_args_string(c, FORWARDING_DIRECTION) for n, c in character_mappings.items() } # Merge the three dictionaries of keys full_mappings = dict(game_mappings, **player_mappings) full_mappings.update(forwarded_character_mappings) # Re-init the mappings following the character change cmd.pianette.init_mappings(full_mappings)
def do_game(self, args): 'The `game` namespace contains all game-defined commands.' try: method = args[0] except IndexError: Debug.println("WARNING", "You must specify a command") return config = self.pianette.get_selected_game_config() player_config = self.pianette.get_selected_player_config() # Is there a command defined in the configuration for this method ? commands = player_config.get("Commands").get(method) if commands is not None: for command in commands.split("\n"): if command.strip(): self.onecmd(command) return # In this case, let's ask the game module module = self.pianette.get_selected_game_module() game = self.pianette.get_selected_game() try: # Call the relevant game method from the loaded module getattr(module, method)(args[1:], cmd=self, config=config, player_config=player_config) except AttributeError: # Method does not exist, gracefully fail Debug.println( "WARNING", "Command %s (%s) does not exist for the game '%s'" % (method, args[1:], game))
def poll(self): channel_inputs = {} channels_with_events = [] for channel in self.last_polled_gpio_inputs.keys(): polled_gpio_input = RPi.GPIO.input(channel) channel_inputs[channel] = polled_gpio_input if self.last_polled_gpio_inputs[channel] is not None: matching_polling_event = GPIOConfigUtil.get_matching_polling_event(self.last_polled_gpio_inputs[channel], polled_gpio_input) if (matching_polling_event is not None and channel in self.polling_event_callbacks and matching_polling_event in self.polling_event_callbacks[channel] ): channels_with_events.append(channel) Debug.println('INFO', 'Invoking polling event callback for channel %d, input %d, event %s' % (channel, polled_gpio_input, matching_polling_event)) self.polling_event_callbacks[channel][matching_polling_event](channel) self.last_polled_gpio_inputs[channel] = polled_gpio_input if (channel not in channels_with_events and channel in self.polling_status_callbacks and polled_gpio_input in self.polling_status_callbacks[channel] ): Debug.println('INFO', 'Invoking polling callback for channel %d, input %d' % (channel, polled_gpio_input)) self.polling_status_callbacks[channel][polled_gpio_input](channel)
def poll(self): channel_inputs = {} channels_with_events = [] for channel in self.last_polled_gpio_inputs.keys(): polled_gpio_input = RPi.GPIO.input(channel) channel_inputs[channel] = polled_gpio_input if self.last_polled_gpio_inputs[channel] is not None: matching_polling_event = GPIOConfigUtil.get_matching_polling_event( self.last_polled_gpio_inputs[channel], polled_gpio_input) if (matching_polling_event is not None and channel in self.polling_event_callbacks and matching_polling_event in self.polling_event_callbacks[channel]): channels_with_events.append(channel) Debug.println( 'INFO', 'Invoking polling event callback for channel %d, input %d, event %s' % (channel, polled_gpio_input, matching_polling_event)) self.polling_event_callbacks[channel][ matching_polling_event](channel) self.last_polled_gpio_inputs[channel] = polled_gpio_input if (channel not in channels_with_events and channel in self.polling_status_callbacks and polled_gpio_input in self.polling_status_callbacks[channel]): Debug.println( 'INFO', 'Invoking polling callback for channel %d, input %d' % (channel, polled_gpio_input)) self.polling_status_callbacks[channel][polled_gpio_input]( channel)
def disable_source(self, source): Debug.println("INFO", "Disabling Source '%s'" % (source)) if source in self.sources: self.sources[source]['enabled'] = False else: raise PianetteConfigError("Source '%s' is not loaded yet" % (source))
def do_game(self, args): 'The `game` namespace contains all game-defined commands.' try: method = args[0] except IndexError: Debug.println("WARNING", "You must specify a command") return config = self.pianette.get_selected_game_config() player_config = self.pianette.get_selected_player_config() # Is there a command defined in the configuration for this method ? commands = player_config.get("Commands").get(method) if commands is not None: for command in commands.split("\n"): if command.strip(): self.onecmd(command) return # In this case, let's ask the game module module = self.pianette.get_selected_game_module() game = self.pianette.get_selected_game() try: # Call the relevant game method from the loaded module getattr(module, method)(args[1:], cmd=self, config=config, player_config=player_config) except AttributeError: # Method does not exist, gracefully fail Debug.println("WARNING", "Command %s (%s) does not exist for the game '%s'" % (method, args[1:], game))
def inputcmds(self, commands, source=None): if source is not None and self.is_source_enabled(source): Debug.println("INFO", "Running commands from source '%s'" % (source)) for command in commands.split("\n"): if command.strip(): self.cmd.onecmd(command) else: Debug.println("WARNING", "Ignoring commands from source '%s'" % (source))
def cmdloop(self): try: cmd.Cmd.cmdloop(self) except KeyboardInterrupt as e: print() Debug.println("NOTICE", "Exiting, bye bye!") self.pianette.stop_timer() return True
def enable_source(self, source): Debug.println("INFO", "Enabling Source '%s'" % (source)) if source in self.sources and self.sources[source][ 'instance'] is not None: self.sources[source]['enabled'] = True else: raise PianetteConfigError("Source '%s' is not loaded yet" % (source))
def select_fighting_handicap(*args, **kwargs): cmd = kwargs['cmd'] try: handicap = args[0][0] except IndexError: Debug.println("WARNING", "You must define a handicap (between ▶ and ▶▶▶▶▶▶▶▶)") return cmd.onecmd("console.play ← ; ← ; ← ; ← ; ← ; ← ; ← ; ← ; " + ((max(1, min(8, len(handicap))) - 1) * "→ ; ") + "✕")
def get_all_configobj(): configobj = ConfigObj() for root, dirs, files in os.walk(PIANETTE_CONFIG_PATH): for file in files: if file.endswith(".ini"): Debug.println("INFO", "Reading configuration file %s/%s ..." % (os.path.basename(root), file)) configobj.merge(ConfigObj(os.path.join(root, file))) return configobj
def unload_source(self, source): Debug.println("INFO", "Unloading Source '%s'" % (source)) # Gives a chance for the source to disable itself if source in self.sources and 'instance' in self.sources[source]: try: self.sources[source]['instance'].disable() except AttributeError: pass self.sources[source] = { 'enabled': False, }
def __init_pedal_states(self): try: supported_pedals = self.configobj['Piano']['supported-pedals'] except KeyError: raise PianetteConfigError('Piano config must define supported-pedals') self.pedal_states = {} for pedal in supported_pedals: self.__set_pedal_state(pedal, False) Debug.println('SUCCESS', 'Piano pedal states initialized')
def __init_note_states(self): try: supported_notes = self.configobj['Piano']['supported-notes'] except KeyError: raise PianetteConfigError('Piano config must define supported-notes') self.note_states = {} for note in supported_notes: self.__set_note_state(note, False) Debug.println('SUCCESS', 'Piano note states initialized')
def get_all_configobj(): configobj = ConfigObj() for root, dirs, files in os.walk(PIANETTE_CONFIG_PATH): for file in files: if file.endswith(".ini"): Debug.println( "INFO", "Reading configuration file %s/%s ..." % (os.path.basename(root), file)) configobj.merge(ConfigObj(os.path.join(root, file))) return configobj
def select_mode(*args, **kwargs): cmd = kwargs['cmd'] try: mode = " ".join(args[0]) except IndexError: Debug.println("WARNING", "You must define a mode (Versus or Arcade)") return if mode == "Versus": cmd.onecmd("console.play → ✕") else: cmd.onecmd("console.play ✕")
def __init__(self, configobj=None): self.__init_using_configobj(configobj=configobj) self.piano = Piano(configobj=self.configobj) self.psx_controller_state = ControllerState(configobj=self.configobj) self.sources = {} self.selected_game = None # Instantiate the console controller that is responsible for sendint out the psx constroller state to the console self.console_controller = ConsoleController(self.psx_controller_state, configobj=self.configobj) # Upcoming state cycles for the Piano Notes (input) self.piano_buffered_note_states = { note: [] for note in self.piano.get_supported_notes() } # Upcoming state cycles for the Piano Pedals (input) self.piano_buffered_pedal_states = { pedal: [] for pedal in self.piano.get_supported_pedals() } # Upcoming state cycles for the Console Controls (output) self.psx_controller_buffered_states = {} try: self.psx_controller_buffered_states = { k: [] for k in self.configobj['Console']['supported-controls'] } except KeyError: pass # Run the Pianette! self._timer = None self._timer_interval = PIANETTE_CYCLE_PERIOD self._timer_is_running = False # Start the timer thread that will cycle buffered states at each interval self.start_timer() Debug.println( "INFO", "Pianette buffered states timer thread started at %f secs interval" % self._timer_interval) # Create pianette command interface self.cmd = PianetteCmd(configobj=configobj, pianette=self)
def __init__(self, configobj=None, pianette=None, **kwargs): super().__init__(**kwargs) app.pianette = pianette app.configs = configobj.get("Game").keys() app.hosts = configobj.get("Pianette").get('Hosts') app.port = configobj.get("Pianette").get('API').get('port') for player, ip in app.hosts.iteritems(): Debug.println("NOTICE", "Adding Player '%s' at %s" % (player, ip)) Debug.println("INFO", "Starting API thread on port %s" % app.port) self.t = Thread(target=self.startApi) self.t.daemon = True self.t.start()
def select_mode(*args, **kwargs): cmd = kwargs['cmd'] try: mode = " ".join(args[0]) except IndexError: Debug.println("WARNING", "You must define a mode (Multiplayer Race, Single Race or Team Race)") return if mode == "Multiplayer Race": cmd.onecmd("console.play ↓ ; ↓ ; ✕ ; ; ; ; ; ; ; ✕ ; ; ; ; ; ; ; ✕ ; ; ; ; ; ; ; ✕ ; ; ; ; ; ; ;") elif mode == "Single Race": cmd.onecmd("console.play ↓ ; ✕ ; ; ; ; ; ; ; ✕ ; ; ; ; ; ; ; ✕ ; ; ; ; ; ; ;") elif mode == "Team Race": cmd.onecmd("console.play ↓ ; ✕ ; ; ; ; ; ; ; ↓ ; ✕ ; ; ; ; ; ; ; ✕ ; ; ; ; ; ; ;") else: cmd.onecmd("console.play ↓ ; ↓ ; ✕ ; ; ; ; ; ; ; ✕ ; ; ; ; ; ; ; ✕ ; ; ; ; ; ; ; ✕ ; ; ; ; ; ; ;") # default to multiplayer
def select_fighting_style(*args, **kwargs): cmd = kwargs['cmd'] try: style = " ".join(args[0]) except IndexError: Debug.println("WARNING", "You must define a style (A-ISM, X-ISM or V-ISM)") return if style == "V-ISM": cmd.onecmd("console.play ↓") elif style == "X-ISM": cmd.onecmd("console.play ↑") elif style == "A-ISM": pass else: Debug.println("WARNING", "%s is not an available fighting style" % style) return cmd.onecmd("console.play ✕")
def select_character(*args, **kwargs): global SELECTED_CHARACTER global FORWARDING_DIRECTION cmd = kwargs['cmd'] config = kwargs['config'] player_config = kwargs['player_config'] try: character = " ".join(args[0]) except IndexError: Debug.println("WARNING", "You must define a character or pass {random}") return if (character == "{random}"): # Select a random character in supported-characters character = random.choice(config.get('supported-characters')) elif not character in config.get('supported-characters'): Debug.println("WARNING", "This character is not supported") return Debug.println("NOTICE", "Choosing character %s" % character) # Process list of commands to obtain this character cmd.onecmd("console.play %s ✕" % player_config.get("Positions").get(character)) SELECTED_CHARACTER = character # When selecting a character, we revert to its default forwarding direction FORWARDING_DIRECTION = player_config.get('default-character-forwarding') reapply_character_mappings(cmd, config, player_config)
def select_game(self, game=None): if game is None: return self.unselect_game() Debug.println("INFO", "Selecting Game '%s'" % (game)) module_name = "config.games.%s.game" % game # Let's import the game module, if not previously done if module_name not in sys.modules: try: self.selected_game_module = importlib.import_module(module_name) except ImportError: Debug.println("FAIL", "Game '%s' doesn't have a module in the games folder" % game) return else: self.selected_game_module = sys.modules[module_name] self.selected_game = game # We have to re-init with the game's mappings # instead of the general mappings : if not self.configobj.get("Game").get(game): raise PianetteConfigError("Undefined Game '%s' section in configobj" % game) if not self.selected_player: raise PianetteConfigError("You must select a player first") self.selected_game_config = self.configobj.get("Game").get(game) self.selected_player_config = self.selected_game_config.get("Player %s" % self.selected_player) # Retrieve the mappings game_mappings = self.selected_game_config.get("Mappings") player_mappings = self.selected_player_config.get("Mappings") # Merge the two dictionaries of keys if player_mappings is not None: full_mappings = dict(game_mappings, **player_mappings); else: full_mappings = game_mappings # Finally, override PIANETTE_CYCLE_PERIOD global PIANETTE_CYCLE_PERIOD if self.selected_game_config.get('cycle-period') is not None: PIANETTE_CYCLE_PERIOD = float(self.selected_game_config.get('cycle-period')) Debug.println("NOTICE", "The selected game overrides the cycle period : %f seconds" % PIANETTE_CYCLE_PERIOD) else: PIANETTE_CYCLE_PERIOD = float(self.configobj.get("Console").get('default-cycle-period')) Debug.println("NOTICE", "Resetting the default cycle period : %f seconds" % PIANETTE_CYCLE_PERIOD) # Re-init the mappings self.init_mappings(full_mappings)
def __init__(self, configobj=None): self.__init_using_configobj(configobj=configobj) self.piano = Piano(configobj=self.configobj) self.psx_controller_state = ControllerState(configobj=self.configobj) self.sources = {} self.selected_game = None # Instantiate the console controller that is responsible for sendint out the psx constroller state to the console self.console_controller = ConsoleController(self.psx_controller_state, configobj=self.configobj) # Upcoming state cycles for the Piano Notes (input) self.piano_buffered_note_states = { note: [] for note in self.piano.get_supported_notes() } # Upcoming state cycles for the Piano Pedals (input) self.piano_buffered_pedal_states = { pedal: [] for pedal in self.piano.get_supported_pedals() } # Upcoming state cycles for the Console Controls (output) self.psx_controller_buffered_states = {} try: self.psx_controller_buffered_states = { k: [] for k in self.configobj['Console']['supported-controls'] } except KeyError: pass # Run the Pianette! self._timer = None self._timer_interval = PIANETTE_CYCLE_PERIOD self._timer_is_running = False # Start the timer thread that will cycle buffered states at each interval self.start_timer() Debug.println("INFO", "Pianette buffered states timer thread started at %f secs interval" % self._timer_interval) # Create pianette command interface self.cmd = PianetteCmd(configobj=configobj, pianette=self)
def do_time__sleep(self, args): 'Block the exeuction for a certain amount of Pianette cycles' Debug.println("INFO", "running command: time.sleep" + " " + args) args_list = args.split() sleep_time = float(args_list[0]) * self.pianette.get_cycle_period() if args_list: Debug.println("NOTICE", 'sleeping for {:.3f} seconds'.format(sleep_time)) time.sleep(sleep_time) Debug.println("NOTICE", 'done sleeping for {:.3f} seconds'.format(sleep_time)) else: raise pianette.errors.PianetteCmdError("No argument provided for time.sleep")
def do_time__sleep(self, args): 'Block the exeuction for a certain amount of Pianette cycles' Debug.println("INFO", "running command: time.sleep" + " " + args) args_list = args.split() sleep_time = float(args_list[0]) * self.pianette.get_cycle_period() if args_list: Debug.println("NOTICE", 'sleeping for {:.3f} seconds'.format(sleep_time)) time.sleep(sleep_time) Debug.println( "NOTICE", 'done sleeping for {:.3f} seconds'.format(sleep_time)) else: raise pianette.errors.PianetteCmdError( "No argument provided for time.sleep")
def __init__(self, psx_controller_state, configobj=None): self.configobj = configobj self.psx_controller_state = psx_controller_state # Tries to find correct Serial port open_ports = self.getSerialPorts() # Opens first port available try: self.serialConnection = serial.Serial(open_ports[0], 38400) Debug.println("INFO", "SPI Slave detected at %s, waiting for the port to initialize." % open_ports[0]) time.sleep(3) # DIRTY but apparently required for the serial port to get fully ready Debug.println("SUCCESS", "SPI Slave initialized.") except Exception: self.serialConnection = None Debug.println("WARNING", "No ConsoleController SPI Slave detected.")
def load_source(self, source): if source in self.sources and 'instance' in self.sources[source]: Debug.println("WARNING", "Source '%s' is already enabled" % (source)) return try: source_module = importlib.import_module('pianette.sources.' + source) except ImportError: Debug.println("FAIL", "Unsupported source '%s'" % (source)) return source_class = getattr(source_module, source) instance = source_class(configobj=self.configobj, pianette=self) Debug.println("INFO", "Loading Source '%s'" % (source)) self.sources[source] = { 'enabled': True, 'instance': instance, }
def select_stage(*args, **kwargs): cmd = kwargs['cmd'] config = kwargs['config'] try: stage = " ".join(args[0]) except IndexError: Debug.println("WARNING", "You must define a stage") return if (stage == "{random}"): # Select a random stage in supported-stages stage = random.choice(config.get('supported-stages')) elif not stage in config.get('supported-stages'): Debug.println("WARNING", "This stage is not supported") return Debug.println("NOTICE", "Choosing stage '%s'" % stage) cmd.onecmd("console.play %s ; ✕" % config.get("Stages").get(stage))
def select_track(*args, **kwargs): cmd = kwargs['cmd'] config = kwargs['config'] try: track = " ".join(args[0]) except IndexError: Debug.println("WARNING", "You must define a track") return if (track == "{random}"): # Select a random track in supported-tracks track = random.choice(config.get('supported-tracks')) elif not track in config.get('supported-tracks'): Debug.println("WARNING", "This track '%s' is not supported" % track) return Debug.println("NOTICE", "Choosing track '%s'" % track) cmd.onecmd("console.play ; ; ; %s ; ; ; ; ; ; ; ; ; ; ✕ " % config.get("Tracks").get(track))
def __init__(self, psx_controller_state, configobj=None): self.configobj = configobj self.psx_controller_state = psx_controller_state # Tries to find correct Serial port open_ports = self.getSerialPorts() # Opens first port available try: self.serialConnection = serial.Serial(open_ports[0], 38400) Debug.println( "INFO", "SPI Slave detected at %s, waiting for the port to initialize." % open_ports[0]) time.sleep( 3 ) # DIRTY but apparently required for the serial port to get fully ready Debug.println("SUCCESS", "SPI Slave initialized.") except Exception: self.serialConnection = None Debug.println("WARNING", "No ConsoleController SPI Slave detected.")
def select_character(*args, **kwargs): cmd = kwargs['cmd'] config = kwargs['config'] player_config = kwargs['player_config'] try: character = " ".join(args[0]) except IndexError: Debug.println("WARNING", "You must define a character or pass {random}") return if (character == "{random}"): # Select a random character in supported-characters character = random.choice(config.get('supported-characters')) elif not character in config.get('supported-characters'): Debug.println("WARNING", "This character '%s' is not supported" % character) return # Choosing a deterministic character will only work for the first player to choose Debug.println("NOTICE", "_Probably_ choosing character %s" % character) # Process list of commands to obtain this character cmd.onecmd("console.play START ; ; ; ; ; %s ; ; ; ; ; ✕" % config.get("Positions").get(character))
def do_pianette__dump_state(self, args): 'Dump a full state of the Pianette configuration' Debug.println("INFO", "running command: pianette.dump_state") # Dump general info on the pianette instance Debug.println("NOTICE", "Enabled sources: %s" % self.pianette.get_selected_player()) # Dump general game configuration Debug.println("NOTICE", "Currently selected game: '%s'" % self.pianette.get_selected_game()) Debug.println("NOTICE", "Current game config:") print(json.dumps(self.pianette.get_selected_game_config(), sort_keys=True, indent=4)) # Dump player specific configuration for this game Debug.println("NOTICE", "Currently selected player: %s" % self.pianette.get_selected_player()) Debug.println("NOTICE", "Current player config:") print(json.dumps(self.pianette.get_selected_player_config(), sort_keys=True, indent=4)) # Dumps the buffered mappings Debug.println("NOTICE", "Current buffered mappings:") print(json.dumps(self.pianette.get_buffered_states_mappings(), sort_keys=True, indent=4))
def do_pianette__enable_source(self, args): 'Enable an input source' Debug.println("INFO", "running command: pianette.enable_source" + " " + args) self.pianette.enable_source(args)
def do_pianette__select_game(self, args): 'Select a game and load its configuration' Debug.println("INFO", "running command: pianette.select_game" + " " + args) self.pianette.select_game(args)
def select_player(self, player=None): Debug.println("INFO", "Selecting Player %s" % (player)) self.selected_player = player
def do_pianette__disable_source(self, args): 'Disable a previously enabled source' Debug.println("INFO", "running command: pianette.disable_source" + " " + args) self.pianette.disable_source(args)
def do_piano__release(self, args): 'Release a sequence of notes, chords and pedals' Debug.println("INFO", "running command: piano.release" + " " + args) self.pianette.release_piano_pedals(args)
def do_piano__play(self, args): 'Play a sequence of notes, chords and pedals' Debug.println("INFO", "running command: piano.play" + " " + args) self.pianette.push_piano_notes(args)
# A command-line emulator of a PS2 Game Pad Controller # that asynchronously listens to GPIO EDGE_RISING # inputs from sensors and sends Serial commands to # an ATMEGA328P acting as a fake SPI Slave for the Console. # Written in Python 3. import importlib import pianette.config import sys from pianette.Pianette import Pianette from pianette.PianetteArgumentParser import PianetteArgumentParser from pianette.utils import Debug Debug.println("INFO", " ") Debug.println("INFO", " ################################## ") Debug.println("INFO", " | PIANETTE | ") Debug.println("INFO", " ################################## ") Debug.println("INFO", " ") configobj = pianette.config.get_all_configobj() parser = PianetteArgumentParser(configobj=configobj) args = parser.parse_args() # Instanciate the global Pianette # Its responsibility is to translate Piano actions to Console actions pianette = Pianette(configobj=configobj) # We MUST select a player before we select a game.
def select_game(self, game=None): if game is None: return self.unselect_game() Debug.println("INFO", "Selecting Game '%s'" % (game)) module_name = "config.games.%s.game" % game # Let's import the game module, if not previously done if module_name not in sys.modules: try: self.selected_game_module = importlib.import_module( module_name) except ImportError: Debug.println( "FAIL", "Game '%s' doesn't have a module in the games folder" % game) return else: self.selected_game_module = sys.modules[module_name] self.selected_game = game # We have to re-init with the game's mappings # instead of the general mappings : if not self.configobj.get("Game").get(game): raise PianetteConfigError( "Undefined Game '%s' section in configobj" % game) if not self.selected_player: raise PianetteConfigError("You must select a player first") self.selected_game_config = self.configobj.get("Game").get(game) self.selected_player_config = self.selected_game_config.get( "Player %s" % self.selected_player) # Retrieve the mappings game_mappings = self.selected_game_config.get("Mappings") player_mappings = self.selected_player_config.get("Mappings") # Merge the two dictionaries of keys if player_mappings is not None: full_mappings = dict(game_mappings, **player_mappings) else: full_mappings = game_mappings # Finally, override PIANETTE_CYCLE_PERIOD global PIANETTE_CYCLE_PERIOD if self.selected_game_config.get('cycle-period') is not None: PIANETTE_CYCLE_PERIOD = float( self.selected_game_config.get('cycle-period')) Debug.println( "NOTICE", "The selected game overrides the cycle period : %f seconds" % PIANETTE_CYCLE_PERIOD) else: PIANETTE_CYCLE_PERIOD = float( self.configobj.get("Console").get('default-cycle-period')) Debug.println( "NOTICE", "Resetting the default cycle period : %f seconds" % PIANETTE_CYCLE_PERIOD) # Re-init the mappings self.init_mappings(full_mappings)
def cycle_buffered_states(self): self.poll_enabled_sources() # Input Piano Notes to Piano Buffered States for piano_note in self.piano.get_supported_notes(): if self.piano.is_note_on(piano_note): Debug.println("INFO", "Buffering Piano Note %s" % (piano_note)) self.piano_buffered_note_states[piano_note].append( {"cycles_remaining": PIANETTE_PROCESSING_CYCLES}) self.piano.switch_note_off(piano_note) # Input Piano Pedals to Piano Buffered States for piano_pedal in self.piano.get_supported_pedals(): if self.piano.is_pedal_on(piano_pedal): if not self.piano_buffered_pedal_states[piano_pedal]: Debug.println( "INFO", "Indefinitely buffering Piano Pedal %s" % (piano_pedal)) self.piano_buffered_pedal_states[piano_pedal] = [{ "cycles_remaining": "∞" }] else: if self.piano_buffered_pedal_states[piano_pedal]: Debug.println( "INFO", "Indefinitely unbuffering Piano Pedal %s" % (piano_pedal)) self.piano_buffered_pedal_states[piano_pedal] = [] # Process Buffered States: Determine piano note or chord # Notes that have reached their last cycle "lead" the chord determination # Other notes may be used to "complement" lead notes. # If a winning chord is found, push corresponding combo to PSX Controller buffer # If complementary notes are actually used, they are discarded from buffer. lead_notes = [] complementary_notes = [] for piano_note in self.piano_buffered_note_states.keys(): processed_buffered_states = [] for buffered_state in self.piano_buffered_note_states[piano_note]: if buffered_state["cycles_remaining"] == 0: lead_notes.append(piano_note) else: complementary_notes.append(piano_note) buffered_state["cycles_remaining"] -= 1 processed_buffered_states.append(buffered_state) self.piano_buffered_note_states[ piano_note] = processed_buffered_states if lead_notes: Debug.println( "INFO", "Processing Piano Notes: lead=%s, complementary=%s" % (lead_notes, complementary_notes)) ranked_chord_bitids = self.get_ranked_chord_bitids_including_at_least_one_of_notes( lead_notes) all_notes_chord_bitid = self.get_notes_chord_bitid( lead_notes + complementary_notes) ranked_winning_chord_bitids = [ chord_bitid for chord_bitid in ranked_chord_bitids if not ((all_notes_chord_bitid & chord_bitid) ^ chord_bitid) ] if ranked_winning_chord_bitids: winning_chord_bitid = ranked_winning_chord_bitids[0] winning_states_mapping = deepcopy( self._buffered_states_mapping_for_chord_bitid[ winning_chord_bitid]) # Push piano command to PSX Controller buffer, clearing any pending combo for control in self.psx_controller_buffered_states.keys(): self.psx_controller_buffered_states[ control] = winning_states_mapping[ "psx_controller"].get(control, []) # Clear winning chord notes from the piano buffer for piano_note in self.piano_buffered_note_states.keys(): if piano_note in self._note_bitids: if self._note_bitids[piano_note] & winning_chord_bitid: if self.piano_buffered_note_states[piano_note]: self.piano_buffered_note_states[ piano_note] = self.piano_buffered_note_states[ piano_note][1:] # Pedals buffer their own PSX controls in parallel of chord resolution for piano_pedal in self.piano_buffered_pedal_states.keys(): for buffered_state in self.piano_buffered_pedal_states[ piano_pedal]: if buffered_state["cycles_remaining"] == "∞": if piano_pedal in self._pedal_bitids: # Unshift pedal command to PSX Controller buffer, merging with any pending combo psx_controller_states_for_pedal = self._buffered_states_mapping_for_pedal_bitid[ self._pedal_bitids[piano_pedal]]["psx_controller"] for control, single_cycles_count in psx_controller_states_for_pedal.items( ): cycles_count = single_cycles_count[0] if not self.psx_controller_buffered_states[control]: self.psx_controller_buffered_states[ control] = [cycles_count] elif self.psx_controller_buffered_states[control][ 0] < cycles_count: self.psx_controller_buffered_states[control][ 0] = cycles_count # Output PSX Controller Buffered states to PSX Controller triggered_controls = "" cleared_controls = "" for psx_control, buffered_state in self.psx_controller_buffered_states.items( ): if buffered_state: cyclesCount = buffered_state.pop(0) if cyclesCount > 0: triggered_controls += "(%s, %d) " % (psx_control, cyclesCount) self.psx_controller_state.raiseFlag(psx_control) cyclesCount -= 1 # If we're decrementing a cycle count, we don't # want to insert 0 to avoid 'missing a step'. if cyclesCount > 0: buffered_state.insert(0, cyclesCount) elif cyclesCount < 0: cleared_controls += "(%s, %d) " % (psx_control, -cyclesCount) self.psx_controller_state.clearFlag(psx_control) cyclesCount += 1 # If we're incrementing a cycle count, we don't # want to insert 0 to avoid 'missing a step'. if cyclesCount < 0: buffered_state.insert(0, cyclesCount) else: cleared_controls += "(%s, %d) " % (psx_control, 1) self.psx_controller_state.clearFlag(psx_control) else: self.psx_controller_state.clearFlag(psx_control) if len(triggered_controls) > 0: Debug.println( "INFO", "Keeping PSX Controls %sTriggered" % triggered_controls) if len(cleared_controls) > 0 and len(triggered_controls) == 0: Debug.println("INFO", "General pause for 1 cycle") elif len(cleared_controls) > 0: Debug.println("DEBUG", "Keeping PSX Control %sCleared" % cleared_controls) self.console_controller.sendStateBytes()
def do_pianette__dump_state(self, args): 'Dump a full state of the Pianette configuration' Debug.println("INFO", "running command: pianette.dump_state") # Dump general info on the pianette instance Debug.println( "NOTICE", "Enabled sources: %s" % self.pianette.get_selected_player()) # Dump general game configuration Debug.println( "NOTICE", "Currently selected game: '%s'" % self.pianette.get_selected_game()) Debug.println("NOTICE", "Current game config:") print( json.dumps(self.pianette.get_selected_game_config(), sort_keys=True, indent=4)) # Dump player specific configuration for this game Debug.println( "NOTICE", "Currently selected player: %s" % self.pianette.get_selected_player()) Debug.println("NOTICE", "Current player config:") print( json.dumps(self.pianette.get_selected_player_config(), sort_keys=True, indent=4)) # Dumps the buffered mappings Debug.println("NOTICE", "Current buffered mappings:") print( json.dumps(self.pianette.get_buffered_states_mappings(), sort_keys=True, indent=4))
def do_piano__hold(self, args): 'Hold a sequence of notes, chords and pedals' Debug.println("INFO", "running command: piano.hold" + " " + args) self.pianette.hold_piano_pedals(args)
def do_console__play(self, args): 'Play a sequence of buttons in a definite order for a full Pianette cycle' Debug.println("INFO", "running command: console.play" + " " + args) self.pianette.push_console_controls(args)
def unselect_game(self): Debug.println("INFO", "Unselecting Game") self.selected_game_module = None self.selected_game = None self.selected_game_config = None self.selected_player_config = None
def get_buffered_states_for_controls_string(self, controls_string, force_duration_cycles=None): # Initial state and time controls_buffered_states = {} time_index = 0 # Combo loop variables in_combo = False combo_controls = [] previous_control = None # By default, no duration cycle is set to detect malformed controls_string starting with `+` duration_cycles = None for control in controls_string.split(): # '+' should not be at the start of the string, fail gracefully if control == "+" and duration_cycles is None: Debug.println( "FAIL", "Control string is not grammatically correct (starting with a +)" ) return controls_buffered_states elif control == "+": time_index -= duration_cycles in_combo = True elif control == ";" and in_combo == False: # During a combo, a semi-colon (;) is not grammatically correct. # We add a "0" cycle count for every possible state, to be sure # that it will add an offset even for future added controls # that are not yet present in self.controls_buffered_states for c in self.psx_controller_buffered_states: if c in controls_buffered_states: controls_buffered_states[c].extend( [0] * PIANETTE_CONSOLE_PLAY_DURATION_CYCLES) else: controls_buffered_states[c] = [ 0 ] * PIANETTE_CONSOLE_PLAY_DURATION_CYCLES else: if in_combo: combo_controls.append( previous_control) # Next control will be in a combo else: combo_controls = [] control, duration_cycles = self.extract( control, combo_controls, force_duration_cycles) if control in controls_buffered_states: buffer_duration = 0 for duration in controls_buffered_states[control]: buffer_duration += abs(duration) if time_index - buffer_duration > 0: controls_buffered_states[control].append( -time_index + buffer_duration) controls_buffered_states[control].append( duration_cycles) elif not in_combo or time_index == buffer_duration: controls_buffered_states[control].append( duration_cycles) # If we've got an unknown control, it will be ignored and the string will # be parsed as if the control was not there, still modifying the time index # if needed and not breaking combos, leading to generally correct and expected # results. No exception should be raised. elif control in self.psx_controller_buffered_states: controls_buffered_states[control] = [] if time_index > 0: controls_buffered_states[control].append(-time_index) controls_buffered_states[control].append(duration_cycles) time_index += duration_cycles in_combo = False # Always assume that we're the last control in the combo previous_control = control return controls_buffered_states