From 9ff40746d3489b611858f7d334b9d1d5f737134b Mon Sep 17 00:00:00 2001 From: Justin S Date: Sun, 16 Oct 2016 14:59:33 -0400 Subject: [PATCH 01/47] added add_transition command, bug fixes --- scripts/streaming/TASLink.py | 111 +++++++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 6 deletions(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 78d6894..4ee35cd 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -5,12 +5,14 @@ import cmd import threading import yaml -#import math -import time +import gc +# import math +# import time import rlcompleter, readline # to add support for tab completion of commands import glob +gc.disable() # for performance reasons def complete(text, state): return (glob.glob(text + '*') + [None])[state] @@ -34,7 +36,7 @@ def complete_nostate(text, *ignored): prebuffer = 60 ser = None -TASLINK_CONNECTED = 0 # set to 0 for development without TASLink plugged in, set to 1 for actual testing +TASLINK_CONNECTED = 1 # set to 0 for development without TASLink plugged in, set to 1 for actual testing consolePorts = [2, 0, 0, 0, 0] # 1 when in use, 0 when available. 2 is used to waste cell 0 consoleLanes = [2, 0, 0, 0, 0, 0, 0, 0, 0] # 1 when in use, 0 when available. 2 is used to waste cell 0 @@ -44,7 +46,7 @@ def complete_nostate(text, *ignored): tasRuns = [] inputBuffers = [] customCommands = [] -isRunModified = [] # TODO: finish implementing this +isRunModified = [] frameCounts = [0, 0, 0, 0] selected_run = -1 @@ -128,6 +130,10 @@ def send_frames(index, amount): frameCounts[index] += amount +class Transition(object): + frameno = None + window = None + dpcmFix = None class TASRun(object): def __init__(self, num_controllers, ports_list, controller_type, controller_bits, ovr, wndw, file_name, dummy_frames, dpcm_fix): @@ -140,6 +146,7 @@ def __init__(self, num_controllers, ports_list, controller_type, controller_bits self.inputFile = file_name self.dummyFrames = dummy_frames self.dpcmFix = dpcm_fix + self.transitions = [] self.fileExtension = file_name.split(".")[-1].strip() # pythonic last element of a list/string/array @@ -150,6 +157,12 @@ def __init__(self, num_controllers, ports_list, controller_type, controller_bits else: self.maxControllers = 1 # random default, but truly we need to support other formats + def addTransition(self, t): + self.transitions.append(t) + + def delTransition(self, index): + del self.transitions[index] + def getInputBuffer(self, customCommand): with open(self.inputFile, 'rb') as myfile: wholefile = myfile.read() @@ -241,7 +254,7 @@ def setupCommunication(tasrun): command += str(port) # should look like 'sp1' now portData = tasrun.controllerType if tasrun.dpcmFix: - portData = +128 + portData += 128 # add the flag for the 8th bit if TASLINK_CONNECTED: ser.write(command + chr(portData)) else: @@ -417,7 +430,7 @@ def do_exit(self, data): for index,modified in enumerate(isRunModified): if modified: while True: - save = raw_input("Run #"+str(index+1)+" is not saved. Save (y/n)? ") # JUSTINS + save = raw_input("Run #"+str(index+1)+" is not saved. Save (y/n)? ") if save == 'y': self.do_save(index+1) break @@ -686,6 +699,49 @@ def do_select(self, data): print("ERROR: Invalid run number!") selected_run = runID - 1 + def do_add_transition(self, data): + """Adds a transition of communication settings at a particular frame""" + if selected_run == -1: + print("ERROR: No run is selected!\n") + return + # transitions + while True: + try: + frameNum = readint("At what frame will this transition occur? ") + if frameNum <= 0: + print("ERROR: Please enter a positive number!\n") + continue + else: + break + except ValueError: + print("ERROR: Please enter an integer!\n") + # DPCM fix + while True: + dpcm_fix = raw_input("Apply DPCM fix (y/n)? ") + if dpcm_fix.lower() == 'y': + dpcm_fix = True + break + elif dpcm_fix.lower() == 'n': + dpcm_fix = False + break + print("ERROR: Please enter y for yes or n for no!\n") + # window mode 0-15.75ms + while True: + window = readfloat( + "Window value (0 to disable, otherwise enter time in ms. Must be multiple of 0.25ms. Must be between 0 and 15.75ms)? ") + if window < 0 or window > 15.25: + print("ERROR: Window out of range [0, 15.75])!\n") + elif window % 0.25 != 0: + print("ERROR: Window is not a multiple of 0.25!\n") + else: + break + t = Transition() + t.dpcmFix = dpcm_fix + t.frameno = frameNum + t.window = window + tasRuns[selected_run].addTransition(t) + isRunModified[selected_run] = True + def do_new(self, data): """Create a new run with parameters specified in the terminal""" global selected_run @@ -808,6 +864,41 @@ def do_EOF(self, line): def postloop(self): print +def handleTransition(run, transition): + # TODO: fix so the current state is monitored rather than the original state + # TODO: redo all random lists as one big class, which monitor each run's state + if run.dpcmFix != transition.dpcmFix: + for port in run.portsList: + # enable the console ports + command = "sp" + command += str(port) # should look like 'sp1' now + portData = run.controllerType + if transition.dpcmFix: + portData += 128 # add the flag for the 8th bit + ser.write(command + chr(portData)) + print(command, portData) + if run.window != transition.window: + controllers = list('00000000') + for port in run.portsList: + if run.controllerType == CONTROLLER_NORMAL: + limit = 1 + elif run.controllerType == CONTROLLER_MULTITAP: + limit = 4 + else: + limit = 2 + for counter in range(limit): + controllers[8 - lanes[port][counter]] = '1' # this is used later for the custom stream command + controllerMask = "".join(controllers) # convert binary to string + + # setup events #s e lane_num byte controllerMask + command = 'se' + str(min(run.portsList)) + # do first byte + byte = list('{0:08b}'.format( + int(transition.window / 0.25))) # create padded bytestring, convert to list for manipulation + byte[0] = '1' # enable flag + bytestring = "".join(byte) # turn back into string + ser.write(command + chr(int(bytestring, 2)) + chr(int(controllerMask, 2))) + # ----- MAIN EXECUTION BEGINS HERE ----- if len(sys.argv) < 2: @@ -862,8 +953,16 @@ def postloop(self): c = ser.read(numBytes) latchCounts = [-1, c.count('f'), c.count('g'), c.count('h'), c.count('i')] + if numBytes > 60: + print ("WARNING: High frame read detected: " + str(numBytes)) + for run_index, run in enumerate(tasRuns): port = min(run.portsList) # the same port we have an event listener on latches = latchCounts[port] + + for transition in run.transitions: + if 0 <= (transition.frameno+run.dummyFrames+prebuffer) - frameCounts[run_index] < latches: # we're about to pass the transition frame + handleTransition(run,transition) + if latches > 0: send_frames(run_index, latches) From 51ce22a8781a648e456e3d073433cd692838115f Mon Sep 17 00:00:00 2001 From: Justin S Date: Sun, 16 Oct 2016 19:10:53 -0400 Subject: [PATCH 02/47] finished implementing add_transition --- scripts/streaming/TASLink.py | 45 +++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 4ee35cd..37b5184 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -1,3 +1,5 @@ +# TODO: redo all random lists as one big class, which monitor each run's state + import os import serial from serial import SerialException @@ -47,6 +49,8 @@ def complete_nostate(text, *ignored): inputBuffers = [] customCommands = [] isRunModified = [] +dpcmState = [] +windowState = [] frameCounts = [0, 0, 0, 0] selected_run = -1 @@ -107,6 +111,8 @@ def load(filename): setupCommunication(run) tasRuns.append(run) isRunModified.append(False) + dpcmState.append(run.dpcmFix) + windowState.append(run.window) selected_run = len(tasRuns) - 1 @@ -547,6 +553,11 @@ def do_reset(self, data): frameCounts = [0, 0, 0, 0] for index in range(len(tasRuns)): send_frames(index, prebuffer) # re-pre-buffer-! + # return runs to their original state + t = Transition() + t.dpcmFix = tasRuns[index].dpcmFix + t.window = tasRuns[index].window + handleTransition(index,t) print("Reset command given to all runs!") return False elif data != "": @@ -587,6 +598,11 @@ def do_reset(self, data): frameCounts[index] = 0 send_frames(index, prebuffer) # re-pre-buffer-! + # return run to its original state + t = Transition() + t.dpcmFix = tasRuns[index].dpcmFix + t.window = tasRuns[index].window + handleTransition(index, t) print("Reset complete!") def do_remove(self, data): @@ -633,6 +649,8 @@ def do_remove(self, data): del tasRuns[index] del customCommands[index] del isRunModified[index] + del dpcmState[index] + del windowState[index] # reset frame counts, move them accordingly for i in range(index, len(frameCounts) - 1): # one less than the hardcoded max of array @@ -850,6 +868,8 @@ def do_new(self, data): setupCommunication(tasrun) tasRuns.append(tasrun) isRunModified.append(True) + dpcmState.append(dpcm_fix) + windowState.append(window) selected_run = len(tasRuns) - 1 @@ -864,25 +884,23 @@ def do_EOF(self, line): def postloop(self): print -def handleTransition(run, transition): - # TODO: fix so the current state is monitored rather than the original state - # TODO: redo all random lists as one big class, which monitor each run's state - if run.dpcmFix != transition.dpcmFix: - for port in run.portsList: +def handleTransition(run_index, transition): + if dpcmState[run_index] != transition.dpcmFix: + for port in tasRuns[run_index].portsList: # enable the console ports command = "sp" command += str(port) # should look like 'sp1' now - portData = run.controllerType + portData = tasRuns[run_index].controllerType if transition.dpcmFix: portData += 128 # add the flag for the 8th bit ser.write(command + chr(portData)) - print(command, portData) - if run.window != transition.window: + dpcmState[run_index] = transition.dpcmFix + if windowState[run_index] != transition.window: controllers = list('00000000') - for port in run.portsList: - if run.controllerType == CONTROLLER_NORMAL: + for port in tasRuns[run_index].portsList: + if tasRuns[run_index].controllerType == CONTROLLER_NORMAL: limit = 1 - elif run.controllerType == CONTROLLER_MULTITAP: + elif tasRuns[run_index].controllerType == CONTROLLER_MULTITAP: limit = 4 else: limit = 2 @@ -891,13 +909,14 @@ def handleTransition(run, transition): controllerMask = "".join(controllers) # convert binary to string # setup events #s e lane_num byte controllerMask - command = 'se' + str(min(run.portsList)) + command = 'se' + str(min(tasRuns[run_index].portsList)) # do first byte byte = list('{0:08b}'.format( int(transition.window / 0.25))) # create padded bytestring, convert to list for manipulation byte[0] = '1' # enable flag bytestring = "".join(byte) # turn back into string ser.write(command + chr(int(bytestring, 2)) + chr(int(controllerMask, 2))) + windowState[run_index] = transition.window # ----- MAIN EXECUTION BEGINS HERE ----- @@ -962,7 +981,7 @@ def handleTransition(run, transition): for transition in run.transitions: if 0 <= (transition.frameno+run.dummyFrames+prebuffer) - frameCounts[run_index] < latches: # we're about to pass the transition frame - handleTransition(run,transition) + handleTransition(run_index,transition) if latches > 0: send_frames(run_index, latches) From c6ebf7157c74dd64c6ea016ff13bdf74e581e443 Mon Sep 17 00:00:00 2001 From: Justin S Date: Mon, 17 Oct 2016 15:40:50 -0400 Subject: [PATCH 03/47] re-organized code, new data structure --- scripts/streaming/TASLink.py | 202 ++++++++++++++++------------------- 1 file changed, 92 insertions(+), 110 deletions(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 37b5184..ddbc5bc 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -1,5 +1,3 @@ -# TODO: redo all random lists as one big class, which monitor each run's state - import os import serial from serial import SerialException @@ -19,44 +17,40 @@ def complete(text, state): return (glob.glob(text + '*') + [None])[state] - def complete_nostate(text, *ignored): return glob.glob(text + '*') + [None] - readline.set_completer_delims(' \t\n') readline.parse_and_bind("tab: complete") readline.set_completer(complete) +# important constants +lanes = [[-1], [1, 2, 5, 6], [3, 4, 7, 8], [5, 6], [7, 8]] +MASKS = 'ABCD' CONTROLLER_NORMAL = 0 # 1 controller CONTROLLER_Y = 1 #: y-cable [like half a multitap] CONTROLLER_MULTITAP = 2 #: multitap (Ports 1 and 2 only) [snes only] CONTROLLER_FOUR_SCORE = 3 #: four-score [nes-only peripheral that we don't do anything with] - baud = 2000000 - prebuffer = 60 ser = None - TASLINK_CONNECTED = 1 # set to 0 for development without TASLink plugged in, set to 1 for actual testing +# important global variables to keep track of consolePorts = [2, 0, 0, 0, 0] # 1 when in use, 0 when available. 2 is used to waste cell 0 consoleLanes = [2, 0, 0, 0, 0, 0, 0, 0, 0] # 1 when in use, 0 when available. 2 is used to waste cell 0 -lanes = [[-1], [1, 2, 5, 6], [3, 4, 7, 8], [5, 6], [7, 8]] -MASKS = 'ABCD' masksInUse = [0, 0, 0, 0] -tasRuns = [] -inputBuffers = [] -customCommands = [] -isRunModified = [] -dpcmState = [] -windowState = [] -frameCounts = [0, 0, 0, 0] - +runStatuses = [] # list of currently active runs and their statuses selected_run = -1 -# For all x in [0,4), tasRuns[x] should always correspond to have customCommands[x]. -# Each tasRuns[x] listens for latch on the min of of its ports. Each run has progressed up to frame frameCounts[x]. +class RunStatus(object): + tasRun = None + inputBuffer = None + customCommand = None + isRunModified = None + dpcmState = None + windowState = None + frameCount = 0 def readint(question): num = -1 @@ -107,34 +101,30 @@ def load(filename): print("ERROR: Requested ports already in use!") return False - # tried switching these two to eliminate the elusive runtime error - setupCommunication(run) - tasRuns.append(run) - isRunModified.append(False) - dpcmState.append(run.dpcmFix) - windowState.append(run.window) + rs = RunStatus() + rs.customCommand = setupCommunication(run) + rs.inputBuffer = run.getInputBuffer(rs.customCommand) + rs.tasRun = run + rs.isRunModified = False + rs.dpcmState = run.dpcmFix + rs.windowState = run.window + runStatuses.append(rs) - selected_run = len(tasRuns) - 1 + selected_run = len(runStatuses) - 1 send_frames(selected_run, prebuffer) print("Run has been successfully loaded!") def send_frames(index, amount): - framecount = frameCounts[index] + framecount = runStatuses[index].frameCount if TASLINK_CONNECTED == 1: - try: - ser.write(''.join(inputBuffers[index][framecount:(framecount + amount)])) - except IndexError: - print("Index error in send_frames. This shouldn't happen.\nDEBUG INFORMATION:") - print("Index: "+str(index)) - print("Amount: "+str(amount)) - print("len(inputBuffers): "+str(len(inputBuffers))) + ser.write(''.join(runStatuses[index].inputBuffer[framecount:(framecount + amount)])) else: - print("DATA SENT: ", ''.join(inputBuffers[index][framecount:(framecount + amount)])) + print("DATA SENT: ", ''.join(runStatuses[index].inputBuffer[framecount:(framecount + amount)])) - frameCounts[index] += amount + runStatuses[index].frameCount += amount class Transition(object): frameno = None @@ -299,7 +289,6 @@ def setupCommunication(tasrun): # setup custom stream command command = 's' customCommand = getNextMask() - customCommands.append(customCommand) if customCommand == 'Z': print("ERROR: all four custom streams are full!") # TODO: handle gracefully @@ -328,7 +317,7 @@ def setupCommunication(tasrun): else: print("r", controllerMask) - inputBuffers.append(tasrun.getInputBuffer(customCommand)) # add the input buffer to a global list of input buffers + return customCommand def isConsolePortAvailable(port, type): @@ -386,12 +375,12 @@ def setprompt(self): if selected_run == -1: self.prompt = "TASLink> " else: - if isRunModified[selected_run]: + if runStatuses[selected_run].isRunModified: self.prompt = "TASLink[#" + str(selected_run + 1) + "][" + str( - tasRuns[selected_run].dummyFrames) + "f][UNSAVED]> " + runStatuses[selected_run].tasRun.dummyFrames) + "f][UNSAVED]> " else: self.prompt = "TASLink[#" + str(selected_run + 1) + "][" + str( - tasRuns[selected_run].dummyFrames) + "f]> " + runStatuses[selected_run].tasRun.dummyFrames) + "f]> " def __init__(self): cmd.Cmd.__init__(self) @@ -433,7 +422,8 @@ def emptyline(self): def do_exit(self, data): """Not 'goodbyte' but rather so long for a while""" - for index,modified in enumerate(isRunModified): + for index,runstatus in enumerate(runStatuses): + modified = runstatus.isRunModified if modified: while True: save = raw_input("Run #"+str(index+1)+" is not saved. Save (y/n)? ") @@ -450,7 +440,7 @@ def do_exit(self, data): def do_save(self, data): """Save a run to a file""" # print options - if not tasRuns: + if not runStatuses: print("No currently active runs.") return False if data != "": @@ -459,7 +449,7 @@ def do_save(self, data): except ValueError: print("ERROR: Invalid run number!") return False - if 0 < runID <= len(tasRuns): # confirm valid run number + if 0 < runID <= len(runStatuses): # confirm valid run number pass else: print("ERROR: Invalid run number!") @@ -470,9 +460,9 @@ def do_save(self, data): filename = raw_input("Please enter filename: ") with open(filename, 'w') as f: - f.write(yaml.dump(tasRuns[runID - 1])) + f.write(yaml.dump(runStatuses[runID - 1].tasRun)) - isRunModified[runID - 1] = False + runStatuses[runID - 1].isRunModified = False print("Save complete!") @@ -493,7 +483,7 @@ def do_restart(self, data): def do_modify_frames(self, data): """Modify the initial blank input frames""" # print options - if not tasRuns: + if not runStatuses: print("No currently active runs.") return False if data != "": @@ -502,7 +492,7 @@ def do_modify_frames(self, data): except ValueError: print("ERROR: Invalid run number!") pass - if 0 < runID <= len(tasRuns): # confirm valid run number + if 0 < runID <= len(runStatuses): # confirm valid run number pass else: print("ERROR: Invalid run number!") @@ -510,14 +500,14 @@ def do_modify_frames(self, data): else: runID = selected_run + 1 index = runID - 1 - run = tasRuns[index] + run = runStatuses[index].tasRun print("The current number of initial blank frames is : " + str(run.dummyFrames)) frames = readint("How many initial blank frames do you want? ") difference = frames - run.dummyFrames # positive means we're adding frames, negative means we're removing frames run.dummyFrames = frames # modify input buffer accordingly if difference > 0: - working_string = customCommands[index] + working_string = runStatuses[index].customCommand max = int(run.controllerBits / 8) * run.numControllers # bytes * number of controllers # next we take controller type into account if run.controllerType == CONTROLLER_Y or run.controllerType == CONTROLLER_FOUR_SCORE: @@ -528,20 +518,18 @@ def do_modify_frames(self, data): working_string += chr(0xFF) for count in range(difference): - inputBuffers[index].insert(0, working_string) # add the correct number of blank input frames + runStatuses[index].inputBuffer.insert(0, working_string) # add the correct number of blank input frames elif difference < 0: # remove input frames - inputBuffers[index] = inputBuffers[index][-difference:] + runStatuses[index].inputBuffer = runStatuses[index].inputBuffer[-difference:] - isRunModified[index] = True + runStatuses[index].isRunModified = True print("Run has been updated. Remember to save if you want this change to be permanent!") def do_reset(self, data): """Reset an active run back to frame 0""" - global frameCounts - # print options - if not tasRuns: + if not runStatuses: print("No currently active runs.") return False @@ -550,13 +538,13 @@ def do_reset(self, data): ser.write("R") else: print("R") - frameCounts = [0, 0, 0, 0] - for index in range(len(tasRuns)): + for index in range(len(runStatuses)): + runStatuses[index].frameCount = 0 send_frames(index, prebuffer) # re-pre-buffer-! # return runs to their original state t = Transition() - t.dpcmFix = tasRuns[index].dpcmFix - t.window = tasRuns[index].window + t.dpcmFix = runStatuses[index].tasRun.dpcmFix + t.window = runStatuses[index].tasRun.window handleTransition(index,t) print("Reset command given to all runs!") return False @@ -567,7 +555,7 @@ def do_reset(self, data): except ValueError: print("ERROR: Please enter 'all' or an integer!\n") return False - if 0 < runID <= len(tasRuns): # confirm valid run number + if 0 < runID <= len(runStatuses): # confirm valid run number pass else: print("ERROR: Invalid run number!") @@ -577,7 +565,7 @@ def do_reset(self, data): index = runID - 1 # get the lane mask controllers = list('00000000') - tasrun = tasRuns[index] + tasrun = runStatuses[index].tasRun if tasrun.controllerType == CONTROLLER_NORMAL: limit = 1 elif tasrun.controllerType == CONTROLLER_MULTITAP: @@ -596,12 +584,12 @@ def do_reset(self, data): else: print("r" + controllerMask, 2) # clear the buffer - frameCounts[index] = 0 + runStatuses[index].frameCount = 0 send_frames(index, prebuffer) # re-pre-buffer-! # return run to its original state t = Transition() - t.dpcmFix = tasRuns[index].dpcmFix - t.window = tasRuns[index].window + t.dpcmFix = runStatuses[index].tasRun.dpcmFix + t.window = runStatuses[index].tasRun.window handleTransition(index, t) print("Reset complete!") @@ -609,7 +597,7 @@ def do_remove(self, data): """Remove one of the current runs.""" global selected_run # print options - if not tasRuns: + if not runStatuses: print("No currently active runs.") return False if data != "": @@ -618,7 +606,7 @@ def do_remove(self, data): except ValueError: print("ERROR: Invalid run number!") return False - if 0 < runID <= len(tasRuns): # confirm valid run number + if 0 < runID <= len(runStatuses): # confirm valid run number pass else: print("ERROR: Invalid run number!") @@ -628,7 +616,7 @@ def do_remove(self, data): index = runID - 1 # make the mask controllers = list('00000000') - tasrun = tasRuns[index] + tasrun = runStatuses[index].tasRun if tasrun.controllerType == CONTROLLER_NORMAL: limit = 1 elif tasrun.controllerType == CONTROLLER_MULTITAP: @@ -640,22 +628,12 @@ def do_remove(self, data): controllers[8 - lanes[port][counter]] = '1' controllerMask = "".join(controllers) # convert binary to string # free ports - for port in tasRuns[index].portsList: - releaseConsolePort(port, tasRuns[index].controllerType) + for port in runStatuses[index].tasRun.portsList: + releaseConsolePort(port, runStatuses[index].tasRun.controllerType) # free custom stream and event - freeMask(customCommands[index]) + freeMask(runStatuses[index].customCommand) # remove input and run from lists - del inputBuffers[index] - del tasRuns[index] - del customCommands[index] - del isRunModified[index] - del dpcmState[index] - del windowState[index] - - # reset frame counts, move them accordingly - for i in range(index, len(frameCounts) - 1): # one less than the hardcoded max of array - frameCounts[i] = frameCounts[i + 1] - frameCounts[-1] = 0 # max should be 0 no matter what, since we've just removed one and compressed the list + del runStatuses[index] # clear the lanes if TASLINK_CONNECTED: @@ -663,7 +641,7 @@ def do_remove(self, data): else: print("r" + controllerMask, 2) # clear the buffer - selected_run = len(tasRuns) - 1 # even if there was only 1 run, it will go to -1, signaling we have no more runs + selected_run = len(runStatuses) - 1 # even if there was only 1 run, it will go to -1, signaling we have no more runs print("Run has been successfully removed!") @@ -680,19 +658,19 @@ def do_load(self, data): def do_list(self, data): """List all active runs""" - if not tasRuns: + if not runStatuses: print("No currently active runs.") return False - for index, run in enumerate(tasRuns): + for index, runstatus in enumerate(runStatuses): print("Run #" + str(index + 1) + ": ") - print yaml.dump(run) + print yaml.dump(runstatus.tasRun) pass def do_select(self, data): """Select a run to modify with other commands""" global selected_run - if not tasRuns: + if not runStatuses: print("No currently active runs.") return False @@ -703,7 +681,7 @@ def do_select(self, data): except ValueError: print("ERROR: Please enter an integer!\n") return False - if 0 < runID <= len(tasRuns): # confirm valid run number + if 0 < runID <= len(runStatuses): # confirm valid run number pass else: print("ERROR: Invalid run number!") @@ -711,7 +689,7 @@ def do_select(self, data): else: while True: runID = readint("Which run # do you want to select? ") - if 0 < runID <= len(tasRuns): # confirm valid run number + if 0 < runID <= len(runStatuses): # confirm valid run number break else: print("ERROR: Invalid run number!") @@ -757,8 +735,8 @@ def do_add_transition(self, data): t.dpcmFix = dpcm_fix t.frameno = frameNum t.window = window - tasRuns[selected_run].addTransition(t) - isRunModified[selected_run] = True + runStatuses[selected_run].tasRun.addTransition(t) + runStatuses[selected_run].isRunModified = True def do_new(self, data): """Create a new run with parameters specified in the terminal""" @@ -865,13 +843,16 @@ def do_new(self, data): # create TASRun object and assign it to our global, defined above tasrun = TASRun(numControllers, portsList, controllerType, controllerBits, overread, window, fileName, dummyFrames, dpcm_fix) - setupCommunication(tasrun) - tasRuns.append(tasrun) - isRunModified.append(True) - dpcmState.append(dpcm_fix) - windowState.append(window) + rs = RunStatus() + rs.customCommand = setupCommunication(tasrun) + rs.inputBuffer = tasrun.getInputBuffer(rs.customCommand) + rs.tasRun = tasrun + rs.isRunModified = True + rs.dpcmState = dpcm_fix + rs.windowState = run.window + runStatuses.append(rs) - selected_run = len(tasRuns) - 1 + selected_run = len(runStatuses) - 1 send_frames(selected_run, prebuffer) @@ -885,22 +866,22 @@ def postloop(self): print def handleTransition(run_index, transition): - if dpcmState[run_index] != transition.dpcmFix: - for port in tasRuns[run_index].portsList: + if runStatuses[run_index].dpcmState != transition.dpcmFix: + for port in runStatuses[run_index].tasRun.portsList: # enable the console ports command = "sp" command += str(port) # should look like 'sp1' now - portData = tasRuns[run_index].controllerType + portData = runStatuses[run_index].tasRun.controllerType if transition.dpcmFix: portData += 128 # add the flag for the 8th bit ser.write(command + chr(portData)) - dpcmState[run_index] = transition.dpcmFix - if windowState[run_index] != transition.window: + runStatuses[run_index].dpcmState = transition.dpcmFix + if runStatuses[run_index].windowState != transition.window: controllers = list('00000000') - for port in tasRuns[run_index].portsList: - if tasRuns[run_index].controllerType == CONTROLLER_NORMAL: + for port in runStatuses[run_index].tasRun.portsList: + if runStatuses[run_index].tasRun.controllerType == CONTROLLER_NORMAL: limit = 1 - elif tasRuns[run_index].controllerType == CONTROLLER_MULTITAP: + elif runStatuses[run_index].tasRun.controllerType == CONTROLLER_MULTITAP: limit = 4 else: limit = 2 @@ -909,14 +890,14 @@ def handleTransition(run_index, transition): controllerMask = "".join(controllers) # convert binary to string # setup events #s e lane_num byte controllerMask - command = 'se' + str(min(tasRuns[run_index].portsList)) + command = 'se' + str(min(runStatuses[run_index].tasRun.portsList)) # do first byte byte = list('{0:08b}'.format( int(transition.window / 0.25))) # create padded bytestring, convert to list for manipulation byte[0] = '1' # enable flag bytestring = "".join(byte) # turn back into string ser.write(command + chr(int(bytestring, 2)) + chr(int(controllerMask, 2))) - windowState[run_index] = transition.window + runStatuses[run_index].windowState = transition.window # ----- MAIN EXECUTION BEGINS HERE ----- @@ -950,7 +931,7 @@ def handleTransition(run_index, transition): # main thread of execution = serial communication thread # keep loop as tight as possible to eliminate communication overhead -while t.isAlive() and not inputBuffers: # wait until we have at least one run ready to go +while t.isAlive() and not runStatuses: # wait until we have at least one run ready to go pass if TASLINK_CONNECTED and not t.isAlive(): @@ -975,12 +956,13 @@ def handleTransition(run_index, transition): if numBytes > 60: print ("WARNING: High frame read detected: " + str(numBytes)) - for run_index, run in enumerate(tasRuns): + for run_index, runstatus in enumerate(runStatuses): + run = runstatus.tasRun port = min(run.portsList) # the same port we have an event listener on latches = latchCounts[port] for transition in run.transitions: - if 0 <= (transition.frameno+run.dummyFrames+prebuffer) - frameCounts[run_index] < latches: # we're about to pass the transition frame + if 0 <= (transition.frameno+run.dummyFrames+prebuffer) - runstatus.frameCount < latches: # we're about to pass the transition frame handleTransition(run_index,transition) if latches > 0: From f9584754c771ec1fbcbe1e3ac4f1568454143a81 Mon Sep 17 00:00:00 2001 From: Justin S Date: Mon, 17 Oct 2016 16:12:35 -0400 Subject: [PATCH 04/47] significantly reduced load at idle --- scripts/streaming/TASLink.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index ddbc5bc..cbf7ff5 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -6,8 +6,8 @@ import threading import yaml import gc +import time # import math -# import time import rlcompleter, readline # to add support for tab completion of commands import glob @@ -908,7 +908,7 @@ def handleTransition(run_index, transition): if TASLINK_CONNECTED: try: - ser = serial.Serial(sys.argv[1], baud) + ser = serial.Serial(sys.argv[1], baud, timeout=1) except SerialException: print ("ERROR: the specified interface (" + sys.argv[1] + ") is in use") sys.exit(0) @@ -932,6 +932,7 @@ def handleTransition(run_index, transition): # main thread of execution = serial communication thread # keep loop as tight as possible to eliminate communication overhead while t.isAlive() and not runStatuses: # wait until we have at least one run ready to go + time.sleep(0.1) pass if TASLINK_CONNECTED and not t.isAlive(): @@ -941,21 +942,20 @@ def handleTransition(run_index, transition): # t3h urn if TASLINK_CONNECTED: while t.isAlive(): - - while ser.inWaiting() == 0 and t.isAlive(): - pass - if not t.isAlive(): ser.close() # close serial communication cleanly break - numBytes = ser.inWaiting() - c = ser.read(numBytes) + c = ser.read(1) + if c == '': # nothing was waiting + continue # so try again + numBytes = ser.inWaiting() # is anything else waiting + if numBytes > 0: + c += ser.read(numBytes) + if numBytes > 60: + print ("WARNING: High frame read detected: " + str(numBytes)) latchCounts = [-1, c.count('f'), c.count('g'), c.count('h'), c.count('i')] - if numBytes > 60: - print ("WARNING: High frame read detected: " + str(numBytes)) - for run_index, runstatus in enumerate(runStatuses): run = runstatus.tasRun port = min(run.portsList) # the same port we have an event listener on From 5e39a34175241628efb6b3803e5ef276d8cc6717 Mon Sep 17 00:00:00 2001 From: Justin S Date: Wed, 19 Oct 2016 17:58:05 -0400 Subject: [PATCH 05/47] quick bug fix --- scripts/streaming/TASLink.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index cbf7ff5..f4a1f46 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -34,6 +34,8 @@ def complete_nostate(text, *ignored): baud = 2000000 prebuffer = 60 ser = None +supportedExtensions = ['r08','r16m'] # TODO: finish implementing this + TASLINK_CONNECTED = 1 # set to 0 for development without TASLink plugged in, set to 1 for actual testing # important global variables to keep track of @@ -148,7 +150,7 @@ def __init__(self, num_controllers, ports_list, controller_type, controller_bits if self.fileExtension == 'r08': self.maxControllers = 2 - elif self.fileExtension == 'r16' or self.fileExtension == 'r16m': + elif self.fileExtension == 'r16m': self.maxControllers = 8 else: self.maxControllers = 1 # random default, but truly we need to support other formats @@ -209,7 +211,7 @@ def getInputBuffer(self, customCommand): command_string += working_string[counter+1:counter+2] # math not-so-magic buffer[frameno+self.dummyFrames] = command_string frameno += 1 - elif self.fileExtension == 'r16' or self.fileExtension == 'r16m': + elif self.fileExtension == 'r16m': while True: working_string = customCommand @@ -849,7 +851,7 @@ def do_new(self, data): rs.tasRun = tasrun rs.isRunModified = True rs.dpcmState = dpcm_fix - rs.windowState = run.window + rs.windowState = window runStatuses.append(rs) selected_run = len(runStatuses) - 1 From 2f4eacf96554caa76c5c9159299f9e463696ea10 Mon Sep 17 00:00:00 2001 From: Justin S Date: Wed, 20 Sep 2017 21:47:00 -0400 Subject: [PATCH 06/47] Added manual delay for restart command. --- scripts/streaming/TASLink.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index f4a1f46..279847d 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -479,7 +479,9 @@ def do_on(self, data): def do_restart(self, data): """Turns the SNES console off, restarts the current run, and turns the SNES console on""" self.do_off(data) + time.sleep(1) self.do_reset(data) + time.sleep(1) self.do_on(data) def do_modify_frames(self, data): From bb9262cdd7b63346ca23911a6b68929a2f396a00 Mon Sep 17 00:00:00 2001 From: Justin S Date: Thu, 5 Oct 2017 22:25:50 -0400 Subject: [PATCH 07/47] little --- scripts/streaming/TASLink.py | 3 +++ scripts/streaming/tasbot.yml | 9 --------- 2 files changed, 3 insertions(+), 9 deletions(-) delete mode 100755 scripts/streaming/tasbot.yml diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 279847d..4227557 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -471,10 +471,12 @@ def do_save(self, data): def do_off(self, data): """Turns off the SNES via reset pin, if connected""" ser.write("sd1") + print("Console off.") def do_on(self, data): """Turns on the SNES via reset pin, if connected""" ser.write("sd0") + print("Console on.") def do_restart(self, data): """Turns the SNES console off, restarts the current run, and turns the SNES console on""" @@ -483,6 +485,7 @@ def do_restart(self, data): self.do_reset(data) time.sleep(1) self.do_on(data) + print("The restart process is complete!") def do_modify_frames(self, data): """Modify the initial blank input frames""" diff --git a/scripts/streaming/tasbot.yml b/scripts/streaming/tasbot.yml deleted file mode 100755 index 2d1fdc4..0000000 --- a/scripts/streaming/tasbot.yml +++ /dev/null @@ -1,9 +0,0 @@ -- option1: true - source: run1.r08 - value1: '23' -- option1: false - source: run2.r08 - value1: '43' -- option1: true - source: run3.r08 - value1: '77' From 1a01b69e280a0a8b7842208f1211b2339b562f91 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Mon, 16 Apr 2018 12:26:14 +1000 Subject: [PATCH 08/47] correct function creating a new run and setting the number of dummy frames --- scripts/streaming/TASLink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 4227557..644bf30 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -840,7 +840,7 @@ def do_new(self, data): while True: try: dummyFrames = readint("Number of blank input frames to prepend? ") - if window < 0: + if dummyFrames < 0: print("ERROR: Please enter a positive number!\n") continue else: From 3026c3603aca206857e5b958340d3b9aec8f3b0a Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Mon, 16 Apr 2018 12:26:14 +1000 Subject: [PATCH 09/47] correct function creating a new run and setting the number of dummy frames --- scripts/streaming/TASLink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 4227557..644bf30 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -840,7 +840,7 @@ def do_new(self, data): while True: try: dummyFrames = readint("Number of blank input frames to prepend? ") - if window < 0: + if dummyFrames < 0: print("ERROR: Please enter a positive number!\n") continue else: From 90a77bc190764fd41cafd6edde83adadc6ea5c03 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sun, 22 Apr 2018 14:30:55 +1000 Subject: [PATCH 10/47] First Attempt at everdrive aware dummy frames --- scripts/streaming/TASLink.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 644bf30..27277f1 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -36,6 +36,8 @@ def complete_nostate(text, *ignored): ser = None supportedExtensions = ['r08','r16m'] # TODO: finish implementing this +EVERDRIVEFRAMES = 47 # Number of frames to offset dummy frames by when running on an everdrive + TASLINK_CONNECTED = 1 # set to 0 for development without TASLink plugged in, set to 1 for actual testing # important global variables to keep track of @@ -134,7 +136,7 @@ class Transition(object): dpcmFix = None class TASRun(object): - def __init__(self, num_controllers, ports_list, controller_type, controller_bits, ovr, wndw, file_name, dummy_frames, dpcm_fix): + def __init__(self, num_controllers, ports_list, controller_type, controller_bits, ovr, wndw, file_name, dummy_frames, dpcm_fix, is_everdrive): self.numControllers = num_controllers self.portsList = ports_list self.controllerType = controller_type @@ -145,6 +147,7 @@ def __init__(self, num_controllers, ports_list, controller_type, controller_bits self.dummyFrames = dummy_frames self.dpcmFix = dpcm_fix self.transitions = [] + self.isEverdrive = is_everdrive self.fileExtension = file_name.split(".")[-1].strip() # pythonic last element of a list/string/array @@ -184,7 +187,10 @@ def getInputBuffer(self, customCommand): working_string = customCommand for bytes in range(bytesPerCommand): working_string += chr(0xFF) - buffer[frame] = working_string + if self.isEverdrive: + buffer[EVERDRIVEFRAMES+frame] = working_string + else: + buffer[frame] = working_string frameno = 0 invertedfile = [""] * len(wholefile) @@ -525,9 +531,15 @@ def do_modify_frames(self, data): working_string += chr(0xFF) for count in range(difference): - runStatuses[index].inputBuffer.insert(0, working_string) # add the correct number of blank input frames + if run.isEverdrive: + runStatuses[index].inputBuffer.insert(EVERDRIVEFRAMES, working_string) + else: + runStatuses[index].inputBuffer.insert(0, working_string) # add the correct number of blank input frames elif difference < 0: # remove input frames - runStatuses[index].inputBuffer = runStatuses[index].inputBuffer[-difference:] + if run.isEverdrive: + runStatuses[index].inputBuffer = runStatuses[index].inputBuffer[0:EVERDRIVEFRAMES]+runStatuses[index].inputBuffer[EVERDRIVEFRAMES-difference:] + else: + runStatuses[index].inputBuffer = runStatuses[index].inputBuffer[-difference:] runStatuses[index].isRunModified = True @@ -847,8 +859,18 @@ def do_new(self, data): break except ValueError: print("ERROR: Please enter integers!\n") + # is the run on a everdrive + while True: + is_everdrive = raw_input("Is the run playback on a Everdrive (y/n)? ") + if is_everdrive.lower() == 'y': + is_everdrive = True + break + elif is_everdrive.lower() == 'n': + is_everdrive = False + break + print("ERROR: Please enter y for yes or n for no!\n") # create TASRun object and assign it to our global, defined above - tasrun = TASRun(numControllers, portsList, controllerType, controllerBits, overread, window, fileName, dummyFrames, dpcm_fix) + tasrun = TASRun(numControllers, portsList, controllerType, controllerBits, overread, window, fileName, dummyFrames, dpcm_fix, is_everdrive) rs = RunStatus() rs.customCommand = setupCommunication(tasrun) From 2bbae880d905dfb0a7bcff108bb31366e6382df8 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Fri, 27 Apr 2018 07:57:00 +1000 Subject: [PATCH 11/47] change to number of frames in everdrive header --- scripts/streaming/TASLink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 27277f1..12a0bb3 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -36,7 +36,7 @@ def complete_nostate(text, *ignored): ser = None supportedExtensions = ['r08','r16m'] # TODO: finish implementing this -EVERDRIVEFRAMES = 47 # Number of frames to offset dummy frames by when running on an everdrive +EVERDRIVEFRAMES = 61 # Number of frames to offset dummy frames by when running on an everdrive TASLINK_CONNECTED = 1 # set to 0 for development without TASLink plugged in, set to 1 for actual testing From a3e5a1cacd28f2033c428baccf81fc8ac3c8a030 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Fri, 27 Apr 2018 09:02:34 +1000 Subject: [PATCH 12/47] Add first attempt a command to add header for everdrive --- scripts/streaming/TASLink.py | 43 +++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 12a0bb3..d39fc86 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -756,6 +756,47 @@ def do_add_transition(self, data): t.window = window runStatuses[selected_run].tasRun.addTransition(t) runStatuses[selected_run].isRunModified = True + + def do_toggle_ed_header(self): + if selected_run == -1: + print("ERROR: No run is selected!\n") + return + run = runStatuses[selected_run].tasRun + + if run.isEverdrive == 1: + print("Removing Everdrive Header!\n") + del runStatuses[selected_run].tasRun.inputBuffer[EVERDRIVEFRAMES:] + runStatuses[selected_run].tasRun.isEverdrive = 0 + runStatuses[selected_run].isRunModified = True + + if run.isEverdrive == 0: + print("Adding Everdrive Header!\n") + blankframe = runStatuses[selected_run].customCommand + max = int(run.controllerBits / 8) * run.numControllers # bytes * number of controllers + # next we take controller type into account + if run.controllerType == CONTROLLER_Y or run.controllerType == CONTROLLER_FOUR_SCORE: + max *= 2 + elif run.controllerType == CONTROLLER_MULTITAP: + max *= 4 + for bytes in range(max): + blankframe += chr(0xFF) + startframe = runStatuses[selected_run].customCommand + max = int(run.controllerBits / 8) * run.numControllers # bytes * number of controllers + # next we take controller type into account + if run.controllerType == CONTROLLER_Y or run.controllerType == CONTROLLER_FOUR_SCORE: + max *= 2 + elif run.controllerType == CONTROLLER_MULTITAP: + max *= 4 + for bytes in range(max): + if bytes == 1: + startframe += chr(0xEF) # press start on controller 1 + else: + startframe += chr(0xFF) + runStatuses[selected_run].tasRun.inputBuffer.insert(0, startframe) # add a frame pressing start to start of input buffer + for frame in range(0, EVERDRIVEFRAMES-1): + runStatuses[selected_run].tasRun.inputBuffer.insert(0, blankframe) # add x number of blank frames to start of input buffer + runStatuses[selected_run].tasRun.isEverdrive = 1 + runStatuses[selected_run].isRunModified = True def do_new(self, data): """Create a new run with parameters specified in the terminal""" @@ -861,7 +902,7 @@ def do_new(self, data): print("ERROR: Please enter integers!\n") # is the run on a everdrive while True: - is_everdrive = raw_input("Is the run playback on a Everdrive (y/n)? ") + is_everdrive = raw_input("Does the input file have a header for playback on an Everdrive (y/n)? ") if is_everdrive.lower() == 'y': is_everdrive = True break From 86fa04e22975141bda3cd582340d191275a16afc Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sat, 28 Apr 2018 20:47:06 +1000 Subject: [PATCH 13/47] cleanup and fix of process for adding/removing header for starting a everdrive --- scripts/streaming/TASLink.py | 75 ++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index d39fc86..2f4377f 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -374,6 +374,37 @@ def releaseConsolePort(port, type): consoleLanes[lanes[port][0]] = 0 consoleLanes[lanes[port][1]] = 0 +def remove_everdrive_header(tasRun, runid): + print("Removing Everdrive Header!\n") + newbuffer = runStatuses[runid].inputBuffer + oldbuffer = newbuffer + newbuffer = oldbuffer[EVERDRIVEFRAMES:] + runStatuses[runid].inputBuffer = newbuffer + +def add_everdrive_header(tasRun, runid): + print("Adding Everdrive Header!\n") + newbuffer = runStatuses[runid].inputBuffer + blankframe = runStatuses[runid].customCommand + max = int(tasRun.controllerBits / 8) * tasRun.numControllers # bytes * number of controllers + # next we take controller type into account + if tasRun.controllerType == CONTROLLER_Y or tasRun.controllerType == CONTROLLER_FOUR_SCORE: + max *= 2 + elif tasRun.controllerType == CONTROLLER_MULTITAP: + max *= 4 + for bytes in range(max): + blankframe += chr(0xFF) + startframe = runStatuses[runid].customCommand + max = int(tasRun.controllerBits / 8) * tasRun.numControllers # bytes * number of controllers + # next we take controller type into account + for bytes in range(max): + if bytes == 0: + startframe += chr(0xEF) # press start on controller 1 + else: + startframe += chr(0xFF) + newbuffer.insert(0, startframe) # add a frame pressing start to start of input buffer + for frame in range(0, EVERDRIVEFRAMES-1): + newbuffer.insert(0, blankframe) # add x number of blank frames to start of input buffer + runStatuses[runid].inputBuffer = newbuffer # return false exits the function # return true exits the whole CLI @@ -757,46 +788,16 @@ def do_add_transition(self, data): runStatuses[selected_run].tasRun.addTransition(t) runStatuses[selected_run].isRunModified = True - def do_toggle_ed_header(self): + def do_toggle_everdrive(self, data): if selected_run == -1: print("ERROR: No run is selected!\n") return - run = runStatuses[selected_run].tasRun - - if run.isEverdrive == 1: - print("Removing Everdrive Header!\n") - del runStatuses[selected_run].tasRun.inputBuffer[EVERDRIVEFRAMES:] - runStatuses[selected_run].tasRun.isEverdrive = 0 - runStatuses[selected_run].isRunModified = True - - if run.isEverdrive == 0: - print("Adding Everdrive Header!\n") - blankframe = runStatuses[selected_run].customCommand - max = int(run.controllerBits / 8) * run.numControllers # bytes * number of controllers - # next we take controller type into account - if run.controllerType == CONTROLLER_Y or run.controllerType == CONTROLLER_FOUR_SCORE: - max *= 2 - elif run.controllerType == CONTROLLER_MULTITAP: - max *= 4 - for bytes in range(max): - blankframe += chr(0xFF) - startframe = runStatuses[selected_run].customCommand - max = int(run.controllerBits / 8) * run.numControllers # bytes * number of controllers - # next we take controller type into account - if run.controllerType == CONTROLLER_Y or run.controllerType == CONTROLLER_FOUR_SCORE: - max *= 2 - elif run.controllerType == CONTROLLER_MULTITAP: - max *= 4 - for bytes in range(max): - if bytes == 1: - startframe += chr(0xEF) # press start on controller 1 - else: - startframe += chr(0xFF) - runStatuses[selected_run].tasRun.inputBuffer.insert(0, startframe) # add a frame pressing start to start of input buffer - for frame in range(0, EVERDRIVEFRAMES-1): - runStatuses[selected_run].tasRun.inputBuffer.insert(0, blankframe) # add x number of blank frames to start of input buffer - runStatuses[selected_run].tasRun.isEverdrive = 1 - runStatuses[selected_run].isRunModified = True + if runStatuses[selected_run].tasRun.isEverdrive == True: + remove_everdrive_header(runStatuses[selected_run].tasRun, selected_run) + runStatuses[selected_run].tasRun.isEverdrive = False + elif runStatuses[selected_run].tasRun.isEverdrive == False: + add_everdrive_header(runStatuses[selected_run].tasRun, selected_run) + runStatuses[selected_run].tasRun.isEverdrive = True def do_new(self, data): """Create a new run with parameters specified in the terminal""" From 5cb06f9fb9f8ed1e43953c69bc6e2296a1fb0458 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sat, 28 Apr 2018 21:19:44 +1000 Subject: [PATCH 14/47] add support for adding everdrive on run load/creation --- scripts/streaming/TASLink.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 2f4377f..0f4a2ac 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -116,6 +116,10 @@ def load(filename): selected_run = len(runStatuses) - 1 + # add everdrive header if needed + if run.isEverdrive == True: + add_everdrive_header(runStatuses[selected_run].tasRun, selected_run) + send_frames(selected_run, prebuffer) print("Run has been successfully loaded!") @@ -925,6 +929,10 @@ def do_new(self, data): selected_run = len(runStatuses) - 1 + # add everdrive header if needed + if tasrun.isEverdrive == True: + add_everdrive_header(runStatuses[selected_run].tasRun, selected_run) + send_frames(selected_run, prebuffer) print("Run is ready to go!") From f90183c94dbebd5e65f7b96dbb36a87c735d1a1b Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Mon, 30 Apr 2018 09:48:16 +1000 Subject: [PATCH 15/47] Quick fix for loading run with everdrive header + dummy frames --- scripts/streaming/TASLink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 0f4a2ac..a728128 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -192,7 +192,7 @@ def getInputBuffer(self, customCommand): for bytes in range(bytesPerCommand): working_string += chr(0xFF) if self.isEverdrive: - buffer[EVERDRIVEFRAMES+frame] = working_string + buffer[frame] = working_string else: buffer[frame] = working_string From af1f47b5d7f403741379c33bbc40eed40eaaeda4 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Mon, 30 Apr 2018 09:52:52 +1000 Subject: [PATCH 16/47] removed redundant if statement --- scripts/streaming/TASLink.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index a728128..df8df66 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -191,10 +191,7 @@ def getInputBuffer(self, customCommand): working_string = customCommand for bytes in range(bytesPerCommand): working_string += chr(0xFF) - if self.isEverdrive: - buffer[frame] = working_string - else: - buffer[frame] = working_string + buffer[frame] = working_string frameno = 0 invertedfile = [""] * len(wholefile) From bba94ddda9c468a1a15b8f181fcbb03066b4ffd7 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Fri, 4 May 2018 08:39:37 +1000 Subject: [PATCH 17/47] change of wording on everdrive check in new run --- scripts/streaming/TASLink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index df8df66..aa4c55c 100644 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -904,7 +904,7 @@ def do_new(self, data): print("ERROR: Please enter integers!\n") # is the run on a everdrive while True: - is_everdrive = raw_input("Does the input file have a header for playback on an Everdrive (y/n)? ") + is_everdrive = raw_input("Add a header for playback on an Everdrive (y/n)? ") if is_everdrive.lower() == 'y': is_everdrive = True break From 7e00555a9190ccf0e2a1d9c9263f9ac146ae69e5 Mon Sep 17 00:00:00 2001 From: Christopher Edwards Date: Thu, 10 May 2018 18:24:20 -0400 Subject: [PATCH 18/47] Fix README formatting --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a26adec..a078f34 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,10 @@ TASLink is a series of hardware and supporting software built around the Papilio Pro FPGA development board designed for interfacing with consoles. The hardware is in the form of "wings" (or "shields") that can be attached depending on the situation. There is currently hardware for NES/SNES and the N64. -###NES/SNES -[![alt text](http://i.imgur.com/5fzLc4F.jpg)](http://i.imgur.com/OExTRlX.jpg)[![alt text](http://i.imgur.com/vFm82Yc.jpg)](http://i.imgur.com/v7FUWHM.jpg) +### NES/SNES + +[![alt text](http://i.imgur.com/5fzLc4F.jpg)](http://i.imgur.com/OExTRlX.jpg) +[![alt text](http://i.imgur.com/vFm82Yc.jpg)](http://i.imgur.com/v7FUWHM.jpg) This board supports up to 4 separate consoles simultaneously. @@ -12,7 +14,8 @@ This board supports up to 4 separate consoles simultaneously. This board supports SNES multitap on 1 console. -###NES/SNES Cables +### NES/SNES Cables + [![alt text](http://i.imgur.com/veWrtIu.jpg)](http://i.imgur.com/p0bdGMR.jpg) [![alt text](http://i.imgur.com/Sx84C0w.jpg)](http://i.imgur.com/s5Hz8Ch.jpg) [![alt text](http://i.imgur.com/pntr0qG.jpg)](http://i.imgur.com/byZXrEN.jpg) @@ -20,11 +23,12 @@ This board supports SNES multitap on 1 console. Cables were built with Eurocable brand CAT5 which is foil+braided shielded with a thick jacket. Heatshrink was added for extra strain relief. -###NES Visualization +### NES Visualization + [![alt text](http://i.imgur.com/B9WCjm5.jpg)](http://i.imgur.com/oQN17On.jpg) -###N64 +### N64 + [![alt text](http://i.imgur.com/LjJm9lz.jpg)](http://i.imgur.com/IqYb3Oq.jpg) This prototype board supports basic TAS playback and some more advanced features. - From f0fd03bf10def3649cdd9b9dbb1b30e4879d8e0c Mon Sep 17 00:00:00 2001 From: Christopher Edwards Date: Thu, 10 May 2018 19:55:03 -0400 Subject: [PATCH 19/47] Add shebang line to python scripts and make executable Add shebang for easy execution of python scripts and set the execute bit Fix typos in TASLink.py comments --- scripts/streaming/TASLink.py | 7 +++--- scripts/streaming/console_off.py | 37 ++++++++++++++-------------- scripts/streaming/console_on.py | 37 ++++++++++++++-------------- scripts/streaming/reset_console.py | 39 +++++++++++++++--------------- scripts/streaming/stream_NES.py | 1 + 5 files changed, 63 insertions(+), 58 deletions(-) mode change 100644 => 100755 scripts/streaming/TASLink.py mode change 100644 => 100755 scripts/streaming/console_off.py mode change 100644 => 100755 scripts/streaming/console_on.py mode change 100644 => 100755 scripts/streaming/reset_console.py mode change 100644 => 100755 scripts/streaming/stream_NES.py diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py old mode 100644 new mode 100755 index aa4c55c..b8bd9c6 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python2 import os import serial from serial import SerialException @@ -320,7 +321,7 @@ def setupCommunication(tasrun): else: print(command, bytestring, controllerMask) - # finnal, clear lanes and get ready to rock + # final, clear lanes and get ready to rock if TASLINK_CONNECTED: ser.write("r" + chr(int(controllerMask, 2))) else: @@ -714,7 +715,7 @@ def do_list(self, data): return False for index, runstatus in enumerate(runStatuses): print("Run #" + str(index + 1) + ": ") - print yaml.dump(runstatus.tasRun) + print(yaml.dump(runstatus.tasRun)) pass def do_select(self, data): @@ -1015,7 +1016,7 @@ def handleTransition(run_index, transition): ser.close() sys.exit(0) -# t3h urn +# the run if TASLINK_CONNECTED: while t.isAlive(): if not t.isAlive(): diff --git a/scripts/streaming/console_off.py b/scripts/streaming/console_off.py old mode 100644 new mode 100755 index 8cf47a0..09c60a2 --- a/scripts/streaming/console_off.py +++ b/scripts/streaming/console_off.py @@ -1,19 +1,20 @@ -import serial -import sys -from serial import SerialException - -baud = 2000000 - -if len(sys.argv) < 2: - sys.stderr.write('Usage: ' + sys.argv[0] + ' \n\n') - sys.exit(0) - -try: - ser = serial.Serial(sys.argv[1], baud) -except SerialException: - print ("ERROR: the specified interface (" + sys.argv[1] + ") is in use") - sys.exit(0) - -ser.write("sd1") - +#!/usr/bin/env python2 +import serial +import sys +from serial import SerialException + +baud = 2000000 + +if len(sys.argv) < 2: + sys.stderr.write('Usage: ' + sys.argv[0] + ' \n\n') + sys.exit(0) + +try: + ser = serial.Serial(sys.argv[1], baud) +except SerialException: + print ("ERROR: the specified interface (" + sys.argv[1] + ") is in use") + sys.exit(0) + +ser.write("sd1") + sys.exit(0) \ No newline at end of file diff --git a/scripts/streaming/console_on.py b/scripts/streaming/console_on.py old mode 100644 new mode 100755 index b30275a..136542a --- a/scripts/streaming/console_on.py +++ b/scripts/streaming/console_on.py @@ -1,19 +1,20 @@ -import serial -import sys -from serial import SerialException - -baud = 2000000 - -if len(sys.argv) < 2: - sys.stderr.write('Usage: ' + sys.argv[0] + ' \n\n') - sys.exit(0) - -try: - ser = serial.Serial(sys.argv[1], baud) -except SerialException: - print ("ERROR: the specified interface (" + sys.argv[1] + ") is in use") - sys.exit(0) - -ser.write("sd0") - +#!/usr/bin/env python2 +import serial +import sys +from serial import SerialException + +baud = 2000000 + +if len(sys.argv) < 2: + sys.stderr.write('Usage: ' + sys.argv[0] + ' \n\n') + sys.exit(0) + +try: + ser = serial.Serial(sys.argv[1], baud) +except SerialException: + print ("ERROR: the specified interface (" + sys.argv[1] + ") is in use") + sys.exit(0) + +ser.write("sd0") + sys.exit(0) \ No newline at end of file diff --git a/scripts/streaming/reset_console.py b/scripts/streaming/reset_console.py old mode 100644 new mode 100755 index 87807d0..7ea5bcf --- a/scripts/streaming/reset_console.py +++ b/scripts/streaming/reset_console.py @@ -1,20 +1,21 @@ -import serial -import sys -from serial import SerialException - -baud = 2000000 - -if len(sys.argv) < 2: - sys.stderr.write('Usage: ' + sys.argv[0] + ' \n\n') - sys.exit(0) - -try: - ser = serial.Serial(sys.argv[1], baud) -except SerialException: - print ("ERROR: the specified interface (" + sys.argv[1] + ") is in use") - sys.exit(0) - -ser.write("sd1") -ser.write("sd0") - +#!/usr/bin/env python2 +import serial +import sys +from serial import SerialException + +baud = 2000000 + +if len(sys.argv) < 2: + sys.stderr.write('Usage: ' + sys.argv[0] + ' \n\n') + sys.exit(0) + +try: + ser = serial.Serial(sys.argv[1], baud) +except SerialException: + print ("ERROR: the specified interface (" + sys.argv[1] + ") is in use") + sys.exit(0) + +ser.write("sd1") +ser.write("sd0") + sys.exit(0) \ No newline at end of file diff --git a/scripts/streaming/stream_NES.py b/scripts/streaming/stream_NES.py old mode 100644 new mode 100755 index 97b7989..6976851 --- a/scripts/streaming/stream_NES.py +++ b/scripts/streaming/stream_NES.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python2 import os import serial import sys From 2c606e4dfad961476071d207fb15edcef57a5088 Mon Sep 17 00:00:00 2001 From: Christopher Edwards Date: Fri, 11 May 2018 08:00:18 -0400 Subject: [PATCH 20/47] Fix finally typo in TASLink.py --- scripts/streaming/TASLink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index b8bd9c6..9174cbf 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -321,7 +321,7 @@ def setupCommunication(tasrun): else: print(command, bytestring, controllerMask) - # final, clear lanes and get ready to rock + # finally, clear lanes and get ready to rock if TASLINK_CONNECTED: ser.write("r" + chr(int(controllerMask, 2))) else: From bbc5b0cd3a962490df447cdf48754bc7ec51a094 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sun, 13 May 2018 19:35:26 +1000 Subject: [PATCH 21/47] Add Functionality to generate a Default filename for saved runs --- scripts/streaming/TASLink.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 9174cbf..dd4a71e 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -56,6 +56,7 @@ class RunStatus(object): dpcmState = None windowState = None frameCount = 0 + defaultSave = None def readint(question): num = -1 @@ -113,6 +114,7 @@ def load(filename): rs.isRunModified = False rs.dpcmState = run.dpcmFix rs.windowState = run.window + rs.defaultSave = filename # Default Save Name for loaded files is the file that was loaded runStatuses.append(rs) selected_run = len(runStatuses) - 1 @@ -923,6 +925,8 @@ def do_new(self, data): rs.isRunModified = True rs.dpcmState = dpcm_fix rs.windowState = window + # Remove Extension from filename 3 times then add ".tcf" to generate a Default Save Name + rs.defaultSave = os.path.splitext(os.path.splitext(os.path.splitext(fileName)[0])[0])[0] + ".tcf" runStatuses.append(rs) selected_run = len(runStatuses) - 1 From 44f8eae204d923c877df4490b032894ae86e2d35 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sun, 13 May 2018 19:47:09 +1000 Subject: [PATCH 22/47] Make do_save use Default Save Name if no input is given --- scripts/streaming/TASLink.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index dd4a71e..31e2d84 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -500,7 +500,9 @@ def do_save(self, data): else: runID = selected_run + 1 - filename = raw_input("Please enter filename: ") + filename = raw_input("Please enter filename [def=" + runStatuses[runID - 1].defaultSave + "]: ") + if filename == "": + filename = runStatuses[runID - 1].defaultSave with open(filename, 'w') as f: f.write(yaml.dump(runStatuses[runID - 1].tasRun)) From cea7341d8b9829f04c4b88e2786d51df5daf4e29 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sun, 13 May 2018 20:39:17 +1000 Subject: [PATCH 23/47] Added a Default options config to the top of the script Had to change how reading the inputs when creating a new run is done --- scripts/streaming/TASLink.py | 67 ++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 31e2d84..ef216e9 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -37,6 +37,10 @@ def complete_nostate(text, *ignored): ser = None supportedExtensions = ['r08','r16m'] # TODO: finish implementing this +# Default Options for new runs +# 'Controller Type', 'Overread', 'DPCM Fix', 'Window Mode', 'Dummy Frames' +DEFAULTS = ["normal", 0, "n", 0, 0] + EVERDRIVEFRAMES = 61 # Number of frames to offset dummy frames by when running on an everdrive TASLINK_CONNECTED = 1 # set to 0 for development without TASLink plugged in, set to 1 for actual testing @@ -70,6 +74,14 @@ def readint(question): break return num +def checkint(value): + try: + out = int(value) + except ValueError: + print("ERROR: Expected integer, but integer not found") + return False + return True + def readfloat(question): num = -1 while True: @@ -82,6 +94,14 @@ def readfloat(question): break return num +def checkfloat(value): + try: + out = float(value) + except ValueError: + print("ERROR: Expected float, but float not found") + return False + return True + def getNextMask(): for index,letter in enumerate(MASKS): if masksInUse[index] == 0: @@ -841,7 +861,9 @@ def do_new(self, data): # get controller type while True: breakout = True - controllerType = raw_input("What controller type does this run use ([n]ormal, [y], [m]ultitap, [f]our-score)? ") + controllerType = raw_input("What controller type does this run use ([n]ormal, [y], [m]ultitap, [f]our-score) [def=" + DEFAULTS[0] + "]? ") + if controllerType == "": + controllerType = DEFAULTS[0] if controllerType.lower() not in ["normal", "y", "multitap", "four-score", "n", "m", "f"]: print("ERROR: Invalid controller type!\n") continue @@ -864,14 +886,33 @@ def do_new(self, data): break # 8, 16, 24, or 32 bit while True: - controllerBits = readint("How many bits of data per controller (8, 16, 24, or 32)? ") + # determine default controller bit by checking input file type + ext = os.path.splitext(fileName)[1] + cbd = "" + if ext == ".r08": + cbd = 8 + if ext == ".r16m": + cbd = 16 + controllerBits = raw_input("How many bits of data per controller (8, 16, 24, or 32) [def=" + str(cbd) + "]? ") + if controllerBits == "": + controllerBits = cbd + if checkint(controllerBits): + controllerBits = int(controllerBits) + else: + continue if controllerBits != 8 and controllerBits != 16 and controllerBits != 24 and controllerBits != 32: print("ERROR: Bits must be either 8, 16, 24, or 32!\n") else: break # overread value while True: - overread = readint("Overread value (0 or 1... if unsure choose 0)? ") + overread = raw_input("Overread value (0 or 1... if unsure choose 0) [def=" + str(DEFAULTS[1]) + "]? ") + if overread == "": + overread = DEFAULTS[1] + if checkint(overread): + overread = int(overread) + else: + continue if overread != 0 and overread != 1: print("ERROR: Overread be either 0 or 1!\n") continue @@ -879,7 +920,9 @@ def do_new(self, data): break # DPCM fix while True: - dpcm_fix = raw_input("Apply DPCM fix (y/n)? ") + dpcm_fix = raw_input("Apply DPCM fix (y/n) [def=" + DEFAULTS[2] + "]? ") + if dpcm_fix == "": + dpcm_fix = DEFAULTS[2] if dpcm_fix.lower() == 'y': dpcm_fix = True break @@ -889,7 +932,13 @@ def do_new(self, data): print("ERROR: Please enter y for yes or n for no!\n") # window mode 0-15.75ms while True: - window = readfloat("Window value (0 to disable, otherwise enter time in ms. Must be multiple of 0.25ms. Must be between 0 and 15.75ms)? ") + window = raw_input("Window value (0 to disable, otherwise enter time in ms. Must be multiple of 0.25ms. Must be between 0 and 15.75ms) [def=" + str(DEFAULTS[3]) + "]? ") + if window == "": + window = DEFAULTS[3] + if checkfloat(window): + window = int(window) + else: + continue if window < 0 or window > 15.25: print("ERROR: Window out of range [0, 15.75])!\n") elif window % 0.25 != 0: @@ -899,7 +948,13 @@ def do_new(self, data): # dummy frames while True: try: - dummyFrames = readint("Number of blank input frames to prepend? ") + dummyFrames = raw_input("Number of blank input frames to prepend [def=" + str(DEFAULTS[4]) + "]? ") + if dummyFrames == "": + dummyFrames = DEFAULTS[4] + if checkint(dummyFrames): + dummyFrames = int(dummyFrames) + else: + continue if dummyFrames < 0: print("ERROR: Please enter a positive number!\n") continue From 305077f4329af262cf69a18419e299411c56ebd5 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Mon, 21 May 2018 09:41:39 +1000 Subject: [PATCH 24/47] fixed a bug with do_new seting window value just a typo i made --- scripts/streaming/TASLink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index ef216e9..5ee6887 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -936,7 +936,7 @@ def do_new(self, data): if window == "": window = DEFAULTS[3] if checkfloat(window): - window = int(window) + window = float(window) else: continue if window < 0 or window > 15.25: From e2d95b0de376e755a368c6a080f8a84f2e0abec3 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sat, 2 Jun 2018 22:39:52 +1000 Subject: [PATCH 25/47] Added functionality to catch missing settings from loaded tcf files --- scripts/streaming/TASLink.py | 105 ++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 5ee6887..a426fe6 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -121,7 +121,110 @@ def load(filename): global selected_run with open(filename, 'r') as f: - run = yaml.load(f) + loadedrun = yaml.load(f) + # check for missing values from run + try: + numControllers = loadedrun.numControllers + portsList = loadedrun.portsList + except AttributeError: + print("ERROR: Missing Controller Count or Port Assignment!") + return False + try: + controllerType = loadedrun.controllerType + controllerBits = loadedrun.controllerBits + except AttributeError: + print("ERROR: Missing Controller Type!") + return False + try: + overread = loadedrun.overread + except AttributeError: + print("WARN: Overread Missing!") + while True: + overread = raw_input("Overread value (0 or 1... if unsure choose 0) [def=" + str(DEFAULTS[1]) + "]? ") + if overread == "": + overread = DEFAULTS[1] + if checkint(overread): + overread = int(overread) + else: + continue + if overread != 0 and overread != 1: + print("ERROR: Overread be either 0 or 1!\n") + continue + else: + break + try: + window = loadedrun.window + except AttributeError: + print("WARN: Window Missing!") + while True: + window = raw_input("Window value (0 to disable, otherwise enter time in ms. Must be multiple of 0.25ms. Must be between 0 and 15.75ms) [def=" + str(DEFAULTS[3]) + "]? ") + if window == "": + window = DEFAULTS[3] + if checkfloat(window): + window = float(window) + else: + continue + if window < 0 or window > 15.25: + print("ERROR: Window out of range [0, 15.75])!\n") + elif window % 0.25 != 0: + print("ERROR: Window is not a multiple of 0.25!\n") + else: + break + try: + inputFile = loadedrun.inputFile + if not os.path.isfile(inputFile): + print("ERROR: Input File is Missing!") + return False + except AttributeError: + print("ERROR: No Input File") # Uhh ??? + return False + try: + dummyFrames = loadedrun.dummyFrames + except AttributeError: + print("WARN: Dummy Frame Count Missing!") + while True: + try: + dummyFrames = raw_input("Number of blank input frames to prepend [def=" + str(DEFAULTS[4]) + "]? ") + if dummyFrames == "": + dummyFrames = DEFAULTS[4] + if checkint(dummyFrames): + dummyFrames = int(dummyFrames) + else: + continue + if dummyFrames < 0: + print("ERROR: Please enter a positive number!\n") + continue + else: + break + except ValueError: + print("ERROR: Please enter integers!\n") + try: + dpcmFix = loadedrun.dpcmFix + except AttributeError: + print("WARN: DCPM Fix Missing!") + while True: + dpcmFix = raw_input("Apply DPCM fix (y/n) [def=" + DEFAULTS[2] + "]? ") + if dpcmFix == "": + dpcmFix = DEFAULTS[2] + if dpcmFix.lower() == 'y': + dpcmFix = True + break + elif dpcmFix.lower() == 'n': + dpcmFix = False + break + print("ERROR: Please enter y for yes or n for no!\n") + try: + transitions = loadedrun.transitions + except AttributeError: + print("WARN: Transitions Missing!") + transitions = [] + try: + isEverdrive = loadedrun.isEverdrive + except AttributeError: + print("WARN: Is Everdrive Run Missing!") + isEverdrive = False + + run = TASRun(numControllers, portsList, controllerType, controllerBits, overread, window, inputFile, dummyFrames, dpcmFix, isEverdrive) # check for port conflicts if not all(isConsolePortAvailable(port, run.controllerType) for port in run.portsList): print("ERROR: Requested ports already in use!") From b8d300c7965e2f4e48a348c8f4c2f0a0c14d8ce9 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Mon, 4 Jun 2018 07:45:10 +1000 Subject: [PATCH 26/47] fix do_restart for dwango holding reset for a full second resets sd2snes to menu --- scripts/streaming/TASLink.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index a426fe6..8af1e96 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -647,9 +647,9 @@ def do_on(self, data): def do_restart(self, data): """Turns the SNES console off, restarts the current run, and turns the SNES console on""" self.do_off(data) - time.sleep(1) + time.sleep(0.2) self.do_reset(data) - time.sleep(1) + time.sleep(0.2) self.do_on(data) print("The restart process is complete!") From a131620e657e03ed0bee38061416ccac1a13dcee Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Wed, 6 Jun 2018 08:08:18 +1000 Subject: [PATCH 27/47] added second restart command to do a hard reset of the console --- scripts/streaming/TASLink.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 8af1e96..86cc1c3 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -653,6 +653,15 @@ def do_restart(self, data): self.do_on(data) print("The restart process is complete!") + def do_hard_restart(self, data): + """Turns the SNES console off, restarts the current run, and turns the SNES console on""" + self.do_off(data) + time.sleep(1) + self.do_reset(data) + time.sleep(1) + self.do_on(data) + print("The restart process is complete!") + def do_modify_frames(self, data): """Modify the initial blank input frames""" # print options From 9784df04d2e6475b98f061d76dbe408c7c6f6bff Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sat, 21 Jul 2018 20:48:00 +1000 Subject: [PATCH 28/47] Add support for triggering a reset of the Console midrun also fixed bug in loading runs that was causing transitions to be discarded on load --- scripts/streaming/TASLink.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 86cc1c3..64a7eac 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -223,8 +223,9 @@ def load(filename): except AttributeError: print("WARN: Is Everdrive Run Missing!") isEverdrive = False - + run = TASRun(numControllers, portsList, controllerType, controllerBits, overread, window, inputFile, dummyFrames, dpcmFix, isEverdrive) + run.transitions = transitions # check for port conflicts if not all(isConsolePortAvailable(port, run.controllerType) for port in run.portsList): print("ERROR: Requested ports already in use!") @@ -264,6 +265,7 @@ class Transition(object): frameno = None window = None dpcmFix = None + trigReset = None class TASRun(object): def __init__(self, num_controllers, ports_list, controller_type, controller_bits, ovr, wndw, file_name, dummy_frames, dpcm_fix, is_everdrive): @@ -919,13 +921,23 @@ def do_add_transition(self, data): print("ERROR: Window is not a multiple of 0.25!\n") else: break + # trigger reset + while True: + trigReset = raw_input("Reset Console (y/n)? ") + if trigReset.lower() == 'y': + trigReset = True + break + elif trigReset.lower() == 'n': + trigReset = False + break t = Transition() t.dpcmFix = dpcm_fix t.frameno = frameNum t.window = window + t.trigReset = trigReset runStatuses[selected_run].tasRun.addTransition(t) runStatuses[selected_run].isRunModified = True - + def do_toggle_everdrive(self, data): if selected_run == -1: print("ERROR: No run is selected!\n") @@ -1148,6 +1160,14 @@ def handleTransition(run_index, transition): bytestring = "".join(byte) # turn back into string ser.write(command + chr(int(bytestring, 2)) + chr(int(controllerMask, 2))) runStatuses[run_index].windowState = transition.window + try: + if transition.trigReset: + if TASLINK_CONNECTED: + ser.write("sd1") + time.sleep(0.2) + ser.write("sd0") + except AttributeError: + print("WARN: HANDLE MISSING RESET FLAG FOR TRANSITION") # ----- MAIN EXECUTION BEGINS HERE ----- From 8aa2ced8dd0f1a93be72d96c436ea2820a8ce9f3 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sun, 22 Jul 2018 19:21:17 +1000 Subject: [PATCH 29/47] Add function to insert blank frames at arbitrary points in a run --- scripts/streaming/TASLink.py | 58 +++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 64a7eac..d312b5f 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -218,6 +218,11 @@ def load(filename): except AttributeError: print("WARN: Transitions Missing!") transitions = [] + try: + blankFrames = loadedrun.blankFrames + except AttributeError: + print("WARN: Blank Frames Missing!") + blankFrames = [] try: isEverdrive = loadedrun.isEverdrive except AttributeError: @@ -226,6 +231,7 @@ def load(filename): run = TASRun(numControllers, portsList, controllerType, controllerBits, overread, window, inputFile, dummyFrames, dpcmFix, isEverdrive) run.transitions = transitions + run.blankFrames = blankFrames # check for port conflicts if not all(isConsolePortAvailable(port, run.controllerType) for port in run.portsList): print("ERROR: Requested ports already in use!") @@ -246,7 +252,8 @@ def load(filename): # add everdrive header if needed if run.isEverdrive == True: add_everdrive_header(runStatuses[selected_run].tasRun, selected_run) - + if run.blankFrames != []: + load_blank_frames(selected_run) send_frames(selected_run, prebuffer) print("Run has been successfully loaded!") @@ -280,6 +287,7 @@ def __init__(self, num_controllers, ports_list, controller_type, controller_bits self.dpcmFix = dpcm_fix self.transitions = [] self.isEverdrive = is_everdrive + self.blankFrames = [] self.fileExtension = file_name.split(".")[-1].strip() # pythonic last element of a list/string/array @@ -535,6 +543,29 @@ def add_everdrive_header(tasRun, runid): newbuffer.insert(0, blankframe) # add x number of blank frames to start of input buffer runStatuses[runid].inputBuffer = newbuffer +def add_blank_frame(frameNum, runid): + run = runStatuses[runid].tasRun + working_string = runStatuses[runid].customCommand + max = int(run.controllerBits / 8) * run.numControllers # bytes * number of controllers + # next we take controller type into account + if run.controllerType == CONTROLLER_Y or run.controllerType == CONTROLLER_FOUR_SCORE: + max *= 2 + elif run.controllerType == CONTROLLER_MULTITAP: + max *= 4 + for bytes in range(max): + working_string += chr(0xFF) + runStatuses[runid].inputBuffer.insert(frameNum, working_string) + +def load_blank_frames(runid): + run = runStatuses[runid].tasRun + for x in range(len(run.blankFrames)): + frame = run.blankFrames[x] + if run.isEverdrive == True: + realframe = run.dummyFrames + EVERDRIVEFRAMES + frame + else: + realframe = run.dummyFrames + frame + add_blank_frame(realframe,runid) + # return false exits the function # return true exits the whole CLI class CLI(cmd.Cmd): @@ -716,6 +747,28 @@ def do_modify_frames(self, data): print("Run has been updated. Remember to save if you want this change to be permanent!") + def do_add_blank_frame(self, data): + if selected_run == -1: + print("ERROR: No run is selected!\n") + return + print("Note this is automatically offset for dummy frame count and headers, it is not offset for other blank frames") + print("This Cannot be undone without reloading run, 0 to cancel") + while True: + try: + frameNum = readint("After what frame will this blank frame be inserted? ") + if frameNum < 0: + print("ERROR: Please enter a positive number!\n") + continue + elif frameNum == 0: + return + else: + break + except ValueError: + print("ERROR: Please enter an integer!\n") + runStatuses[selected_run].tasRun.blankFrames.append(frameNum) + runStatuses[selected_run].isRunModified = True + add_blank_frame(frameNum, selected_run) + def do_reset(self, data): """Reset an active run back to frame 0""" # print options @@ -1234,6 +1287,9 @@ def handleTransition(run_index, transition): for transition in run.transitions: if 0 <= (transition.frameno+run.dummyFrames+prebuffer) - runstatus.frameCount < latches: # we're about to pass the transition frame handleTransition(run_index,transition) + for addedframe in run.blankFrames: + if 0 <= (addedframe+run.dummyFrames+prebuffer) - runstatus.frameCount < latches: # we're about to pass the addedframe frame + print("Passing Added Blank frame at: " + str(addedframe)) if latches > 0: send_frames(run_index, latches) From 6598e1e4024d19ad33e6c2f679dbc021fe0ec2e8 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sun, 22 Jul 2018 21:28:45 +1000 Subject: [PATCH 30/47] Add a note in do_add_transitions about resets --- scripts/streaming/TASLink.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 64a7eac..e6c4c7c 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -891,6 +891,7 @@ def do_add_transition(self, data): print("ERROR: No run is selected!\n") return # transitions + print("NOTE: Reset Transitions need to be triggered 1 frame early") while True: try: frameNum = readint("At what frame will this transition occur? ") From 8d5186af564d612d1f97b6e521110184b979e17c Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sun, 26 Aug 2018 08:54:51 +1000 Subject: [PATCH 31/47] Implement support for a SD2SNES header also fixed issue with adding blank frames not accounting for headers --- scripts/streaming/TASLink.py | 115 ++++++++++++++++++++++++++++------- 1 file changed, 92 insertions(+), 23 deletions(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 182daf6..c22836f 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -42,6 +42,7 @@ def complete_nostate(text, *ignored): DEFAULTS = ["normal", 0, "n", 0, 0] EVERDRIVEFRAMES = 61 # Number of frames to offset dummy frames by when running on an everdrive +SD2SNESFRAMES = 130 # Number of frames to offset dummy frames by when running on a SD2SNES TASLINK_CONNECTED = 1 # set to 0 for development without TASLink plugged in, set to 1 for actual testing @@ -228,14 +229,24 @@ def load(filename): except AttributeError: print("WARN: Is Everdrive Run Missing!") isEverdrive = False + try: + isSD2SNES = loadedrun.isSD2SNES + except AttributeError: + print("WARN: Is SD2SNES Run Missing!") + isSD2SNES = False - run = TASRun(numControllers, portsList, controllerType, controllerBits, overread, window, inputFile, dummyFrames, dpcmFix, isEverdrive) + run = TASRun(numControllers, portsList, controllerType, controllerBits, overread, window, inputFile, dummyFrames, dpcmFix) + run.isEverdrive = isEverdrive + run.isSD2SNES = isSD2SNES run.transitions = transitions run.blankFrames = blankFrames # check for port conflicts if not all(isConsolePortAvailable(port, run.controllerType) for port in run.portsList): print("ERROR: Requested ports already in use!") return False + if run.isEverdrive == run.isSD2SNES == True: + print("ERROR: Run cannot be on both Everdrive and SD2SNES") + return False rs = RunStatus() rs.customCommand = setupCommunication(run) @@ -251,7 +262,10 @@ def load(filename): # add everdrive header if needed if run.isEverdrive == True: - add_everdrive_header(runStatuses[selected_run].tasRun, selected_run) + add_everdrive_header(selected_run) + # add SD2SNES header if needed + if run.isSD2SNES == True: + add_sd2snes_header(selected_run) if run.blankFrames != []: load_blank_frames(selected_run) send_frames(selected_run, prebuffer) @@ -275,7 +289,7 @@ class Transition(object): trigReset = None class TASRun(object): - def __init__(self, num_controllers, ports_list, controller_type, controller_bits, ovr, wndw, file_name, dummy_frames, dpcm_fix, is_everdrive): + def __init__(self, num_controllers, ports_list, controller_type, controller_bits, ovr, wndw, file_name, dummy_frames, dpcm_fix): self.numControllers = num_controllers self.portsList = ports_list self.controllerType = controller_type @@ -286,7 +300,8 @@ def __init__(self, num_controllers, ports_list, controller_type, controller_bits self.dummyFrames = dummy_frames self.dpcmFix = dpcm_fix self.transitions = [] - self.isEverdrive = is_everdrive + self.isEverdrive = False + self.isSD2SNES = False self.blankFrames = [] self.fileExtension = file_name.split(".")[-1].strip() # pythonic last element of a list/string/array @@ -511,14 +526,15 @@ def releaseConsolePort(port, type): consoleLanes[lanes[port][0]] = 0 consoleLanes[lanes[port][1]] = 0 -def remove_everdrive_header(tasRun, runid): +def remove_everdrive_header(runid): print("Removing Everdrive Header!\n") newbuffer = runStatuses[runid].inputBuffer oldbuffer = newbuffer newbuffer = oldbuffer[EVERDRIVEFRAMES:] runStatuses[runid].inputBuffer = newbuffer -def add_everdrive_header(tasRun, runid): +def add_everdrive_header(runid): + tasRun = runStatuses[runid].tasRun print("Adding Everdrive Header!\n") newbuffer = runStatuses[runid].inputBuffer blankframe = runStatuses[runid].customCommand @@ -543,6 +559,45 @@ def add_everdrive_header(tasRun, runid): newbuffer.insert(0, blankframe) # add x number of blank frames to start of input buffer runStatuses[runid].inputBuffer = newbuffer +def remove_sd2snes_header(runid): + print("Removing SD2SNES Header!\n") + newbuffer = runStatuses[runid].inputBuffer + oldbuffer = newbuffer + newbuffer = oldbuffer[130:] + runStatuses[runid].inputBuffer = newbuffer + +def add_sd2snes_header(runid): + tasRun = runStatuses[runid].tasRun + print("Adding SD2SNES Header!\n") + newbuffer = runStatuses[runid].inputBuffer + max = int(tasRun.controllerBits / 8) * tasRun.numControllers # bytes * number of controllers + blankframe = runStatuses[runid].customCommand + if tasRun.controllerType == CONTROLLER_Y or tasRun.controllerType == CONTROLLER_FOUR_SCORE: + max *= 2 + elif tasRun.controllerType == CONTROLLER_MULTITAP: + max *= 4 + for bytes in range(max): + blankframe += chr(0xFF) + startframe = runStatuses[runid].customCommand + for bytes in range(max): + if bytes == 0: + startframe += chr(0xEF) # press start on controller 1 + else: + startframe += chr(0xFF) + aframe = runStatuses[runid].customCommand + for bytes in range(max): + if bytes == 1: + aframe += chr(0x7F) # press A on controller 1 + else: + aframe += chr(0xFF) + newbuffer.insert(0, aframe) # add a frame pressing A to start of input buffer + for frame in range(0, 9): + newbuffer.insert(0, blankframe) # add 10 blank frames to start of input buffer + newbuffer.insert(0, startframe) # add a frame pressing start to start of input buffer + for frame in range(0, 119): + newbuffer.insert(0, blankframe) # add 120 blank frames to start of input buffer + runStatuses[runid].inputBuffer = newbuffer + def add_blank_frame(frameNum, runid): run = runStatuses[runid].tasRun working_string = runStatuses[runid].customCommand @@ -562,6 +617,8 @@ def load_blank_frames(runid): frame = run.blankFrames[x] if run.isEverdrive == True: realframe = run.dummyFrames + EVERDRIVEFRAMES + frame + elif run.isSD2SNES == True: + realframe = run.dummyFrames + SD2SNESFRAMES + frame else: realframe = run.dummyFrames + frame add_blank_frame(realframe,runid) @@ -735,11 +792,15 @@ def do_modify_frames(self, data): for count in range(difference): if run.isEverdrive: runStatuses[index].inputBuffer.insert(EVERDRIVEFRAMES, working_string) + elif run.isSD2SNES: + runStatuses[index].inputBuffer.insert(SD2SNESFRAMES, working_string) else: runStatuses[index].inputBuffer.insert(0, working_string) # add the correct number of blank input frames elif difference < 0: # remove input frames if run.isEverdrive: runStatuses[index].inputBuffer = runStatuses[index].inputBuffer[0:EVERDRIVEFRAMES]+runStatuses[index].inputBuffer[EVERDRIVEFRAMES-difference:] + elif run.isSD2SNES: + runStatuses[index].inputBuffer = runStatuses[index].inputBuffer[0:SD2SNESFRAMES]+runStatuses[index].inputBuffer[SD2SNESFRAMES-difference:] else: runStatuses[index].inputBuffer = runStatuses[index].inputBuffer[-difference:] @@ -767,6 +828,10 @@ def do_add_blank_frame(self, data): print("ERROR: Please enter an integer!\n") runStatuses[selected_run].tasRun.blankFrames.append(frameNum) runStatuses[selected_run].isRunModified = True + if runStatuses[selected_run].tasRun.isEverdrive: + frameNum = frameNum + EVERDRIVEFRAMES + if runStatuses[selected_run].tasRun.isSD2SNES: + frameNum = frameNum + SD2SNESFRAMES add_blank_frame(frameNum, selected_run) def do_reset(self, data): @@ -996,13 +1061,30 @@ def do_toggle_everdrive(self, data): if selected_run == -1: print("ERROR: No run is selected!\n") return + if runStatuses[selected_run].tasRun.isSD2SNES == True: + print("ERROR: Run Cannot be on both Everdrive and SD2SNES!\n") + return if runStatuses[selected_run].tasRun.isEverdrive == True: - remove_everdrive_header(runStatuses[selected_run].tasRun, selected_run) + remove_everdrive_header(selected_run) runStatuses[selected_run].tasRun.isEverdrive = False elif runStatuses[selected_run].tasRun.isEverdrive == False: - add_everdrive_header(runStatuses[selected_run].tasRun, selected_run) + add_everdrive_header(selected_run) runStatuses[selected_run].tasRun.isEverdrive = True + def do_toggle_sd2snes(self, data): + if selected_run == -1: + print("ERROR: No run is selected!\n") + return + if runStatuses[selected_run].tasRun.isEverdrive == True: + print("ERROR: Run Cannot be on both Everdrive and SD2SNES!\n") + return + if runStatuses[selected_run].tasRun.isSD2SNES == True: + remove_sd2snes_header(selected_run) + runStatuses[selected_run].tasRun.isSD2SNES = False + elif runStatuses[selected_run].tasRun.isSD2SNES == False: + add_sd2snes_header(selected_run) + runStatuses[selected_run].tasRun.isSD2SNES = True + def do_new(self, data): """Create a new run with parameters specified in the terminal""" global selected_run @@ -1140,18 +1222,9 @@ def do_new(self, data): break except ValueError: print("ERROR: Please enter integers!\n") - # is the run on a everdrive - while True: - is_everdrive = raw_input("Add a header for playback on an Everdrive (y/n)? ") - if is_everdrive.lower() == 'y': - is_everdrive = True - break - elif is_everdrive.lower() == 'n': - is_everdrive = False - break - print("ERROR: Please enter y for yes or n for no!\n") + # create TASRun object and assign it to our global, defined above - tasrun = TASRun(numControllers, portsList, controllerType, controllerBits, overread, window, fileName, dummyFrames, dpcm_fix, is_everdrive) + tasrun = TASRun(numControllers, portsList, controllerType, controllerBits, overread, window, fileName, dummyFrames, dpcm_fix) rs = RunStatus() rs.customCommand = setupCommunication(tasrun) @@ -1166,10 +1239,6 @@ def do_new(self, data): selected_run = len(runStatuses) - 1 - # add everdrive header if needed - if tasrun.isEverdrive == True: - add_everdrive_header(runStatuses[selected_run].tasRun, selected_run) - send_frames(selected_run, prebuffer) print("Run is ready to go!") From 140b50447530cbb97dd195fc64a7ea9b8d6d0868 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Fri, 31 Aug 2018 17:05:28 +1000 Subject: [PATCH 32/47] Add a single blank frame to the end of the input buffer this fixes an issue causing runs to desync if there is an input on the last frame it gets held --- scripts/streaming/TASLink.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index c22836f..11059e3 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -392,6 +392,12 @@ def getInputBuffer(self, customCommand): buffer[frameno+self.dummyFrames] = command_string frameno += 1 + # add empty frame to end of file to prevent desyncs on last frame + working_string = customCommand + for bytes in range(bytesPerCommand): + working_string += chr(0xFF) + buffer.append(working_string) + return buffer From 8d014eca7f328e9c4c7c4341dd506ac96296cbdf Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sun, 2 Sep 2018 15:42:48 +1000 Subject: [PATCH 33/47] Add command to reload saved run from file see Ownasaurus/TASLink#14 --- scripts/streaming/TASLink.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index c22836f..581d9f5 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -62,6 +62,7 @@ class RunStatus(object): windowState = None frameCount = 0 defaultSave = None + isLoadedRun = False def readint(question): num = -1 @@ -256,6 +257,7 @@ def load(filename): rs.dpcmState = run.dpcmFix rs.windowState = run.window rs.defaultSave = filename # Default Save Name for loaded files is the file that was loaded + rs.isLoadedRun = True runStatuses.append(rs) selected_run = len(runStatuses) - 1 @@ -901,6 +903,18 @@ def do_reset(self, data): handleTransition(index, t) print("Reset complete!") + def do_reload(self, data): + if selected_run == -1: + print("ERROR: No run is selected!\n") + return + if not runStatuses[selected_run].isLoadedRun: + print("ERROR: Run wasn't loaded from file!\n") + return + fileToLoad = runStatuses[selected_run].defaultSave + self.onecmd("remove") + self.onecmd("load " + fileToLoad) + return False + def do_remove(self, data): """Remove one of the current runs.""" global selected_run From cc4bb58eb8353e8c6a012cdaae1165b4d2e82d48 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sun, 2 Sep 2018 16:17:34 +1000 Subject: [PATCH 34/47] Add help documentation to commands that dont have any --- scripts/streaming/TASLink.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index c22836f..c39ec5f 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -809,6 +809,7 @@ def do_modify_frames(self, data): print("Run has been updated. Remember to save if you want this change to be permanent!") def do_add_blank_frame(self, data): + """Add a blank frame at a particular offset in the current run""" if selected_run == -1: print("ERROR: No run is selected!\n") return @@ -1004,7 +1005,7 @@ def do_select(self, data): selected_run = runID - 1 def do_add_transition(self, data): - """Adds a transition of communication settings at a particular frame""" + """Adds a transition of communication settings at a particular frame or Adds a Console Reset at a particular frame""" if selected_run == -1: print("ERROR: No run is selected!\n") return @@ -1058,6 +1059,8 @@ def do_add_transition(self, data): runStatuses[selected_run].isRunModified = True def do_toggle_everdrive(self, data): + """Adds a header of input to the start of the run to boot + the most recent rom on the NES Everdrive Cart""" if selected_run == -1: print("ERROR: No run is selected!\n") return @@ -1072,6 +1075,8 @@ def do_toggle_everdrive(self, data): runStatuses[selected_run].tasRun.isEverdrive = True def do_toggle_sd2snes(self, data): + """Adds a header of input to the start of the run to boot + the most recent rom on the SNES SD2SNES Cart""" if selected_run == -1: print("ERROR: No run is selected!\n") return From 759ed40ad187a67782c2095f4a8d98c714c127b7 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sun, 2 Sep 2018 16:21:43 +1000 Subject: [PATCH 35/47] Add help messgae to the reload command --- scripts/streaming/TASLink.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index 581d9f5..1d47d67 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -904,6 +904,7 @@ def do_reset(self, data): print("Reset complete!") def do_reload(self, data): + """Reload selected run from file, need to have loaded from a file first""" if selected_run == -1: print("ERROR: No run is selected!\n") return From f547659e9dfeceb12f69717a0e09130bdb260a23 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sun, 2 Sep 2018 17:44:48 +1000 Subject: [PATCH 36/47] Add command to run commands from a file Basic testing indicates it works fairly well I was able to have it load a run List the run and remove it again without issues It can call other commands as well but it cant provide input if they ask for input it will wait for the user to supply the input --- scripts/streaming/TASLink.py | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/scripts/streaming/TASLink.py b/scripts/streaming/TASLink.py index c22836f..b7851f0 100755 --- a/scripts/streaming/TASLink.py +++ b/scripts/streaming/TASLink.py @@ -724,6 +724,49 @@ def do_save(self, data): print("Save complete!") + def do_execute(self, data): + """execute a sequence of commands from a file""" + if data == "": + while True: + file = raw_input("File (Blank to cancel): ") + if file == "": + break + elif os.path.exists(file): + break + else: + print("Error: File does not exist!\n") + continue + else: + file = data + if file == "": + return False + elif not os.path.exists(file): + print("Error: File does not exist!\n") + return False + scriptList = [] + with open(file, 'r') as f: + for command in f: + command = command[:-1] + scriptList.append(command) + while True: + a = raw_input("[s]how, [r]un, [e]xit: ") + a = a.lower() + if a == "": + continue + elif a == "s": + print(scriptList) + continue + elif a == "e": + return False + elif a == "r": + print("Executing: " + file) + break + else: + continue + for command in scriptList: + self.onecmd(command) + return False + def do_off(self, data): """Turns off the SNES via reset pin, if connected""" ser.write("sd1") From 138de41e3fdfb45cbc36274007e5ef68845cb355 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sun, 16 Sep 2018 21:03:59 +1000 Subject: [PATCH 37/47] Starting work on python3 working bit by bit implements get_input() to replace readint,checkint,readfloat,checkfloat --- scripts/streaming/TASLink3.py | 101 ++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100755 scripts/streaming/TASLink3.py diff --git a/scripts/streaming/TASLink3.py b/scripts/streaming/TASLink3.py new file mode 100755 index 0000000..ff59dd5 --- /dev/null +++ b/scripts/streaming/TASLink3.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# ImportModules +import os +import sys +import cmd +import threading +import yaml +import gc +import time +import rlcompleter +import readline +import glob +import signal # Handle InteruptSignals +import serial +from serial import SerialException + +# GarbageCollection +gc.disable() # for performance reasons + +# Readline Config +def complete(text, state): + return (glob.glob(text + '*') + [None])[state] +def complete_nostate(text, *ignored): + return glob.glob(text + '*') + [None] +readline.set_completer_delims(' \t\n') +readline.parse_and_bind("tab: complete") +readline.set_completer(complete) + +# Set Constants +lanes = [[-1], [1, 2, 5, 6], [3, 4, 7, 8], [5, 6], [7, 8]] +MASKS = 'ABCD' +CONTROLLER_NORMAL = 0 # 1 controller +CONTROLLER_Y = 1 #: y-cable [like half a multitap] +CONTROLLER_MULTITAP = 2 #: multitap (Ports 1 and 2 only) [snes only] +CONTROLLER_FOUR_SCORE = 3 #: four-score [nes-only peripheral that we don't do anything with] +baud = 2000000 +prebuffer = 60 +ser = None +EVERDRIVEFRAMES = 61 # Number of frames to offset dummy frames by when running on an everdrive +SD2SNESFRAMES = 130 # Number of frames to offset dummy frames by when running on a SD2SNES +# important global variables to keep track of +consolePorts = [2, 0, 0, 0, 0] # 1 when in use, 0 when available. 2 is used to waste cell 0 +consoleLanes = [2, 0, 0, 0, 0, 0, 0, 0, 0] # 1 when in use, 0 when available. 2 is used to waste cell 0 +masksInUse = [0, 0, 0, 0] +runStatuses = [] # list of currently active runs and their statuses +selected_run = -1 +TASLINK_CONNECTED = 1 # set to 0 for development without TASLink plugged in, set to 1 for actual testing +supportedExtensions = ['r08','r16m'] # TODO: finish implementing this +# Default Options for new runs +DEFAULTS = {'contype': "normal", + 'overread': 0, + 'dpcmfix': "n", + 'windowmode': 0, + 'dummyframes': 0} + +class RunStatus(object): + tasRun = None + inputBuffer = None + customCommand = None + isRunModified = None + dpcmState = None + windowState = None + frameCount = 0 + defaultSave = None + isLoadedRun = False + +# types implemented int,str,float,bool +def get_input(type, prompt, default=''): + while True: + try: + data = input(prompt) + if data == default == None: + print('No Default Configured') + continue + if data == '' and default != '': + return default + if type == 'int': + try: + return int(data) + except ValueError: + print('ERROR: Expected integer') + if type == 'float': + try: + return float(data) + except ValueError: + print('ERROR: Expected float') + if type == 'str': + try: + return str(data) + except ValueError: + print('ERROR: Expected string') + if type == 'bool': + if data.lower() in (1,'true','y','yes') + return True + elif data.lower() in (0,'false','n','no') + except EOFError: + print('EOF') + return None + + ### MAIN LOOP ### + signal.signal(signal.SIGINT,signal.SIG_IGN) # Catch Ctrl+C from interupting the mainloop From b48c670af47eb037383bb1b375ba51d6b5b43e02 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Sun, 16 Sep 2018 22:33:02 +1000 Subject: [PATCH 38/47] implemented load() --- scripts/streaming/TASLink3.py | 80 ++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/scripts/streaming/TASLink3.py b/scripts/streaming/TASLink3.py index ff59dd5..2dbda08 100755 --- a/scripts/streaming/TASLink3.py +++ b/scripts/streaming/TASLink3.py @@ -65,15 +65,28 @@ class RunStatus(object): isLoadedRun = False # types implemented int,str,float,bool -def get_input(type, prompt, default=''): +# constraints only work on int and float +def get_input(type, prompt, default='', constraints={}): while True: try: data = input(prompt) if data == default == None: - print('No Default Configured') + print('ERROR: No Default Configured') continue if data == '' and default != '': return default + if 'min' in constraints: + if data < constraints['min']: + print('ERROR: Input less than Minimium of ' + str(constraints['min'])) + continue + if 'max' in constraints: + if data > constraints['max']: + print('ERROR: Input greater than maximum of ' + str(constraints['max'])) + continue + if 'interval' in constraints: + if data % constraints['interval'] != 0: + print('ERROR: Input does not match interval of ' + str(constraints['max'])) + continue if type == 'int': try: return int(data) @@ -90,12 +103,67 @@ def get_input(type, prompt, default=''): except ValueError: print('ERROR: Expected string') if type == 'bool': - if data.lower() in (1,'true','y','yes') + if data.lower() in (1,'true','y','yes'): return True - elif data.lower() in (0,'false','n','no') + elif data.lower() in (0,'false','n','no'): + return False + else: + print('ERROR: Expected boolean') except EOFError: print('EOF') return None - ### MAIN LOOP ### - signal.signal(signal.SIGINT,signal.SIG_IGN) # Catch Ctrl+C from interupting the mainloop +def getNextMask(): + for index,letter in enumerate(MASKS): + if masksInUse[index] == 0: + masksInUse[index] = 1 + return letter + return 'Z' + +def freeMask(letter): + val = ord(letter) + if not (65 <= val <= 68): + return False + masksInUse[val-65] = 0 + return True + +def load(filename, batch=False): + global selected_run + with open(filename, 'r') as f: + loadedrun = yaml.load(f) + # check for missing values from run + missingValues = 0 + try: + numControllers = loadedrun.numControllers + portsList = loadedrun.portsList + controllerType = loadedrun.controllerType + controllerBits = loadedrun.controllerBits + inputFile = loadedrun.inputFile + except AttributeError as error: + print("ERROR: Missing Attribute from loaded run!") + print(error) + return False + try: + overread = loadedrun.overread + except AttributeError: + missingValues += 1 + overread = get_input(type = 'int', + prompt = 'Overread value (0 or 1) [def=' + str(DEFAULTS['overread']) + ']? ', + default = DEFAULTS['overread'], + constraints = {'min': 0, 'max': 1}) + if overread == None: + return False + try: + window = loadedrun.window + except AttributeError as error: + missingValues += 1 + window = get_input(type = 'float', + prompt = 'Window value (0 to disable, otherwise enter time in ms. Must be multiple of 0.25ms. Must be between 0 and 15.75ms) [def=' + str(DEFAULTS['windowmode']) + ']? ', + default = DEFAULTS['windowmode'], + constraints = {'min': 0, 'max': 15.75, 'interval': 0.25}) + + + + +### MAIN LOOP ### +# signal.signal(signal.SIGINT,signal.SIG_IGN) # Catch Ctrl+C from interupting the mainloop From 40a8b9eb7800a1ecda5f0eade379a3158acc47fd Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Mon, 17 Sep 2018 01:06:41 +1000 Subject: [PATCH 39/47] Copy most of the code over and start making changes needed for python3 --- scripts/streaming/TASLink3.py | 1249 ++++++++++++++++++++++++++++++++- 1 file changed, 1227 insertions(+), 22 deletions(-) diff --git a/scripts/streaming/TASLink3.py b/scripts/streaming/TASLink3.py index 2dbda08..caaf557 100755 --- a/scripts/streaming/TASLink3.py +++ b/scripts/streaming/TASLink3.py @@ -23,7 +23,7 @@ def complete(text, state): def complete_nostate(text, *ignored): return glob.glob(text + '*') + [None] readline.set_completer_delims(' \t\n') -readline.parse_and_bind("tab: complete") +readline.parse_and_bind('tab: complete') readline.set_completer(complete) # Set Constants @@ -46,24 +46,14 @@ def complete_nostate(text, *ignored): selected_run = -1 TASLINK_CONNECTED = 1 # set to 0 for development without TASLink plugged in, set to 1 for actual testing supportedExtensions = ['r08','r16m'] # TODO: finish implementing this + # Default Options for new runs -DEFAULTS = {'contype': "normal", +DEFAULTS = {'contype': 'normal', 'overread': 0, - 'dpcmfix': "n", + 'dpcmfix': 'False', 'windowmode': 0, 'dummyframes': 0} -class RunStatus(object): - tasRun = None - inputBuffer = None - customCommand = None - isRunModified = None - dpcmState = None - windowState = None - frameCount = 0 - defaultSave = None - isLoadedRun = False - # types implemented int,str,float,bool # constraints only work on int and float def get_input(type, prompt, default='', constraints={}): @@ -118,7 +108,7 @@ def getNextMask(): if masksInUse[index] == 0: masksInUse[index] = 1 return letter - return 'Z' + return b'Z' def freeMask(letter): val = ord(letter) @@ -140,7 +130,7 @@ def load(filename, batch=False): controllerBits = loadedrun.controllerBits inputFile = loadedrun.inputFile except AttributeError as error: - print("ERROR: Missing Attribute from loaded run!") + print('ERROR: Missing Attribute from loaded run!') print(error) return False try: @@ -155,15 +145,1230 @@ def load(filename, batch=False): return False try: window = loadedrun.window - except AttributeError as error: + except AttributeError: + missingValues += 1 + window = get_input(type = 'float', + prompt = 'Window value (0 to disable, otherwise enter time in ms. Must be multiple of 0.25ms. Must be between 0 and 15.75ms) [def=' + str(DEFAULTS['windowmode']) + ']? ', + default = DEFAULTS['windowmode'], + constraints = {'min': 0, 'max': 15.75, 'interval': 0.25}) + if window == None: + return False + if not os.path.isfile(inputFile): + print('ERROR: Input File is Missing!') + return False + try: + dummyFrames = loadedrun.dummyFrames + except AttributeError: + missingValues += 1 + dummyFrames = get_input(type = 'int', + prompt = 'Number of blank input frames to prepend [def=' + str(DEFAULTS['dummyframes']) + ']? ', + default = DEFAULTS['dummyframes'], + constraints = {'min': 0}) + if dummyFrames == None: + return False + try: + dpcmFix = loadedrun.dpcmFix + except AttributeError: + missingValues += 1 + dpcmFix = get_input(type = 'bool', + prompt = 'Apply DPCM fix (y/n) [def=' + str(DEFAULTS['dpcmfix']) + ']? ', + default = DEFAULTS['dpcmfix']) + if dpcmFix == None: + return False + try: + transitions = loadedrun.transitions + except AttributeError: + missingValues += 1 + transitions = [] + try: + blankFrames = loadedrun.blankFrames + except AttributeError: + missingValues += 1 + blankFrames = [] + try: + isEverdrive = loadedrun.isEverdrive + except AttributeError: missingValues += 1 + isEverdrive = False + try: + isSD2SNES = loadedrun.isSD2SNES + except AttributeError: + missingValues += 1 + isSD2SNES = False + + # Create New TASRun Object + run = TASRun(numControllers, portsList, controllerType, controllerBits, overread, window, inputFile, dummyFrames, dpcmFix) + run.isEverdrive = isEverdrive + run.isSD2SNES = isSD2SNES + run.transitions = transitions + run.blankFrames = blankFrames + # check for port conflicts + if not all(isConsolePortAvailable(port, run.controllerType) for port in run.portsList): + print('ERROR: Requested ports already in use!') + return False + if run.isEverdrive == run.isSD2SNES == True: + print('ERROR: Run cannot be on both Everdrive and SD2SNES') + return False + + # Create RunStatus Object + rs = RunStatus() + rs.customCommand = setupCommunication(run) + rs.inputBuffer = run.getInputBuffer(rs.customCommand) + rs.tasRun = run + rs.isRunModified = False + rs.dpcmState = run.dpcmFix + rs.windowState = run.window + rs.defaultSave = filename # Default Save Name for loaded files is the file that was loaded + rs.isLoadedRun = True + runStatuses.append(rs) + + # Select New Run + selected_run = len(runStatuses) - 1 + # add everdrive header if needed + if run.isEverdrive == True: + add_everdrive_header(selected_run) + # add SD2SNES header if needed + if run.isSD2SNES == True: + add_sd2snes_header(selected_run) + # add blank frames + if run.blankFrames != []: + load_blank_frames(selected_run) + send_frames(selected_run, prebuffer) + + if missingValues != 0: + print('Run was missing ' + missingValues + 'settings resave suggested.') + print('Run has been successfully loaded!') + +def send_frames(index, amount): + framecount = runStatuses[index].frameCount + + if TASLINK_CONNECTED == 1: + string = ''.join(runStatuses[index].inputBuffer[framecount:(framecount + amount)]) + ser.write(bytes(string, 'utf-8')) + else: + print("DATA SENT: ", ''.join(runStatuses[index].inputBuffer[framecount:(framecount + amount)])) + + runStatuses[index].frameCount += amount + +def setupCommunication(tasrun): + print("Now preparing TASLink....") + # claim the ports / lanes + for port in tasrun.portsList: + claimConsolePort(port, tasrun.controllerType) + + # begin serial communication + controllers = list('00000000') + # set controller lanes and ports + for port in tasrun.portsList: + # enable the console ports + command = "sp" + command += str(port) # should look like 'sp1' now + portData = tasrun.controllerType + if tasrun.dpcmFix: + portData += 128 # add the flag for the 8th bit + if TASLINK_CONNECTED: + string = command + chr(portData) + ser.write(bytes(string, 'utf-8')) + else: + print(command, portData) + + # enable the controllers lines + if tasrun.controllerType == CONTROLLER_NORMAL: + limit = 1 + elif tasrun.controllerType == CONTROLLER_MULTITAP: + limit = 4 + else: + limit = 2 + for counter in range(limit): + command = "sc" + str(lanes[port][counter]) # should look like 'sc1' now + controllers[8 - lanes[port][counter]] = '1' # this is used later for the custom stream command + # now we need to set the byte data accordingly + byte = list('00000000') + byte[0] = '1' # first set it plugged in + byte[1] = str(tasrun.overread) # next set overread value + # set controller size + if tasrun.controllerBits == 8: + pass # both bytes should be 0, so we're good + elif tasrun.controllerBits == 16: + byte[7] = '1' + elif tasrun.controllerBits == 24: + byte[6] = '1' + elif tasrun.controllerBits == 32: + byte[6] = '1' + byte[7] = '1' + bytestring = "".join(byte) # convert binary to string + if TASLINK_CONNECTED: + string = command + chr(int(bytestring, 2)) + ser.write(bytes(string, 'utf-8')) # send the sc command + else: + print(command, bytestring) + + # setup custom stream command + command = 's' + customCommand = getNextMask() + if customCommand == 'Z': + print("ERROR: all four custom streams are full!") + # TODO: handle gracefully + controllerMask = "".join(controllers) # convert binary to string + command += customCommand + if TASLINK_CONNECTED: + string = command + chr(int(controllerMask, 2)) + ser.write(bytes(string, 'utf-8')) # send the sA/sB/sC/sD command + else: + print(command, controllerMask) + + # setup events #s e lane_num byte controllerMask + command = 'se' + str(min(tasrun.portsList)) + # do first byte + byte = list( + '{0:08b}'.format(int(tasrun.window / 0.25))) # create padded bytestring, convert to list for manipulation + byte[0] = '1' # enable flag + bytestring = "".join(byte) # turn back into string + if TASLINK_CONNECTED: + string = command + chr(int(bytestring, 2)) + chr(int(controllerMask, 2)) + ser.write(bytes(string, 'utf-8')) # send the sA/sB/sC/sD command + else: + print(command, bytestring, controllerMask) + + # finally, clear lanes and get ready to rock + if TASLINK_CONNECTED: + string = "r" + chr(int(controllerMask, 2)) + ser.write(bytes(string, 'utf-8')) + else: + print("r", controllerMask) + + return customCommand + +def isConsolePortAvailable(port, type): + # port check + if consolePorts[port] != 0: # if port is disabled or in use + return False + + # lane check + if type == CONTROLLER_NORMAL: + if consoleLanes[lanes[port][0]]: + return False + elif type == CONTROLLER_MULTITAP: + if port != 1 and port != 2: # multitap only works on ports 1 and 2 + return False + if any(consoleLanes[lanes[port][x]] for x in range(4)): + return False + else: # y-cable or four-score + if any(consoleLanes[lanes[port][x]] for x in range(2)): + return False + + return True # passed all checks + +def claimConsolePort(port, type): + if consolePorts[port] == 0: + consolePorts[port] = 1 # claim it + if type == CONTROLLER_NORMAL: + consoleLanes[lanes[port][0]] = 1 + elif type == CONTROLLER_MULTITAP: + for lane in lanes[port]: + consoleLanes[lane] = 1 + else: + consoleLanes[lanes[port][0]] = 1 + consoleLanes[lanes[port][1]] = 1 + +def releaseConsolePort(port, type): + if consolePorts[port] == 1: + consolePorts[port] = 0 + if type == CONTROLLER_NORMAL: + consoleLanes[lanes[port][0]] = 0 + elif type == CONTROLLER_MULTITAP: + for lane in lanes[port]: + consoleLanes[lane] = 0 + else: + consoleLanes[lanes[port][0]] = 0 + consoleLanes[lanes[port][1]] = 0 + +def remove_everdrive_header(runid): + print("Removing Everdrive Header!\n") + newbuffer = runStatuses[runid].inputBuffer + oldbuffer = newbuffer + newbuffer = oldbuffer[EVERDRIVEFRAMES:] + runStatuses[runid].inputBuffer = newbuffer +def add_everdrive_header(runid): + tasRun = runStatuses[runid].tasRun + print("Adding Everdrive Header!\n") + newbuffer = runStatuses[runid].inputBuffer + blankframe = runStatuses[runid].customCommand + max = int(tasRun.controllerBits / 8) * tasRun.numControllers # bytes * number of controllers + # next we take controller type into account + if tasRun.controllerType == CONTROLLER_Y or tasRun.controllerType == CONTROLLER_FOUR_SCORE: + max *= 2 + elif tasRun.controllerType == CONTROLLER_MULTITAP: + max *= 4 + for bytes in range(max): + blankframe += chr(0xFF) + startframe = runStatuses[runid].customCommand + max = int(tasRun.controllerBits / 8) * tasRun.numControllers # bytes * number of controllers + # next we take controller type into account + for bytes in range(max): + if bytes == 0: + startframe += chr(0xEF) # press start on controller 1 + else: + startframe += chr(0xFF) + newbuffer.insert(0, startframe) # add a frame pressing start to start of input buffer + for frame in range(0, EVERDRIVEFRAMES-1): + newbuffer.insert(0, blankframe) # add x number of blank frames to start of input buffer + runStatuses[runid].inputBuffer = newbuffer + +def remove_sd2snes_header(runid): + print("Removing SD2SNES Header!\n") + newbuffer = runStatuses[runid].inputBuffer + oldbuffer = newbuffer + newbuffer = oldbuffer[130:] + runStatuses[runid].inputBuffer = newbuffer +def add_sd2snes_header(runid): + tasRun = runStatuses[runid].tasRun + print("Adding SD2SNES Header!\n") + newbuffer = runStatuses[runid].inputBuffer + max = int(tasRun.controllerBits / 8) * tasRun.numControllers # bytes * number of controllers + blankframe = runStatuses[runid].customCommand + if tasRun.controllerType == CONTROLLER_Y or tasRun.controllerType == CONTROLLER_FOUR_SCORE: + max *= 2 + elif tasRun.controllerType == CONTROLLER_MULTITAP: + max *= 4 + for bytes in range(max): + blankframe += chr(0xFF) + startframe = runStatuses[runid].customCommand + for bytes in range(max): + if bytes == 0: + startframe += chr(0xEF) # press start on controller 1 + else: + startframe += chr(0xFF) + aframe = runStatuses[runid].customCommand + for bytes in range(max): + if bytes == 1: + aframe += chr(0x7F) # press A on controller 1 + else: + aframe += chr(0xFF) + newbuffer.insert(0, aframe) # add a frame pressing A to start of input buffer + for frame in range(0, 9): + newbuffer.insert(0, blankframe) # add 10 blank frames to start of input buffer + newbuffer.insert(0, startframe) # add a frame pressing start to start of input buffer + for frame in range(0, 119): + newbuffer.insert(0, blankframe) # add 120 blank frames to start of input buffer + runStatuses[runid].inputBuffer = newbuffer + +def add_blank_frame(frameNum, runid): + run = runStatuses[runid].tasRun + working_string = runStatuses[runid].customCommand + max = int(run.controllerBits / 8) * run.numControllers # bytes * number of controllers + # next we take controller type into account + if run.controllerType == CONTROLLER_Y or run.controllerType == CONTROLLER_FOUR_SCORE: + max *= 2 + elif run.controllerType == CONTROLLER_MULTITAP: + max *= 4 + for bytes in range(max): + working_string += chr(0xFF) + runStatuses[runid].inputBuffer.insert(frameNum, working_string) +def load_blank_frames(runid): + run = runStatuses[runid].tasRun + for x in range(len(run.blankFrames)): + frame = run.blankFrames[x] + if run.isEverdrive == True: + realframe = run.dummyFrames + EVERDRIVEFRAMES + frame + elif run.isSD2SNES == True: + realframe = run.dummyFrames + SD2SNESFRAMES + frame + else: + realframe = run.dummyFrames + frame + add_blank_frame(realframe,runid) + +def handleTransition(run_index, transition): + if runStatuses[run_index].dpcmState != transition.dpcmFix: + for port in runStatuses[run_index].tasRun.portsList: + # enable the console ports + command = "sp" + command += str(port) # should look like 'sp1' now + portData = runStatuses[run_index].tasRun.controllerType + if transition.dpcmFix: + portData += 128 # add the flag for the 8th bit + string = command + chr(portData) + ser.write(bytes(string, 'utf-8')) + runStatuses[run_index].dpcmState = transition.dpcmFix + if runStatuses[run_index].windowState != transition.window: + controllers = list('00000000') + for port in runStatuses[run_index].tasRun.portsList: + if runStatuses[run_index].tasRun.controllerType == CONTROLLER_NORMAL: + limit = 1 + elif runStatuses[run_index].tasRun.controllerType == CONTROLLER_MULTITAP: + limit = 4 + else: + limit = 2 + for counter in range(limit): + controllers[8 - lanes[port][counter]] = '1' # this is used later for the custom stream command + controllerMask = "".join(controllers) # convert binary to string + + # setup events #s e lane_num byte controllerMask + command = 'se' + str(min(runStatuses[run_index].tasRun.portsList)) + # do first byte + byte = list('{0:08b}'.format( + int(transition.window / 0.25))) # create padded bytestring, convert to list for manipulation + byte[0] = '1' # enable flag + bytestring = "".join(byte) # turn back into string + string = command + chr(int(bytestring, 2)) + chr(int(controllerMask, 2)) + ser.write(bytes(string, 'utf-8')) + runStatuses[run_index].windowState = transition.window + try: + if transition.trigReset: + if TASLINK_CONNECTED: + ser.write(b"sd1") + time.sleep(0.2) + ser.write(b"sd0") + except AttributeError: + # print("WARN: HANDLE MISSING RESET FLAG FOR TRANSITION") + pass + +### CUSTOM CLASSES ### + +class RunStatus(object): + tasRun = None + inputBuffer = None + customCommand = None + isRunModified = None + dpcmState = None + windowState = None + frameCount = 0 + defaultSave = None + isLoadedRun = False + +class Transition(object): + frameno = None + window = None + dpcmFix = None + trigReset = None + +class TASRun(object): + def __init__(self, num_controllers, ports_list, controller_type, controller_bits, ovr, wndw, file_name, dummy_frames, dpcm_fix): + self.numControllers = num_controllers + self.portsList = ports_list + self.controllerType = controller_type + self.controllerBits = controller_bits + self.overread = ovr + self.window = wndw + self.inputFile = file_name + self.dummyFrames = dummy_frames + self.dpcmFix = dpcm_fix + self.transitions = [] + self.isEverdrive = False + self.isSD2SNES = False + self.blankFrames = [] + + self.fileExtension = file_name.split(".")[-1].strip() # pythonic last element of a list/string/array + + if self.fileExtension == 'r08': + self.maxControllers = 2 + elif self.fileExtension == 'r16m': + self.maxControllers = 8 + else: + self.maxControllers = 1 # random default, but truly we need to support other formats + + def addTransition(self, t): + self.transitions.append(t) + + def delTransition(self, index): + del self.transitions[index] + + def getInputBuffer(self, customCommand): + with open(self.inputFile, 'rb') as myfile: + wholefile = myfile.read() + count = 0 + working_string = "" + numBytes = int(self.controllerBits / 8) + bytesPerFrame = numBytes * self.maxControllers # 1 * 2 = 2 for NES, 2 * 8 = 16 for SNES + buffer = [""] * (int(len(wholefile) / bytesPerFrame) + self.dummyFrames) # create a new empty buffer + + numLanes = self.numControllers + # next we take controller type into account + if self.controllerType == CONTROLLER_Y or self.controllerType == CONTROLLER_FOUR_SCORE: + numLanes *= 2 + elif self.controllerType == CONTROLLER_MULTITAP: + numLanes *= 4 + + bytesPerCommand = numLanes * numBytes + + # add the dummy frames + for frame in range(self.dummyFrames): + working_string = customCommand + for bytes in range(bytesPerCommand): + working_string += chr(0xFF) + buffer[frame] = working_string + + frameno = 0 + invertedfile = [""] * len(wholefile) + for index, b in enumerate(wholefile): + print(b) + invertedfile[index] = chr(~ord(b) & 0xFF) # flip our 1's and 0's to be hardware compliant; mask just to make sure its a byte + + if self.fileExtension == 'r08': + while True: + working_string = customCommand + + one_frame = invertedfile[frameno * 2:frameno * 2 + 2] + + if len(one_frame) != 2: # fail case + break + + working_string += ''.join(one_frame) + + # combine the appropriate parts of working_string + command_string = working_string[0] + for counter in range(self.numControllers): + if self.controllerType == CONTROLLER_FOUR_SCORE: + pass # what is a four score? would probably require a new file format in fact.... + else: # normal controller + command_string += working_string[counter+1:counter+2] # math not-so-magic + buffer[frameno+self.dummyFrames] = command_string + frameno += 1 + elif self.fileExtension == 'r16m': + while True: + working_string = customCommand + + one_frame = invertedfile[frameno*16:frameno*16+16] + + if len(one_frame) != 16: # fail case + break + + working_string += ''.join(one_frame) + + # combine the appropriate parts of working_string + command_string = working_string[0] + for counter in range(self.numControllers): + if self.controllerType == CONTROLLER_Y: + command_string += working_string[(counter * 8) + 1:(counter * 8) + 5] # math magic + elif self.controllerType == CONTROLLER_MULTITAP: + command_string += working_string[(counter * 8) + 1:(counter * 8) + 9] # math magic + else: + command_string += working_string[(counter * 8) + 1:(counter * 8) + 3] # math magic + buffer[frameno+self.dummyFrames] = command_string + frameno += 1 + + return buffer + +### CUSTOM CMD CLASS ### + +# return false exits the function +# return true exits the whole CLI +class CLI(cmd.Cmd): + def __init__(self): + cmd.Cmd.__init__(self) + self.setprompt() + self.intro = "\nWelcome to the TASLink command-line interface!\nType 'help' for a list of commands.\n" + + def setprompt(self): + if selected_run == -1: + self.prompt = "TASLink> " + else: + if runStatuses[selected_run].isRunModified: + self.prompt = "TASLink[#" + str(selected_run + 1) + "][" + str( + runStatuses[selected_run].tasRun.dummyFrames) + "f][UNSAVED]> " + else: + self.prompt = "TASLink[#" + str(selected_run + 1) + "][" + str( + runStatuses[selected_run].tasRun.dummyFrames) + "f]> " + + def postcmd(self, stop, line): + self.setprompt() + return stop + + def complete(self, text, state): + if state == 0: + origline = readline.get_line_buffer() + line = origline.lstrip() + stripped = len(origline) - len(line) + begidx = readline.get_begidx() - stripped + endidx = readline.get_endidx() - stripped + compfunc = self.custom_comp_func + self.completion_matches = compfunc(text, line, begidx, endidx) + try: + return self.completion_matches[state] + except IndexError: + return None + + def custom_comp_func(self, text, line, begidx, endidx): + return self.completenames(text, line, begidx, endidx) + self.completedefault(text, line, begidx, endidx) + + # complete local directory listing + def completedefault(self, text, *ignored): + return complete_nostate(text) # get directory when it doesn't know how to autocomplete + + # do not execute the previous command! (which is the default behavior if not overridden + def emptyline(self): + return False + + def do_exit(self, data): + """Not 'goodbyte' but rather so long for a while""" + for index,runstatus in enumerate(runStatuses): + modified = runstatus.isRunModified + if modified: + while True: + save = raw_input("Run #"+str(index+1)+" is not saved. Save (y/n)? ") + if save == 'y': + self.do_save(index+1) + break + elif save == 'n': + break + else: + print("ERROR: Could not interpret response.") + + return True + + def do_save(self, data): + """Save a run to a file""" + # print options + if not runStatuses: + print("No currently active runs.") + return False + if data != "": + try: + runID = int(data) + except ValueError: + print("ERROR: Invalid run number!") + return False + if 0 < runID <= len(runStatuses): # confirm valid run number + pass + else: + print("ERROR: Invalid run number!") + return False + else: + runID = selected_run + 1 + + filename = raw_input("Please enter filename [def=" + runStatuses[runID - 1].defaultSave + "]: ") + if filename == "": + filename = runStatuses[runID - 1].defaultSave + + with open(filename, 'w') as f: + f.write(yaml.dump(runStatuses[runID - 1].tasRun)) + + runStatuses[runID - 1].isRunModified = False + + print("Save complete!") + + def do_execute(self, data): + """execute a sequence of commands from a file""" + if data == "": + while True: + file = raw_input("File (Blank to cancel): ") + if file == "": + break + elif os.path.exists(file): + break + else: + print("Error: File does not exist!\n") + continue + else: + file = data + if file == "": + return False + elif not os.path.exists(file): + print("Error: File does not exist!\n") + return False + scriptList = [] + with open(file, 'r') as f: + for command in f: + command = command[:-1] + scriptList.append(command) + while True: + a = raw_input("[s]how, [r]un, [e]xit: ") + a = a.lower() + if a == "": + continue + elif a == "s": + print(scriptList) + continue + elif a == "e": + return False + elif a == "r": + print("Executing: " + file) + break + else: + continue + for command in scriptList: + self.onecmd(command) + return False + + def do_off(self, data): + """Turns off the SNES via reset pin, if connected""" + ser.write(b"sd1") + print("Console off.") + + def do_on(self, data): + """Turns on the SNES via reset pin, if connected""" + ser.write(b"sd0") + print("Console on.") + + def do_restart(self, data): + """Turns the SNES console off, restarts the current run, and turns the SNES console on""" + self.do_off(data) + time.sleep(0.2) + self.do_reset(data) + time.sleep(0.2) + self.do_on(data) + print("The restart process is complete!") + + def do_hard_restart(self, data): + """Turns the SNES console off, restarts the current run, and turns the SNES console on""" + self.do_off(data) + time.sleep(1) + self.do_reset(data) + time.sleep(1) + self.do_on(data) + print("The restart process is complete!") + + def do_modify_frames(self, data): + """Modify the initial blank input frames""" + # print options + if not runStatuses: + print("No currently active runs.") + return False + if data != "": + try: + runID = int(data) + except ValueError: + print("ERROR: Invalid run number!") + pass + if 0 < runID <= len(runStatuses): # confirm valid run number + pass + else: + print("ERROR: Invalid run number!") + return False + else: + runID = selected_run + 1 + index = runID - 1 + run = runStatuses[index].tasRun + print("The current number of initial blank frames is : " + str(run.dummyFrames)) + frames = readint("How many initial blank frames do you want? ") + difference = frames - run.dummyFrames # positive means we're adding frames, negative means we're removing frames + run.dummyFrames = frames + # modify input buffer accordingly + if difference > 0: + working_string = runStatuses[index].customCommand + max = int(run.controllerBits / 8) * run.numControllers # bytes * number of controllers + # next we take controller type into account + if run.controllerType == CONTROLLER_Y or run.controllerType == CONTROLLER_FOUR_SCORE: + max *= 2 + elif run.controllerType == CONTROLLER_MULTITAP: + max *= 4 + for bytes in range(max): + working_string += chr(0xFF) + + for count in range(difference): + if run.isEverdrive: + runStatuses[index].inputBuffer.insert(EVERDRIVEFRAMES, working_string) + elif run.isSD2SNES: + runStatuses[index].inputBuffer.insert(SD2SNESFRAMES, working_string) + else: + runStatuses[index].inputBuffer.insert(0, working_string) # add the correct number of blank input frames + elif difference < 0: # remove input frames + if run.isEverdrive: + runStatuses[index].inputBuffer = runStatuses[index].inputBuffer[0:EVERDRIVEFRAMES]+runStatuses[index].inputBuffer[EVERDRIVEFRAMES-difference:] + elif run.isSD2SNES: + runStatuses[index].inputBuffer = runStatuses[index].inputBuffer[0:SD2SNESFRAMES]+runStatuses[index].inputBuffer[SD2SNESFRAMES-difference:] + else: + runStatuses[index].inputBuffer = runStatuses[index].inputBuffer[-difference:] + + runStatuses[index].isRunModified = True + + print("Run has been updated. Remember to save if you want this change to be permanent!") + + def do_add_blank_frame(self, data): + """Add a blank frame at a particular offset in the current run""" + if selected_run == -1: + print("ERROR: No run is selected!\n") + return + print("Note this is automatically offset for dummy frame count and headers, it is not offset for other blank frames") + print("This Cannot be undone without reloading run, 0 to cancel") + while True: + try: + frameNum = readint("After what frame will this blank frame be inserted? ") + if frameNum < 0: + print("ERROR: Please enter a positive number!\n") + continue + elif frameNum == 0: + return + else: + break + except ValueError: + print("ERROR: Please enter an integer!\n") + runStatuses[selected_run].tasRun.blankFrames.append(frameNum) + runStatuses[selected_run].isRunModified = True + if runStatuses[selected_run].tasRun.isEverdrive: + frameNum = frameNum + EVERDRIVEFRAMES + if runStatuses[selected_run].tasRun.isSD2SNES: + frameNum = frameNum + SD2SNESFRAMES + add_blank_frame(frameNum, selected_run) + + def do_reset(self, data): + """Reset an active run back to frame 0""" + # print options + if not runStatuses: + print("No currently active runs.") + return False + + if data.lower() == 'all': + if TASLINK_CONNECTED: + ser.write(b"R") + else: + print("R") + for index in range(len(runStatuses)): + runStatuses[index].frameCount = 0 + send_frames(index, prebuffer) # re-pre-buffer-! + # return runs to their original state + t = Transition() + t.dpcmFix = runStatuses[index].tasRun.dpcmFix + t.window = runStatuses[index].tasRun.window + handleTransition(index,t) + print("Reset command given to all runs!") + return False + elif data != "": + # confirm integer + try: + runID = int(data) + except ValueError: + print("ERROR: Please enter 'all' or an integer!\n") + return False + if 0 < runID <= len(runStatuses): # confirm valid run number + pass + else: + print("ERROR: Invalid run number!") + return False + else: + runID = selected_run + 1 + index = runID - 1 + # get the lane mask + controllers = list('00000000') + tasrun = runStatuses[index].tasRun + if tasrun.controllerType == CONTROLLER_NORMAL: + limit = 1 + elif tasrun.controllerType == CONTROLLER_MULTITAP: + limit = 4 + else: + limit = 2 + + for port in tasrun.portsList: + for counter in range(limit): + controllers[8 - lanes[port][counter]] = '1' + + controllerMask = "".join(controllers) # convert binary to string + + if TASLINK_CONNECTED: + string = "r" + chr(int(controllerMask, 2)) + ser.write(bytes(string, 'utf-8')) # clear the buffer + else: + print("r" + controllerMask, 2) # clear the buffer + + runStatuses[index].frameCount = 0 + send_frames(index, prebuffer) # re-pre-buffer-! + # return run to its original state + t = Transition() + t.dpcmFix = runStatuses[index].tasRun.dpcmFix + t.window = runStatuses[index].tasRun.window + handleTransition(index, t) + print("Reset complete!") + + def do_reload(self, data): + """Reload selected run from file, need to have loaded from a file first""" + if selected_run == -1: + print("ERROR: No run is selected!\n") + return + if not runStatuses[selected_run].isLoadedRun: + print("ERROR: Run wasn't loaded from file!\n") + return + fileToLoad = runStatuses[selected_run].defaultSave + self.onecmd("remove") + self.onecmd("load " + fileToLoad) + return False + + def do_remove(self, data): + """Remove one of the current runs.""" + global selected_run + # print options + if not runStatuses: + print("No currently active runs.") + return False + if data != "": + try: + runID = int(data) + except ValueError: + print("ERROR: Invalid run number!") + return False + if 0 < runID <= len(runStatuses): # confirm valid run number + pass + else: + print("ERROR: Invalid run number!") + return False + else: + runID = selected_run + 1 + index = runID - 1 + # make the mask + controllers = list('00000000') + tasrun = runStatuses[index].tasRun + if tasrun.controllerType == CONTROLLER_NORMAL: + limit = 1 + elif tasrun.controllerType == CONTROLLER_MULTITAP: + limit = 4 + else: # y-cable + limit = 2 + for port in tasrun.portsList: + for counter in range(limit): + controllers[8 - lanes[port][counter]] = '1' + controllerMask = "".join(controllers) # convert binary to string + # free ports + for port in runStatuses[index].tasRun.portsList: + releaseConsolePort(port, runStatuses[index].tasRun.controllerType) + # free custom stream and event + freeMask(runStatuses[index].customCommand) + # remove input and run from lists + del runStatuses[index] + + # clear the lanes + if TASLINK_CONNECTED: + string = "r" + chr(int(controllerMask, 2)) + ser.write(bytes(string, 'utf-8')) # clear the buffer + else: + print("r" + controllerMask, 2) # clear the buffer + + selected_run = len(runStatuses) - 1 # even if there was only 1 run, it will go to -1, signaling we have no more runs + + print("Run has been successfully removed!") + + def do_load(self, data): + """Load a run from a file""" + if data == "": + fileName = get_input(type = 'str', + prompt = 'What is the input file (path to filename) ? ') + if fileName == None: + return False + else: + fileName = data + if not os.path.isfile(fileName): + print("ERROR: File does not exist!") + return False + load(fileName) + + def do_list(self, data): + """List all active runs""" + if not runStatuses: + print("No currently active runs.") + return False + for index, runstatus in enumerate(runStatuses): + print("Run #" + str(index + 1) + ": ") + print(yaml.dump(runstatus.tasRun)) + pass + + def do_select(self, data): + """Select a run to modify with other commands""" + global selected_run + + if not runStatuses: + print("No currently active runs.") + return False + + if data != "": + # confirm integer + try: + runID = int(data) + except ValueError: + print("ERROR: Please enter an integer!\n") + return False + if 0 < runID <= len(runStatuses): # confirm valid run number + pass + else: + print("ERROR: Invalid run number!") + return False + else: + while True: + runID = readint("Which run # do you want to select? ") + if 0 < runID <= len(runStatuses): # confirm valid run number + break + else: + print("ERROR: Invalid run number!") + selected_run = runID - 1 + + def do_add_transition(self, data): + """Adds a transition of communication settings at a particular frame or Adds a Console Reset at a particular frame""" + if selected_run == -1: + print("ERROR: No run is selected!\n") + return + # transitions + print("NOTE: Reset Transitions need to be triggered 1 frame early") + frameNum = get_input(type = 'int', + prompt = 'At what frame will this transition occur? ', + constraints = {'min': 1}) + if frameNum == None: + return False + dpcm_fix = get_input(type = 'bool', + prompt = 'Apply DPCM fix (y/n)? ') + if dpcm_fix == None: + return False + window = get_input(type = 'float', + prompt = 'Window value (0 to disable, otherwise enter time in ms. Must be multiple of 0.25ms. Must be between 0 and 15.75ms) [def=' + str(DEFAULTS['windowmode']) + ']? ', + default = DEFAULTS['windowmode'], + constraints = {'min': 0, 'max': 15.75, 'interval': 0.25}) + if window == None: + return False + trigReset = get_input(type = 'bool', + prompt = 'Reset Console (y/n)? ') + if trigReset == None: + return False + t = Transition() + t.dpcmFix = dpcm_fix + t.frameno = frameNum + t.window = window + t.trigReset = trigReset + runStatuses[selected_run].tasRun.addTransition(t) + runStatuses[selected_run].isRunModified = True + + def do_toggle_everdrive(self, data): + """Adds a header of input to the start of the run to boot + the most recent rom on the NES Everdrive Cart""" + if selected_run == -1: + print("ERROR: No run is selected!\n") + return + if runStatuses[selected_run].tasRun.isSD2SNES == True: + print("ERROR: Run Cannot be on both Everdrive and SD2SNES!\n") + return + if runStatuses[selected_run].tasRun.isEverdrive == True: + remove_everdrive_header(selected_run) + runStatuses[selected_run].tasRun.isEverdrive = False + elif runStatuses[selected_run].tasRun.isEverdrive == False: + add_everdrive_header(selected_run) + runStatuses[selected_run].tasRun.isEverdrive = True + + def do_toggle_sd2snes(self, data): + """Adds a header of input to the start of the run to boot + the most recent rom on the SNES SD2SNES Cart""" + if selected_run == -1: + print("ERROR: No run is selected!\n") + return + if runStatuses[selected_run].tasRun.isEverdrive == True: + print("ERROR: Run Cannot be on both Everdrive and SD2SNES!\n") + return + if runStatuses[selected_run].tasRun.isSD2SNES == True: + remove_sd2snes_header(selected_run) + runStatuses[selected_run].tasRun.isSD2SNES = False + elif runStatuses[selected_run].tasRun.isSD2SNES == False: + add_sd2snes_header(selected_run) + runStatuses[selected_run].tasRun.isSD2SNES = True + + def do_new(self, data): + """Create a new run with parameters specified in the terminal""" + global selected_run + + # get input file + while True: + fileName = get_input(type = 'str', + prompt = 'What is the input file (path to filename) ? ') + if fileName == None: + return False + if not os.path.isfile(fileName): + print('ERROR: File does not exist!') + continue + else: + break + + # get ports to use + while True: + try: + breakout = True + portsList = get_input(type = 'str', + prompt = 'Which physical controller port numbers will you use (1-4, commas between port numbers)? ') + if portsList == None: + return False + portsList = list(map(int, portsList.split(","))) # splits by commas or spaces, then convert to int + numControllers = len(portsList) + for port in portsList: + if port not in range(1, 5): # Top of range is exclusive + print("ERROR: Port out of range... " + str(port) + " is not between (1-4)!\n") + breakout = False + break + if not isConsolePortAvailable(port, CONTROLLER_NORMAL): # check assuming one lane at first + print("ERROR: The main data lane for port " + str(port) + " is already in use!\n") + breakout = False + break + if any(portsList.count(x) > 1 for x in portsList): # check duplciates + print("ERROR: One of the ports was listed more than once!\n") + continue + if breakout: + break + except ValueError: + print("ERROR: Please enter integers!\n") + + # get controller type + while True: + breakout = True + controllerType = get_input(type = 'str', + prompt = 'What controller type does this run use ([n]ormal, [y], [m]ultitap, [f]our-score) [def=' + DEFAULTS['contype'] + ']? ', + default = DEFAULTS['contype']) + if controllerType == None: + return False + if controllerType.lower() not in ["normal", "y", "multitap", "four-score", "n", "m", "f"]: + print("ERROR: Invalid controller type!\n") + continue + if controllerType.lower() == "normal" or controllerType.lower() == "n": + controllerType = CONTROLLER_NORMAL + elif controllerType.lower() == "y": + controllerType = CONTROLLER_Y + elif controllerType.lower() == "multitap" or controllerType.lower() == "m": + controllerType = CONTROLLER_MULTITAP + elif controllerType.lower() == "four-score" or controllerType.lower() == "f": + controllerType = CONTROLLER_FOUR_SCORE + for x in range(len(portsList)): + if not isConsolePortAvailable(portsList[x], controllerType): # check ALL lanes + print("ERROR: One or more lanes is in use for port " + str(portsList[x]) + "!\n") + breakout = False + if breakout: + break + + # 8, 16, 24, or 32 bit + while True: + # determine default controller bit by checking input file type + ext = os.path.splitext(fileName)[1] + cbd = "" + if ext == ".r08": + cbd = 8 + if ext == ".r16m": + cbd = 16 + controllerBits = get_input(type = 'int', + prompt = 'How many bits of data per controller (8, 16, 24, or 32) [def=' + str(cbd) + ']? ', + default = cbd, + constraints = {'min': 8, 'max': 32, 'interval': 8}) + if controllerBits == None: + return False + if controllerBits != 8 and controllerBits != 16 and controllerBits != 24 and controllerBits != 32: + print("ERROR: Bits must be either 8, 16, 24, or 32!\n") + else: + break + + # overread value + overread = get_input(type = 'int', + prompt = 'Overread value (0 or 1) [def=' + str(DEFAULTS['overread']) + ']? ', + default = DEFAULTS['overread'], + constraints = {'min': 0, 'max': 1}) + if overread == None: + return False + + # DPCM fix + dpcmFix = get_input(type = 'bool', + prompt = 'Apply DPCM fix (y/n) [def=' + str(DEFAULTS['dpcmfix']) + ']? ', + default = DEFAULTS['dpcmfix']) + if dpcmFix == None: + return False + + # window mode 0-15.75ms window = get_input(type = 'float', prompt = 'Window value (0 to disable, otherwise enter time in ms. Must be multiple of 0.25ms. Must be between 0 and 15.75ms) [def=' + str(DEFAULTS['windowmode']) + ']? ', default = DEFAULTS['windowmode'], constraints = {'min': 0, 'max': 15.75, 'interval': 0.25}) - - - - + if window == None: + return False + + # dummy frames + dummyFrames = get_input(type = 'int', + prompt = 'Number of blank input frames to prepend [def=' + str(DEFAULTS['dummyframes']) + ']? ', + default = DEFAULTS['dummyframes'], + constraints = {'min': 0}) + if dummyFrames == None: + return False + + # create TASRun object and assign it to our global, defined above + tasrun = TASRun(numControllers, portsList, controllerType, controllerBits, overread, window, fileName, dummyFrames, dpcmFix) + + # create the RunStatus object + rs = RunStatus() + rs.customCommand = setupCommunication(tasrun) + rs.inputBuffer = tasrun.getInputBuffer(rs.customCommand) + rs.tasRun = tasrun + rs.isRunModified = True + rs.dpcmState = dpcmFix + rs.windowState = window + # Remove Extension from filename 3 times then add ".tcf" to generate a Default Save Name + rs.defaultSave = os.path.splitext(os.path.splitext(os.path.splitext(fileName)[0])[0])[0] + ".tcf" + runStatuses.append(rs) + + selected_run = len(runStatuses) - 1 + send_frames(selected_run, prebuffer) + print("Run is ready to go!") + + def do_EOF(self, line): + """/wave""" + return True + + def postloop(self): + print + +### MAIN EXECUTION BEGINS HERE ### +if len(sys.argv) < 2: + sys.stderr.write('Usage: ' + sys.argv[0] + ' \n\n') + sys.stderr.write('OR: ' + sys.argv[0] + ' ... \n\n') + sys.exit(0) + +if TASLINK_CONNECTED: + try: + ser = serial.Serial(sys.argv[1], baud, timeout=1) + except SerialException: + print ("ERROR: the specified interface (" + sys.argv[1] + ") is in use") + sys.exit(0) + + # ensure we start with all events disabled + for x in range(1,5): + ser.write(bytes("se"+str(x)+chr(0)+chr(0), 'utf-8')) + +if len(sys.argv) > 2: # load some initial files! + for filename in sys.argv[2:]: + if not os.path.isfile(filename): + print("ERROR: File "+filename+" does not exist!") + continue + load(filename) + +# Catch Ctrl+C from interupting the mainloop +signal.signal(signal.SIGINT,signal.SIG_IGN) +# start CLI in its own thread +cli = CLI() +t = threading.Thread(target=cli.cmdloop) # no parens on cmdloop is important... otherwise it blocks +t.start() + +# main thread of execution = serial communication thread +# keep loop as tight as possible to eliminate communication overhead +while t.isAlive() and not runStatuses: # wait until we have at least one run ready to go + time.sleep(0.1) + pass + +if TASLINK_CONNECTED and not t.isAlive(): + ser.close() + sys.exit(0) + ### MAIN LOOP ### -# signal.signal(signal.SIGINT,signal.SIG_IGN) # Catch Ctrl+C from interupting the mainloop + +if TASLINK_CONNECTED: + while t.isAlive(): + if not t.isAlive(): + ser.close() # close serial communication cleanly + break + + c = ser.read(1) + if c == '': # nothing was waiting + continue # so try again + numBytes = ser.inWaiting() # is anything else waiting + if numBytes > 0: + c += ser.read(numBytes) + if numBytes > 60: + print ("WARNING: High frame read detected: " + str(numBytes)) + latchCounts = [-1, c.count('f'), c.count('g'), c.count('h'), c.count('i')] + + for run_index, runstatus in enumerate(runStatuses): + run = runstatus.tasRun + port = min(run.portsList) # the same port we have an event listener on + latches = latchCounts[port] + + for transition in run.transitions: + if 0 <= (transition.frameno+run.dummyFrames+prebuffer) - runstatus.frameCount < latches: # we're about to pass the transition frame + handleTransition(run_index,transition) + for addedframe in run.blankFrames: + if 0 <= (addedframe+run.dummyFrames+prebuffer) - runstatus.frameCount < latches: # we're about to pass the addedframe frame + print("Passing Added Blank frame at: " + str(addedframe)) + + if latches > 0: + send_frames(run_index, latches) From 641d75fc56b80285216185f024f8f6bad104a9b0 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Mon, 17 Sep 2018 15:17:19 +1000 Subject: [PATCH 40/47] more fixes --- scripts/streaming/TASLink3.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/scripts/streaming/TASLink3.py b/scripts/streaming/TASLink3.py index caaf557..63e1461 100755 --- a/scripts/streaming/TASLink3.py +++ b/scripts/streaming/TASLink3.py @@ -236,7 +236,7 @@ def load(filename, batch=False): send_frames(selected_run, prebuffer) if missingValues != 0: - print('Run was missing ' + missingValues + 'settings resave suggested.') + print('Run was missing ' + str(missingValues) + ' setting(s) resave suggested.') print('Run has been successfully loaded!') def send_frames(index, amount): @@ -600,8 +600,12 @@ def getInputBuffer(self, customCommand): frameno = 0 invertedfile = [""] * len(wholefile) for index, b in enumerate(wholefile): - print(b) - invertedfile[index] = chr(~ord(b) & 0xFF) # flip our 1's and 0's to be hardware compliant; mask just to make sure its a byte +# c = 255 - b +# print(b, chr(c)) +# print(type(b),type(chr(c))) +# invertedfile[index] = chr(c) # flip our 1's and 0's to be hardware compliant; mask just to make sure its a byte +# invertedfile[index] = chr(c).encode('ascii', 'ignore') # flip our 1's and 0's to be hardware compliant; mask just to make sure its a byte + invertedfile[index] = chr(~b & 0xFF).encode('ascii', errors='replace').decode("utf-8") if self.fileExtension == 'r08': while True: @@ -746,6 +750,9 @@ def do_save(self, data): print("Save complete!") + def do_debug(self, data): + print(str(runStatuses[selected_run].inputBuffer[:-20]).encode('utf-8')) + def do_execute(self, data): """execute a sequence of commands from a file""" if data == "": @@ -1356,7 +1363,7 @@ def postloop(self): c += ser.read(numBytes) if numBytes > 60: print ("WARNING: High frame read detected: " + str(numBytes)) - latchCounts = [-1, c.count('f'), c.count('g'), c.count('h'), c.count('i')] + latchCounts = [-1, c.count(b'f'), c.count(b'g'), c.count(b'h'), c.count(b'i')] for run_index, runstatus in enumerate(runStatuses): run = runstatus.tasRun From 4ad934b2572f25aad6e5c990929d606d0d3af1ad Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Mon, 17 Sep 2018 18:04:57 +1000 Subject: [PATCH 41/47] more string encoding fixes --- scripts/streaming/TASLink3.py | 63 ++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/scripts/streaming/TASLink3.py b/scripts/streaming/TASLink3.py index 63e1461..419f8e1 100755 --- a/scripts/streaming/TASLink3.py +++ b/scripts/streaming/TASLink3.py @@ -243,8 +243,8 @@ def send_frames(index, amount): framecount = runStatuses[index].frameCount if TASLINK_CONNECTED == 1: - string = ''.join(runStatuses[index].inputBuffer[framecount:(framecount + amount)]) - ser.write(bytes(string, 'utf-8')) + string = b''.join(runStatuses[index].inputBuffer[framecount:(framecount + amount)]) + ser.write(string) else: print("DATA SENT: ", ''.join(runStatuses[index].inputBuffer[framecount:(framecount + amount)])) @@ -268,7 +268,7 @@ def setupCommunication(tasrun): portData += 128 # add the flag for the 8th bit if TASLINK_CONNECTED: string = command + chr(portData) - ser.write(bytes(string, 'utf-8')) + ser.write(string.encode('latin-1')) else: print(command, portData) @@ -299,7 +299,7 @@ def setupCommunication(tasrun): bytestring = "".join(byte) # convert binary to string if TASLINK_CONNECTED: string = command + chr(int(bytestring, 2)) - ser.write(bytes(string, 'utf-8')) # send the sc command + ser.write(string.encode('latin-1')) # send the sc command else: print(command, bytestring) @@ -313,7 +313,7 @@ def setupCommunication(tasrun): command += customCommand if TASLINK_CONNECTED: string = command + chr(int(controllerMask, 2)) - ser.write(bytes(string, 'utf-8')) # send the sA/sB/sC/sD command + ser.write(string.encode('latin-1')) # send the sA/sB/sC/sD command else: print(command, controllerMask) @@ -326,18 +326,18 @@ def setupCommunication(tasrun): bytestring = "".join(byte) # turn back into string if TASLINK_CONNECTED: string = command + chr(int(bytestring, 2)) + chr(int(controllerMask, 2)) - ser.write(bytes(string, 'utf-8')) # send the sA/sB/sC/sD command + ser.write(string.encode('latin-1')) # send the sA/sB/sC/sD command else: print(command, bytestring, controllerMask) # finally, clear lanes and get ready to rock if TASLINK_CONNECTED: string = "r" + chr(int(controllerMask, 2)) - ser.write(bytes(string, 'utf-8')) + ser.write(string.encode('latin-1')) else: print("r", controllerMask) - return customCommand + return customCommand.encode('latin-1') def isConsolePortAvailable(port, type): # port check @@ -445,12 +445,12 @@ def add_sd2snes_header(runid): aframe += chr(0x7F) # press A on controller 1 else: aframe += chr(0xFF) - newbuffer.insert(0, aframe) # add a frame pressing A to start of input buffer + newbuffer.insert(0, aframe.encode('latin-1')) # add a frame pressing A to start of input buffer for frame in range(0, 9): - newbuffer.insert(0, blankframe) # add 10 blank frames to start of input buffer + newbuffer.insert(0, blankframe.encode('latin-1')) # add 10 blank frames to start of input buffer newbuffer.insert(0, startframe) # add a frame pressing start to start of input buffer for frame in range(0, 119): - newbuffer.insert(0, blankframe) # add 120 blank frames to start of input buffer + newbuffer.insert(0, blankframe.encode('latin-1')) # add 120 blank frames to start of input buffer runStatuses[runid].inputBuffer = newbuffer def add_blank_frame(frameNum, runid): @@ -463,7 +463,7 @@ def add_blank_frame(frameNum, runid): elif run.controllerType == CONTROLLER_MULTITAP: max *= 4 for bytes in range(max): - working_string += chr(0xFF) + working_string += chr(0xFF).encode('latin-1') runStatuses[runid].inputBuffer.insert(frameNum, working_string) def load_blank_frames(runid): run = runStatuses[runid].tasRun @@ -487,7 +487,7 @@ def handleTransition(run_index, transition): if transition.dpcmFix: portData += 128 # add the flag for the 8th bit string = command + chr(portData) - ser.write(bytes(string, 'utf-8')) + ser.write(string.encode('latin-1')) runStatuses[run_index].dpcmState = transition.dpcmFix if runStatuses[run_index].windowState != transition.window: controllers = list('00000000') @@ -510,14 +510,14 @@ def handleTransition(run_index, transition): byte[0] = '1' # enable flag bytestring = "".join(byte) # turn back into string string = command + chr(int(bytestring, 2)) + chr(int(controllerMask, 2)) - ser.write(bytes(string, 'utf-8')) + ser.write(string.encode('latin-1')) runStatuses[run_index].windowState = transition.window try: if transition.trigReset: if TASLINK_CONNECTED: - ser.write(b"sd1") + ser.write("sd1".encode('latin-1')) time.sleep(0.2) - ser.write(b"sd0") + ser.write("sd0".encode('latin-1')) except AttributeError: # print("WARN: HANDLE MISSING RESET FLAG FOR TRANSITION") pass @@ -594,7 +594,7 @@ def getInputBuffer(self, customCommand): for frame in range(self.dummyFrames): working_string = customCommand for bytes in range(bytesPerCommand): - working_string += chr(0xFF) + working_string += chr(0xFF).encode('latin-1') buffer[frame] = working_string frameno = 0 @@ -605,7 +605,7 @@ def getInputBuffer(self, customCommand): # print(type(b),type(chr(c))) # invertedfile[index] = chr(c) # flip our 1's and 0's to be hardware compliant; mask just to make sure its a byte # invertedfile[index] = chr(c).encode('ascii', 'ignore') # flip our 1's and 0's to be hardware compliant; mask just to make sure its a byte - invertedfile[index] = chr(~b & 0xFF).encode('ascii', errors='replace').decode("utf-8") + invertedfile[index] = chr(~b & 0xFF).encode('latin-1') if self.fileExtension == 'r08': while True: @@ -616,7 +616,7 @@ def getInputBuffer(self, customCommand): if len(one_frame) != 2: # fail case break - working_string += ''.join(one_frame) + working_string += ''.join(one_frame).encode('latin-1') # combine the appropriate parts of working_string command_string = working_string[0] @@ -636,18 +636,18 @@ def getInputBuffer(self, customCommand): if len(one_frame) != 16: # fail case break - working_string += ''.join(one_frame) + working_string += b''.join(one_frame) # combine the appropriate parts of working_string - command_string = working_string[0] + command_string = chr(working_string[0]) for counter in range(self.numControllers): if self.controllerType == CONTROLLER_Y: - command_string += working_string[(counter * 8) + 1:(counter * 8) + 5] # math magic + command_string += working_string[(counter * 8) + 1:(counter * 8) + 5].decode('latin-1') # math magic elif self.controllerType == CONTROLLER_MULTITAP: - command_string += working_string[(counter * 8) + 1:(counter * 8) + 9] # math magic + command_string += working_string[(counter * 8) + 1:(counter * 8) + 9].decode('latin-1') # math magic else: - command_string += working_string[(counter * 8) + 1:(counter * 8) + 3] # math magic - buffer[frameno+self.dummyFrames] = command_string + command_string += working_string[(counter * 8) + 1:(counter * 8) + 3].decode('latin-1') # math magic + buffer[frameno+self.dummyFrames] = command_string.encode('latin-1') frameno += 1 return buffer @@ -798,12 +798,12 @@ def do_execute(self, data): def do_off(self, data): """Turns off the SNES via reset pin, if connected""" - ser.write(b"sd1") + ser.write("sd1".encode('latin-1')) print("Console off.") def do_on(self, data): """Turns on the SNES via reset pin, if connected""" - ser.write(b"sd0") + ser.write("sd0".encode('latin-1')) print("Console on.") def do_restart(self, data): @@ -916,7 +916,7 @@ def do_reset(self, data): if data.lower() == 'all': if TASLINK_CONNECTED: - ser.write(b"R") + ser.write("R".encode('latin-1')) else: print("R") for index in range(len(runStatuses)): @@ -962,7 +962,7 @@ def do_reset(self, data): if TASLINK_CONNECTED: string = "r" + chr(int(controllerMask, 2)) - ser.write(bytes(string, 'utf-8')) # clear the buffer + ser.write(string.encode('latin-1')) # clear the buffer else: print("r" + controllerMask, 2) # clear the buffer @@ -1033,7 +1033,7 @@ def do_remove(self, data): # clear the lanes if TASLINK_CONNECTED: string = "r" + chr(int(controllerMask, 2)) - ser.write(bytes(string, 'utf-8')) # clear the buffer + ser.write(string.encode('latin-1')) # clear the buffer else: print("r" + controllerMask, 2) # clear the buffer @@ -1321,7 +1321,8 @@ def postloop(self): # ensure we start with all events disabled for x in range(1,5): - ser.write(bytes("se"+str(x)+chr(0)+chr(0), 'utf-8')) + string = "se"+str(x)+chr(0)+chr(0) + ser.write(string.encode('latin-1')) if len(sys.argv) > 2: # load some initial files! for filename in sys.argv[2:]: From 1716e9e6c1b4e1e8c1549e97bab681df5faea6f8 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Wed, 19 Sep 2018 14:08:55 +1000 Subject: [PATCH 42/47] Commented out a couple of debug commands --- scripts/streaming/TASLink3.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/streaming/TASLink3.py b/scripts/streaming/TASLink3.py index 419f8e1..32de992 100755 --- a/scripts/streaming/TASLink3.py +++ b/scripts/streaming/TASLink3.py @@ -100,7 +100,7 @@ def get_input(type, prompt, default='', constraints={}): else: print('ERROR: Expected boolean') except EOFError: - print('EOF') + # print('EOF') return None def getNextMask(): @@ -750,8 +750,9 @@ def do_save(self, data): print("Save complete!") - def do_debug(self, data): - print(str(runStatuses[selected_run].inputBuffer[:-20]).encode('utf-8')) +# def do_debug(self, data): +# """Debug command for testing input buffer""" +# print(str(runStatuses[selected_run].inputBuffer[:-20]).encode('utf-8')) def do_execute(self, data): """execute a sequence of commands from a file""" From 38e46c3c9564dd9e58a30bf9a20ec79e8b88facf Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Wed, 19 Sep 2018 14:59:47 +1000 Subject: [PATCH 43/47] Replaced the rest of the calls to readint/readfloat and raw_input with get_input calls --- scripts/streaming/TASLink3.py | 38 ++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/scripts/streaming/TASLink3.py b/scripts/streaming/TASLink3.py index 32de992..869a196 100755 --- a/scripts/streaming/TASLink3.py +++ b/scripts/streaming/TASLink3.py @@ -708,12 +708,15 @@ def do_exit(self, data): modified = runstatus.isRunModified if modified: while True: - save = raw_input("Run #"+str(index+1)+" is not saved. Save (y/n)? ") + save = get_input(type = 'str', + prompt = 'Run #'+str(index+1)+' is not saved. Save (y/n)? ') if save == 'y': self.do_save(index+1) break elif save == 'n': break + elif save == None: + return False else: print("ERROR: Could not interpret response.") @@ -739,9 +742,12 @@ def do_save(self, data): else: runID = selected_run + 1 - filename = raw_input("Please enter filename [def=" + runStatuses[runID - 1].defaultSave + "]: ") + filename = get_input(type = 'str', + prompt = 'Please enter filename [def=' + runStatuses[runID - 1].defaultSave + ']: ') if filename == "": filename = runStatuses[runID - 1].defaultSave + if filename == None: + return False with open(filename, 'w') as f: f.write(yaml.dump(runStatuses[runID - 1].tasRun)) @@ -758,7 +764,8 @@ def do_execute(self, data): """execute a sequence of commands from a file""" if data == "": while True: - file = raw_input("File (Blank to cancel): ") + file = get_input(type = 'str', + prompt = 'File (Blank/Ctrl+D to cancel): ') if file == "": break elif os.path.exists(file): @@ -779,7 +786,8 @@ def do_execute(self, data): command = command[:-1] scriptList.append(command) while True: - a = raw_input("[s]how, [r]un, [e]xit: ") + a = get_input(type = 'str', + prompt = '[s]how, [r]un, [e]xit: ') a = a.lower() if a == "": continue @@ -847,7 +855,11 @@ def do_modify_frames(self, data): index = runID - 1 run = runStatuses[index].tasRun print("The current number of initial blank frames is : " + str(run.dummyFrames)) - frames = readint("How many initial blank frames do you want? ") + frames = get_input(type = 'int', + prompt = 'How many initial blank frames do you want? ', + constraints = {'min': 0}) + if frames == None: + return False difference = frames - run.dummyFrames # positive means we're adding frames, negative means we're removing frames run.dummyFrames = frames # modify input buffer accordingly @@ -890,12 +902,11 @@ def do_add_blank_frame(self, data): print("This Cannot be undone without reloading run, 0 to cancel") while True: try: - frameNum = readint("After what frame will this blank frame be inserted? ") - if frameNum < 0: - print("ERROR: Please enter a positive number!\n") - continue - elif frameNum == 0: - return + frameNum = get_input(type = 'int', + prompt = 'After what frame will this blank frame be inserted? ', + constraints = {'min': 0}) + elif frameNum == None: + return False else: break except ValueError: @@ -1088,9 +1099,12 @@ def do_select(self, data): return False else: while True: - runID = readint("Which run # do you want to select? ") + runID = get_input(type = 'int', + prompt = 'Which run # do you want to select? ') if 0 < runID <= len(runStatuses): # confirm valid run number break + elif runID == None: + return False else: print("ERROR: Invalid run number!") selected_run = runID - 1 From 0197d78ebab0be76848c5f3ade1bebcf85ba516f Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Wed, 19 Sep 2018 15:22:58 +1000 Subject: [PATCH 44/47] more fixes --- scripts/streaming/TASLink3.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/scripts/streaming/TASLink3.py b/scripts/streaming/TASLink3.py index 869a196..5ad7430 100755 --- a/scripts/streaming/TASLink3.py +++ b/scripts/streaming/TASLink3.py @@ -65,6 +65,10 @@ def get_input(type, prompt, default='', constraints={}): continue if data == '' and default != '': return default + if type == 'int': + data = int(data) + if type == 'float': + data = float(data) if 'min' in constraints: if data < constraints['min']: print('ERROR: Input less than Minimium of ' + str(constraints['min'])) @@ -401,15 +405,15 @@ def add_everdrive_header(runid): elif tasRun.controllerType == CONTROLLER_MULTITAP: max *= 4 for bytes in range(max): - blankframe += chr(0xFF) + blankframe += chr(0xFF).encode('latin-1') startframe = runStatuses[runid].customCommand max = int(tasRun.controllerBits / 8) * tasRun.numControllers # bytes * number of controllers # next we take controller type into account for bytes in range(max): if bytes == 0: - startframe += chr(0xEF) # press start on controller 1 + startframe += chr(0xEF).encode('latin-1') # press start on controller 1 else: - startframe += chr(0xFF) + startframe += chr(0xFF).encode('latin-1') newbuffer.insert(0, startframe) # add a frame pressing start to start of input buffer for frame in range(0, EVERDRIVEFRAMES-1): newbuffer.insert(0, blankframe) # add x number of blank frames to start of input buffer @@ -432,25 +436,25 @@ def add_sd2snes_header(runid): elif tasRun.controllerType == CONTROLLER_MULTITAP: max *= 4 for bytes in range(max): - blankframe += chr(0xFF) + blankframe += chr(0xFF).encode('latin-1') startframe = runStatuses[runid].customCommand for bytes in range(max): if bytes == 0: - startframe += chr(0xEF) # press start on controller 1 + startframe += chr(0xEF).encode('latin-1') # press start on controller 1 else: - startframe += chr(0xFF) + startframe += chr(0xFF).encode('latin-1') aframe = runStatuses[runid].customCommand for bytes in range(max): if bytes == 1: - aframe += chr(0x7F) # press A on controller 1 + aframe += chr(0x7F).encode('latin-1') # press A on controller 1 else: - aframe += chr(0xFF) - newbuffer.insert(0, aframe.encode('latin-1')) # add a frame pressing A to start of input buffer + aframe += chr(0xFF).encode('latin-1') + newbuffer.insert(0, aframe) # add a frame pressing A to start of input buffer for frame in range(0, 9): - newbuffer.insert(0, blankframe.encode('latin-1')) # add 10 blank frames to start of input buffer + newbuffer.insert(0, blankframe) # add 10 blank frames to start of input buffer newbuffer.insert(0, startframe) # add a frame pressing start to start of input buffer for frame in range(0, 119): - newbuffer.insert(0, blankframe.encode('latin-1')) # add 120 blank frames to start of input buffer + newbuffer.insert(0, blankframe) # add 120 blank frames to start of input buffer runStatuses[runid].inputBuffer = newbuffer def add_blank_frame(frameNum, runid): @@ -872,7 +876,7 @@ def do_modify_frames(self, data): elif run.controllerType == CONTROLLER_MULTITAP: max *= 4 for bytes in range(max): - working_string += chr(0xFF) + working_string += chr(0xFF).encode('latin-1') for count in range(difference): if run.isEverdrive: @@ -905,7 +909,7 @@ def do_add_blank_frame(self, data): frameNum = get_input(type = 'int', prompt = 'After what frame will this blank frame be inserted? ', constraints = {'min': 0}) - elif frameNum == None: + if frameNum == None: return False else: break From cb5f877bb3af4ae6644b922de82a78d10776cb97 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Fri, 21 Sep 2018 17:00:30 +1000 Subject: [PATCH 45/47] Implements notification on end of playback. --- scripts/streaming/TASLink3.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/streaming/TASLink3.py b/scripts/streaming/TASLink3.py index 5ad7430..7d69c43 100755 --- a/scripts/streaming/TASLink3.py +++ b/scripts/streaming/TASLink3.py @@ -246,6 +246,13 @@ def load(filename, batch=False): def send_frames(index, amount): framecount = runStatuses[index].frameCount + # Report on end of run + if not runStatuses[index].runOver: + totalframes = len(runStatuses[index].inputBuffer) + if framecount >= totalframes: + runStatuses[index].runOver = True + print('Playback of ' + runStatuses[index].tasRun.inputFile + ' finished') + if TASLINK_CONNECTED == 1: string = b''.join(runStatuses[index].inputBuffer[framecount:(framecount + amount)]) ser.write(string) @@ -538,6 +545,7 @@ class RunStatus(object): frameCount = 0 defaultSave = None isLoadedRun = False + runOver = False class Transition(object): frameno = None @@ -937,6 +945,7 @@ def do_reset(self, data): print("R") for index in range(len(runStatuses)): runStatuses[index].frameCount = 0 + runStatuses[index].runOver = False send_frames(index, prebuffer) # re-pre-buffer-! # return runs to their original state t = Transition() @@ -983,6 +992,7 @@ def do_reset(self, data): print("r" + controllerMask, 2) # clear the buffer runStatuses[index].frameCount = 0 + runStatuses[index].runOver = False send_frames(index, prebuffer) # re-pre-buffer-! # return run to its original state t = Transition() From d538a1671a49475718278c324e8dda502e7bce55 Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Tue, 9 Oct 2018 15:08:50 +1100 Subject: [PATCH 46/47] fix emcoding issue for r08 files --- scripts/streaming/TASLink3.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/streaming/TASLink3.py b/scripts/streaming/TASLink3.py index 5ad7430..e914ea7 100755 --- a/scripts/streaming/TASLink3.py +++ b/scripts/streaming/TASLink3.py @@ -620,7 +620,7 @@ def getInputBuffer(self, customCommand): if len(one_frame) != 2: # fail case break - working_string += ''.join(one_frame).encode('latin-1') + working_string += ''.join(one_frame) # combine the appropriate parts of working_string command_string = working_string[0] @@ -628,8 +628,8 @@ def getInputBuffer(self, customCommand): if self.controllerType == CONTROLLER_FOUR_SCORE: pass # what is a four score? would probably require a new file format in fact.... else: # normal controller - command_string += working_string[counter+1:counter+2] # math not-so-magic - buffer[frameno+self.dummyFrames] = command_string + command_string += working_string[counter+1:counter+2].decode('latin-1') # math not-so-magic + buffer[frameno+self.dummyFrames] = command_string.encode('latin-1') frameno += 1 elif self.fileExtension == 'r16m': while True: From 846805d2bc5b328d6fe867a68c239209c536158c Mon Sep 17 00:00:00 2001 From: TheMas3212 Date: Mon, 5 Nov 2018 14:54:21 +1100 Subject: [PATCH 47/47] fixed loading of r08 files in python3 script --- scripts/streaming/TASLink3.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/streaming/TASLink3.py b/scripts/streaming/TASLink3.py index e914ea7..dd843c9 100755 --- a/scripts/streaming/TASLink3.py +++ b/scripts/streaming/TASLink3.py @@ -620,10 +620,10 @@ def getInputBuffer(self, customCommand): if len(one_frame) != 2: # fail case break - working_string += ''.join(one_frame) + working_string += b''.join(one_frame) # combine the appropriate parts of working_string - command_string = working_string[0] + command_string = chr(working_string[0]) for counter in range(self.numControllers): if self.controllerType == CONTROLLER_FOUR_SCORE: pass # what is a four score? would probably require a new file format in fact....