Skip to content

Commit

Permalink
Player Pianoroll (Rewrite)
Browse files Browse the repository at this point in the history
Rewrote the code to play pianorolls.

Co-Authored-By: Aiden <[email protected]>
  • Loading branch information
ximk and vabold committed Mar 28, 2023
1 parent c223f49 commit 781a2a3
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 90 deletions.
192 changes: 192 additions & 0 deletions scripts/framesequence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
from typing import List, Optional
import csv


class Frame:
"""
A class representing an input combination on a frame.
Attributes:
accel (bool): Whether or not we press 'A' on that frame.
brake (bool): Whether or not we press 'B' on that frame.
item (bool): Whether or not we press 'L' on that frame.
stick_x (int): Horizontal stick input, ranging from -7 to +7.
stick_y (int): Vertical stick input, ranging from -7 to +7.
dpad_up (bool): Whether or not we press 'Up' on that frame.
dpad_down (bool): Whether or not we press 'Down' on that frame.
dpad_left (bool): Whether or not we press 'Left' on that frame.
dpad_right (bool): Whether or not we press 'Right' on that frame.
valid (bool): Whether or not the Frame is valid.
"""
accel: bool
brake: bool
item: bool

stick_x: int
stick_y: int

dpad_up: bool
dpad_down: bool
dpad_left: bool
dpad_right: bool

valid: bool

def __init__(self, raw: List):
"""
Initializes a Frame object given a CSV line.
The structure of the list is as follows:
* raw[0] (str) - A
* raw[1] (str) - B/R
* raw[2] (str) - L
* raw[3] (str) - Horizontal stick
* raw[4] (str) - Vertical stick
* raw[5] (str) - Dpad
Args:
raw (List): CSV line to be read
"""
self.valid = True

self.accel = self.read_button(raw[0])
self.brake = self.read_button(raw[1])
self.item = self.read_button(raw[2])
self.stick_x = self.read_stick(raw[3])
self.stick_y = self.read_stick(raw[4])
self.read_dpad(raw[5])

def read_button(self, button: str) -> bool:
"""
Parses the button input into a boolean. Sets `self.valid` to False if invalid.
"""
try:
val = int(button)
except ValueError:
self.valid = False
return False

if val < 0 or val > 1:
self.valid = False

return bool(val)

def read_stick(self, stick: str) -> int:
"""
Parses the stick input into an int. Sets `self.valid` to False if invalid.
"""
try:
val = int(stick)
except ValueError:
self.valid = False
return 0

if val < -7 or val > 7:
self.valid = False

return val

def read_dpad(self, dpad: str) -> None:
"""
Sets dpad members based on dpad input. Sets `self.valid` to False if invalid.
"""
try:
val = int(dpad)
except ValueError:
self.valid = False
return

if val < 0 or val > 4:
self.valid = False

self.dpad_up = val == 1
self.dpad_down = val == 2
self.dpad_left = val == 3
self.dpad_right = val == 4


class FrameSequence:
"""
A class representing a sequence of inputs, indexed by frames.
Attributes:
frames (list): The sequence of frames.
filename (str): The name of the CSV file initializing the frame sequence.
"""
frames: list
filename: str

# TODO: Make filename optional and allow for non-CSV frame sequences
def __init__(self, filename: str):
self.frames = []
self.filename = filename

self.refresh()

def refresh(self) -> None:
"""
Loads the CSV into a new frame sequence. Ideally called on savestate load.
Args: None
Returns: None
"""
with open(self.filename, 'r') as f:
reader = csv.reader(f)
for row in reader:
frame = self.process(row)
if not frame:
# TODO: Handle error
pass

self.frames.append(frame)

def process(self, raw_frame: List) -> Optional[Frame]:
"""
Processes a raw frame into an instance of the Frame class. Ideally used internally.
Args:
raw_frame (List): Line from the CSV to process.
Returns:
A new Frame object initialized with the raw frame, or None if the frame is invalid.
"""
if len(raw_frame) != 6:
return None

frame = Frame(raw_frame)
if not frame.valid:
return None

return frame

def get_controller_inputs(self, idx: int) -> Optional[dict]:
"""
Gets the controller inputs for a given frame in the sequence. Compatible with Dolphin's "set_gc_buttons" method.
Args:
idx (int): The index for the sequence.
Returns:
The controller input dict for the provided frame, or None if the frame is not in the sequence.
"""
if idx >= len(self.frames):
return None

frame = self.frames[idx]
inputs = dict()

inputs['A'] = frame.accel
inputs['B'] = frame.brake
inputs['L'] = frame.item

raw_stick_inputs = [0, 60, 70, 80, 90, 100,
110, 128, 155, 165, 175, 185, 195, 200, 255]
inputs['StickX'] = raw_stick_inputs[frame.stick_x + 7]
inputs['StickY'] = raw_stick_inputs[frame.stick_y + 7]

inputs['Up'] = frame.dpad_up
inputs['Down'] = frame.dpad_down
inputs['Left'] = frame.dpad_left
inputs['Right'] = frame.dpad_right

return inputs
90 changes: 0 additions & 90 deletions scripts/pianoroll_player.py

This file was deleted.

38 changes: 38 additions & 0 deletions scripts/pianoroll_player_only.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from dolphin import controller, event, gui
import mkw_core as core
import mkw_classes as classes
from framesequence import FrameSequence


@event.on_frameadvance
def on_frame_advance():
race_frame = core.get_frame_of_input()
global last_race_frame, sequence
if 0 < race_frame == last_race_frame:
race_frame += 1
if last_race_frame > race_frame:
sequence.refresh()
inputs = sequence.get_controller_inputs(race_frame)
if inputs and classes.RaceInfo.stage() >= 1: # If there are inputs on this frame, send the inputs
controller.set_gc_buttons(0, inputs)
last_race_frame = race_frame


@event.on_savestateload
def on_savestate_load(is_slot, slot):
global sequence
if (is_slot):
sequence.refresh()
on_frame_advance()


if __name__ == '__main__':
global sequence
# Run on script start
race_frame = core.get_frame_of_input()
last_race_frame = race_frame - 1
# TODO (xi): eventually automate filepath search (would like saving in place
# first before implementing)
pianoroll_path = r'filepath'
sequence = FrameSequence(pianoroll_path)
gui.add_osd_message(f"{pianoroll_path} successfully loaded!")

0 comments on commit 781a2a3

Please sign in to comment.