Post subject: Multi-track rerecording (Multitrack2.lua) (6)
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
I think I'll just stick my current lua code here. Seems like a good a spot as any. EDIT:(2) Displays status of all players when you select all players EDIT:(3) Less processor intensive when rewinding while unpaused. Also more robust. EDIT:(4) Added frame insertion/deletion, minimal comments. I now remember I added a pair of poorly implemented movie.rerecordcounting functions... EDIT:(5) Fixed display when inserting frames. Fixed implementation of freezing the record count when rewinding. Then cleaned up a bunch of code and added more thorough comments. EDIT:(6) Old, Multitrack.lua | New, Multitrack2.lua Removed the code stuff and such in this post and now point to the latest, cutting edge script instead. Please use Multitrack2.lua, however. They should be packaged with FCEUX anyway, but just in case... ------ Old post. Just pretend this is the only thing written for some of the posts that follows: ------ Basically, I want to record one player without interfering with what I recorded with the other player. Scratch that -- I want to record one button without interfering with the other 15 buttons. If this feature is already available on FCEUX, I clearly haven't explored it enough to find out, so I ask someone to point me to it. If not, then I ask whether it's likely possible to make a Lua script that can do this now. I'm thinking that the input of frames after a savestate should be saved, and that attempting to make input will XOR with the saved input for that frame. Wouldn't have to worry about which player is getting recorded at the time, since an XOR with zeroes doesn't change the value. Thing is, my two-player Salamander run should go a bit more smoothly when I don't have to rely on using a text editor and running the movie with each edit to see what happens. With fairly free movement, individual buttons on a player are often used separately, especially when I want to dance around. Not sure if this will make me get right back into it, but I think it will be one push that helps. The TASEdit feature looks promising, but it's currently incomplete, and isn't suited for my needs now. I can wait, but I'm asking for a possible solution now.
Former player
Joined: 12/5/2007
Posts: 716
I like your idea, maybe I can look into this at the weekend.
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
One possible problem that comes up and screams in our face: What about loading savestates out of order? Suppose I save on state 1 and begin, did stuff, saved to state 9 then reloaded 1. Now I find that the new stuff I did on reload wasn't all that great and continued on my merry way through 9. But wait! I found a neat technique that could really come in handy for 1! I jump back to 1. What's the state of the input after 1? What about when I re-load 9, what's the input look like prior to and after that load? Okay, continuing on from 9 (my master plan at 1 failed), I split of on two different paths, saved on 2 and 3. They have equal time thus far, but their states are wildly different. I do stuff on 2, decide I don't like what 2 is doing so far, and switch over to 3. Should the input after 3 carry over from 2, even though the two could very well fail to relate? How would one go about saving multiple paths without any unexpected interfered input between them? If this is going to be an option built-in to FCEUX, this idea looks like it would fit in well with the read-only switch, making it three-state instead of binary. Maybe four states if you want to pack in a "no movie" state. - In one case, you have read+write (which feels like write-only, since only your input is used after the loadstate) - In the second, you have read-only (input file only) - And the third option, possibly this XOR idea of mine, if we can work out the kinks (which takes both your input and the file's input at once. XOR the two). Just throwing out problems and ideas. Just my further thoughts on this, in case they have any importance.
adelikat
He/Him
Emulator Coder, Site Developer, Site Owner, Expert player (3598)
Joined: 11/3/2004
Posts: 4738
Location: Tennessee
there is a patched version of FCEU.16 with multi-tracking implemented by miau at my request. I used it for making my 2-player contra movie (particularly levels 2 & 4). However it is buggy, and is for .16 which has some bugs in and of itself. Currently there is no feature like that for FCEUX but I surely intend to add it. TASEdit was supposed to have that feature but unfortunately it has been unfinished for quite some time now. I'd be interested in tinkering with it and trying to get this going. It would be a very helpful feature. EDIT: I think the feature could be implemented with Lua, in theory. Though I'd rather see TASEdit improved to support it instead.
It's hard to look this good. My TAS projects
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
Don't forget that I want to record over individual buttons as opposed to between players and their 8-button chunks. I may want to control what I do with up or down without needing to remember what I did with left or right. Sometimes I come up with an optimal firing pattern, and want to later record over it without needing to keep track of when to shoot so I can dance without worry. Nice to see you've been wanting to add multi-track recording for some time now. A lot of good progress has been made so far, and I await with anticipation of what's to come. Compliments are always nice to hear, right?
adelikat
He/Him
Emulator Coder, Site Developer, Site Owner, Expert player (3598)
Joined: 11/3/2004
Posts: 4738
Location: Tennessee
FatRatKnight wrote:
Compliments are always nice to hear, right?
Indeed they are :) FCEU.16 multi-tracker is more than just player 1, player 2. Basically it appended input onto the already existing input. What you suggest is what I had in mind for FCEUX.
It's hard to look this good. My TAS projects
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
Darn it, I can't wait for the Multi-track rerecording anymore! I want it now! ... You know, why not now? I tried crafting up a lua script to do just that! -------- EDIT: Just pretend I had this awesome script here. Removed so this site uses a little less bandwidth. The particular script that was here is outdated anyway! -------- I put it up to a stress test of sorts: I ran a copy of my current progress through Salamander. The whole movie, roughly 10000 frames, as it stands. After the read-only finished, I restarted the whole thing in read+write mode. The script played back the entire movie without any desync. It's still a prototype. Needs more comments in the code to help people to read some of it, displaying the input could be handled differently, and so forth. ... Man, it was quite a feeling I got when I finally got multi-track rerecording done through lua. If there's anything that shouldn't work in there, well too bad, it's working anyway. At least, on my end. I offer what I got so far, as seen by that huge, white blot with words and symbols in the same post, and ask for how I should go about improving it.
adelikat
He/Him
Emulator Coder, Site Developer, Site Owner, Expert player (3598)
Joined: 11/3/2004
Posts: 4738
Location: Tennessee
Holy crap, you are awesome. I'll go check this out. I've been contemplating doing Nightmare on Elm Street 4 player. And was going to try to write a 4 player multi-tracker. Looks like this might be a great start. EDIT: Once it is cleaned up, some bugs weeded out, and 4 player support, I would love to add a script like this to the FCEUX release package.
It's hard to look this good. My TAS projects
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
Actually, in theory, this already has the possibility to support infinite players. All you need to do is adjust that little number somewhere in there...
--**********************************************
local players= 2

local selectplayer = "tab"   -- For selecting which player
local recordingtype= "space" -- For selecting how to record
local key = {"right", "left", "down", "up",   "L",      "O",   "J", "K"}
local btn = {"right", "left", "down", "up", "start", "select", "B", "A"}
-- Don't change btn, unless it's to adjust the spacing to keep it neat.
--**********************************************
In fact, everything in between the lines of asterisks, except maybe btn, can be changed if needed. I try to keep most of the stuff that have good reasons to be changed in a convenient spot. Please do try it out for a while, and let me know what should be changed. Having to display the input somewhere in the middle of all the action isn't what I call a wonderful display, as a likely example of what should be changed. I wanted the main thing done, the multi-track rerecording, and well... That much works. You consider it awesome enough to include it as part of the lua package? You may go ahead and include it, once the coding and display is cleaned up, and possibly the controls. I did create it with the intention that it gets used after all. Primarily for myself, since my Salamander run is largely on hold while I worked things out in lua, but hopefully others can enjoy my work as well.
adelikat
He/Him
Emulator Coder, Site Developer, Site Owner, Expert player (3598)
Joined: 11/3/2004
Posts: 4738
Location: Tennessee
Ok, I can't figure how to work this thing. First of all, what does toggle mean? Here is how I am trying to use it: I'll make a savestate at the beginning of a level. I'll record player 1 by setting player 1 input to "You". Then I will record a small segment. However, it often doesn't accept my input. I will press right and it will be on for 1 frame, then stay off. Loading my savestate has weird effects too. Then I tried to turn player 1 to script and then I set player 2 to "You" but I get the same input problem (where it only accepts 1 frame).
It's hard to look this good. My TAS projects
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
First, the script needs its own buttons defined from within its script, but you probably know that already. If not... Now you know. Second, for the fact it accepts stuff one frame at a time... That's actually one of its inconveniences that you went and discovered and I didn't. You have to keep pushing the button... Every. Single. Frame. So basically, tap, advance, tap, advance... It works on a tap, not by holding. You're probably well used to holding buttons on frame advance by now, I take it? Since I'm able to detect when you advance a frame, I could make the key act as though it is re-pressed, by clearing the lastkeys table. I probably should see if I can't add in a "hold" or "invert" option in there too, in case you can't hold down that many buttons, and that you need them over multiple frames anyway. Clearly, my options aren't descriptive enough, or is misleading. But I'll try to explain what they do, regardless of the silly names I've given them. "Toggle" will load input stored in the script and allow you, through the script's buttons, to change said input. "You" will throw away the input stored in the script. It will accept your input through the script's buttons. "Script" will load the input that it has stored, but prevent changes from you accidentally pressing the script buttons. "Off" will simply plunk in no input at all for that controller button. Maybe if you want to erase the stored input and let it run for a few seconds... I suggest leaving it on "Toggle" and never change it. Try that for a bit and see how it feels. They really could take a renaming... Any suggestions? One thing my script does, is it catches the input the emulation actually gets and stores it. I don't assume the script itself is perfect at keeping track of the input, and who knows? Maybe you're running the movie on playback or something. I haven't tried the emulation's own input into this, other than playback mode. Basically, you use the actual keys assigned by the emulator, separate from the script. The script should still pick up on it, since it is catching whatever input the emulator itself says it gets. But, as for loading and saving states... You should be able to back up without many problems, but if it was on "You" or "Off" at the time, it won't load the first frame of the state (as well as the later ones, if you advance some frames). The input, thankfully, is still in there, unless you already advanced a frame, but it still can be inconvenient when you have to switch it to "Script" or "Toggle" and reload the state again. ... In theory, anyway. Make that reload a state at least two frames away from the one you want to load, then reload the desired state. It can't detect state-loads if they happen on the same frame. EDIT: Spotting bugs by reading my code now. Apparently, when a frame has yet to be defined, I forget to clear the current input table. As a result, keys just prior to the undefined frames may stick while creating room for the new frames. I intended it to reset after every frame, but it does something different when an undefined frame is coming up. Whoops.
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
Hmm, I adjusted the code a bit. Prototype Mk II is here now. I'll leave the earlier version intact just in case something went horribly wrong in the process. The differences: - Changed input display. It's now a bunch of small boxes. I dropped the text, as I'm not sure where to fit the darn thing. - In addition to that input display, I also let you adjust the opacity on the fly. Page Up and Page Down are the default keys. - Added a few comments, which I hope points out distinct parts of my code. I find it somewhat easier to navigate now. - Removed movie limitation. Now it will run out outside of movies. - The current input frame will no longer smear itself into undefined frames. - Holding down a script keyed input will re-press the button when advancing the frame. Or loading a save state, now that I think about it. I still haven't worked out any issues involving actual input from the player to emulator, rather than input through script buttons. Then again, are there issues about it in the first place? I don't really know even that. I still could do with a little renaming of a few things, too. It's cleaner now, that much is certain, but I still feel more can be done. EDIT: Small update. Basically my attempt at highlighting functions and added the ability to move the input display, albeit slowly. I can't figure out much else I can do, so if you need anything done, you might need to do it yourself, or ask me specifically about some point. I am lost without a direction now. EDIT2: Renamed the button options. Took me this long to realize better names. Added an ability to use the mouse to move the input display. Also put hard limits on where the input display goes. Apparently, ideas are still coming, but slowly. ... Though, what's going on when the movie is on "playback" and you attempt to drag the input display with the mouse? -------- EDIT3: Removed the white blot. Less bytes to transfer this way. Latest is now in the thread-starting post. I'm also keeping a copy of what was once here, just in case. ... Just pretend there was a more-awesome piece of lua scripting here, but not quite as awesome as the latest stuff I put up at the top.
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
Alright, now I'm actually trying my script seriously. No more simple tests that lead nowhere, I'm field testing it on my Salamander run. ... Geez, things are vastly more convenient with this script! But aside from admiring my own silly stuff, on to random tidbits that I saw with my own code... Your usual emulator controls are intact. Your individual player buttons you set up for your emulator will still work. I had never intended any compatibility with player input via standard emulator set-up, but it seems quite compatible anyhow. However, while it can set buttons for my script to pick up, they can't unset the button the script has recorded. If it's important to keep resetting the input, use the "Off" or "You" settings so it stops reading from the script's stored input, and thus allow you to unset a button without script keys, by simply never loading the input from the script. There's quite an interference with "Script", however, as I fear the bug I saw involving setting false instead of nil for joypad.set. The script can pick up input from a movie on playback. I did mention my stress test already, and it was quite successful. Just load my script and let your movie run on read-only, and you'll surely fill up my script's input table. It will fail to record the first frame you activate or reload the script, but after the start-up, it'll record fine, even on a state-load (there are cases where it will fail on a state-load). You can edit stuff in the middle of the movie without killing whatever input you have set up for later, since this script can hold your dear progress. You can catch the last bit of input of your movie to continue the multi-track recording without too much worry. Stuff like that. Though, thinking on this, if you do make an edit to a movie that turns out to be faster, I might want to add a feature to insert or delete frames. Although things like lag or luck might get in the way, insertion or deletion might still be desired. I have made attempts, but am giving up for the time being. The names... The friggin' names... I can't come up with anything better! "Toggle" is highly misleading, but I can't think of anything better than that! I may as well use "Apple", "Sky", "Holy cow!", and "But sir, that's not an option!" for my names. Seriously, what am I to do with names? I. Can't. Name. At all... Please, any suggestions? ... Okay, I made it more dramatic than it really is, but I wanted to get the point across. The controls and display makes sense to me. I designed the darn thing myself, so it should be comfortable with me. I haven't noticed anything seriously wrong on my end. Unfortunately, I have no idea how comfortable it is with anyone else. Again, I want feedback on this. Or modify the script yourself if you want. In fact, any feedback is good. I tend to assume the worst when I get silence. Insults are more welcome than silence, because at least I'd get the idea I'm doing something wrong. I hate insults, sure, but it leaves less room for doubt. In any case, I'm out of ideas. If anyone here thinks I wish to keep "control" over the script, that's not the case. Use it, change it, whatever. As far as I'm concerned, this is the final release from me. I'm not going to make changes until someone tells me to do so. I've done my goal: Multi-track rerecording. I can work on my Salamander run more easily now, and I can't find a reason to go beyond that. I'm not going to add anything more to that, unless again, someone specifically asks. However, I will be around in case anyone needs help breaking down my code. I don't want it to fall into the abyss after putting some effort into it. I believe I know every detail with my script, so I should be able to answer any question, and I want to make sure anyone else can continue where I left off without too much difficulty. By the way, I edited my last post. That's my final script.
adelikat
He/Him
Emulator Coder, Site Developer, Site Owner, Expert player (3598)
Joined: 11/3/2004
Posts: 4738
Location: Tennessee
Ok, just now getting around to trying this script. It rocks my face off! This is such a great tool and makes multi-player so much easier. I want to put this in the FCEUX svn and make it part of the next release. This is where the real power of lua shines. The users can create new and innovative tools themselves; keeping the emulator lighter and the devs working on more core oriented things. Thanks so much for this script. I shall start taking my nightmare on elm street 4-player improvement more seriously now :)
It's hard to look this good. My TAS projects
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
adelikat wrote:
Ok, just now getting around to trying this script. [...]
Glad to know you find it useful. Go ahead and include it as part of the package, if you like. By the way, here's your face. How did it get dislodged from your head?! I would prefer a few things cleaned up first, but I don't know what direction to head, so someone else would need to put in a request or some edits. Then again, that's not a good enough reason to restrict this script, especially since restricting it would slow any progress that I would like done to it, extending the length of time that cleanliness reason sticks around. Here's hoping that future multiplayer runs and even a few single-player runs get easier to handle from here on. ... The silence was just starting to get at me. I was very near to asking several questions in order to provoke a response. In fact, I dropped by to do just that, and what do I see? I am quite glad to see a response now.
Skilled player (1637)
Joined: 11/15/2004
Posts: 2202
Location: Killjoy
FatRatKnight - I'm going to work on combining this script with a rewind script, if you don't mind. EDIT: Here is attempt #1 at combining scripts: EDIT #2: I added the ability to select both players. This really is an 'all' option.
-- Multi-track rerecording for FCEUX
-- To make up for the lack of an internal multi-track rerecording in FCEUX.
-- It may get obsolete when TASEdit comes around.
--FatRatKnight


-- Stuff that you are encouraged to change to fit what you need.
--*****************************************************************************
local players= 2             -- You may tweak the number of players here.

local opaque= 0              -- Default opacity. 0 is solid, 4 is invisible.

local dispX, dispY= 10, 99   -- For the display stuffs

local saveMax = 1000;		-- Max Rewind Power

-- Control scheme. You may want to change these.
local selectplayer = "S"   -- For selecting which player
local recordingtype= "space" -- For selecting how to record
local show, hide   = "pageup", "pagedown" -- Opacity adjuster
local scrlup, scrldown, scrlleft, scrlright= "numpad8", "numpad2", "numpad4", "numpad6"  -- Input display mover
local rewind = "R"
local key = {"right", "left", "down", "up",   "C",      "V",   "X", "Z"}

local btn = {"right", "left", "down", "up", "start", "select", "B", "A"}
-- Don't change btn, unless it's to adjust the spacing to keep it lined up neatly.
--*****************************************************************************

-- Stuff that really shouldn't be messed with, unless you plan to change the code significantly.
local optType= {"Both", "Keys", "List", "Off"} -- Names for option types
local keys, lastkeys = {}, {} -- To hold keyboard input
local Pin, thisInput = {}, {} -- Input manipulation array
local option = {}             -- Options for individual button input by script
local fc, lastfc= nil, nil    -- Frame counters
local pl= 1                   -- Player selected
local repeater;				  -- for holding a button




--The stuff below is for more advanced users, enter at your own peril!



local saveArray = {};--the Array in which the save states are stored
local saveCount = 0;--used for finding which array position to cycle through
local saver; -- the variable used for storing the save state
local rewindCount = 0;--this stops you looping back around the array if theres nothing at the end
local savePreventBuffer = 1;--Used for more control over when save states will be saved, not really used in this version much.

-- Initialize tables for each player.
for i= 1, players do
    Pin[i]        = {}
    thisInput[i]  = {}
    option[i]     = {}
    for j= 1, 8 do
        option[i][btn[j]]= 1
    end
end

-- Intention of options:
-- "Both"  Script buttons and input list used
-- "Keys"  Script buttons used
-- "List"  Input list used
-- "Off"   Script will not interfere with button


--*****************************************************************************
function press(button)
--*****************************************************************************
-- Checks if a button is pressed.
-- The tables it accesses should be obvious in the next line.

    if keys[button] and not lastkeys[button] then
        return true

    end
    return false
end

function pressrepeater(button)

    if keys[button] then      
    	if not lastkeys[button] or repeater == 3 then
        	repeater = 0;
        	return true        
       else 
        	repeater = repeater + 1;
    	end
    end;
    return false
end


--*****************************************************************************
function newframe()
--*****************************************************************************
-- Psuedo-detection of frame-advance or state load.
-- The lack of a function to otherwise detect state loads demands this cheap hack.
-- I will admit I'm not aware of things. Does there exist such a function already?
-- The values it accesses are: fc, lastfc

    if not fc then
        return nil              -- Sanity check
    end
    if not lastfc then          -- Although I could return errors or
        return "stateload"      -- different strings, I'm using these
    end                         -- for my purposes in this script.


    if fc == lastfc +1 then     -- I hope the string it returns
        return "frameadvance"   -- explains the intention.

elseif fc ~= lastfc    then     -- A bug occurs when you load a state
        return "stateload"      -- equal to current frame or current frame+1

    end
    return nil                  -- fc == lastfc. No advance or load
end


-- Primary function. ... Why did I pick itisyourturn, anyway?
--*****************************************************************************
function itisyourturn()
--*****************************************************************************
    keys= input.get()              -- Standard get info stuff.
    

    if pressrepeater(rewind) then 
    	if rewindCount==0 then
			--makes sure you can't go back too far could also include other things in here, left empty for now.	
		else	
			savestate.load(saveArray[saveCount-1]);
			saveCount = saveCount-1;
			rewindCount = rewindCount-1;
			if saveCount==0 then		
				saveCount = saveMax;
			end;
		end;
	end;
	--Need to grab after a possible load.
	fc= movie.framecount()         -- Without which, what can I do?

-- Selection: Players
    if press(selectplayer) then    -- Hopefully, this block is
        pl= pl + 1                 -- self-explanatory.
        if pl > players+1 then       --
            pl= 1                  -- If not, it... Selects a player...
        end                        -- Joy.
    end

-- Option: Opacity
    if press(hide) and opaque < 4 then
        opaque= opaque+1
    end
    if press(show) and opaque > 0 then
        opaque= opaque-1
    end
    gui.transparency(opaque)

-- Option: Move input display by keyboard
    if press(scrlup) then
        dispY= dispY - 1
    end
    if press(scrldown) then
        dispY= dispY + 1
    end
    if press(scrlleft) then
        dispX= dispX - 1
    end
    if press(scrlright) then
        dispX= dispX + 1
    end
    
-- Option: Move input display by mouse
    if keys["leftclick"] and lastkeys["leftclick"] then
        dispX= dispX + keys.xmouse - lastkeys.xmouse
        dispY= dispY + keys.ymouse - lastkeys.ymouse
    end


    framed= newframe()
    if framed then
        for P= 1, players do

-- Store input if you advanced the frame.
            if framed == "frameadvance" then
                if not Pin[P][lastfc] then
                    Pin[P][lastfc]= {}        -- Initialize table if none exists.
                end
                Pin[P][lastfc]= joypad.get(P)
            end


-- Load stored input, if it exists.
-- Shouldn't matter if frame advanced or state loaded. It's a new frame, darn it!
            if Pin[P][fc] then
                for i= 1, 8 do
                    op= option[P][btn[i]]
                    if op == 1  or  op == 3 then  -- "Both" or "List"
                        thisInput[P][btn[i]]= Pin[P][fc][btn[i]]
                    else
                        thisInput[P][btn[i]]= nil
                    end
                end

            else -- No input to load, so erase stuff.
                for i= 1, 8 do
                    thisInput[P][btn[i]]= nil
                end
            end
        end -- Loop for each player

-- Resets key input, so it counts as pressed again if held through frame advance.
        for i= 1, 8 do
            lastkeys[key[i]]= nil
        end

    end -- If framecount changed


-- We're done checking if a new frame happened.
-- Now we're going to check for change in input!


    if keys[recordingtype] then  -- Are you setting options?
        for i= 1, 8 do

-- Make sure to change the options of whatever key we press. Not the ones we don't press.
        if pl == players + 1 then 
        	for q = 1,players,1 do 
            	if press(key[i]) then
                	option[q][btn[i]]= option[q][btn[i]]+1
                	if option[q][btn[i]] > 4 then
                    	option[q][btn[i]]= 1
                	end
            	end
            end
         else
          	if press(key[i]) then
               	option[pl][btn[i]]= option[pl][btn[i]]+1
               	if option[pl][btn[i]] > 4 then
                   	option[pl][btn[i]]= 1
               	end
           	end
                     
         end;
-- Display current options
            gui.transparency(0)
            gui.text(20,20+12*i,btn[i])
            if pl == players + 1 then 
            	gui.text(50,20+12*i,optType[option[1][btn[i]]])
            else
            	gui.text(50,20+12*i,optType[option[pl][btn[i]]])

     	   end -- individual button loop

      end;
    else -- Oh, you're not setting options. Actual input, eh?
        for i= 1, 8 do

-- Check options to decide if we should ignore you. Else, toggle the keyed button.
         if pl == players + 1 then 
           	for q = 1,players,1 do 
            	op= option[q][btn[i]]
            	if op == 1  or  op == 2 then   -- "Both" or "Keys"
            		if press(key[i]) then          -- Spread the AND to the next line
                		if thisInput[q][btn[i]] then
                    		thisInput[q][btn[i]]= nil
                		else	
                    		thisInput[q][btn[i]]= true
                		end
            		end
            	end
            end;
         else
            op= option[pl][btn[i]]
            if op == 1  or  op == 2 then   -- "Both" or "Keys"
            	if press(key[i]) then          -- Spread the AND to the next line
                	if thisInput[pl][btn[i]] then
	                    thisInput[pl][btn[i]]= nil
    	            else
        	            thisInput[pl][btn[i]]= true
            	    end
            	end
            end
         end        
        end -- individual button loop


-- Display current input.
    dispX= math.min(math.max(dispX,-2),219) -- Limits hack
    dispY= math.min(math.max(dispY,51),185)

        for i= -10, 10 do

            Y= dispY
            if i < 0 then
                Y= Y-3
            elseif i > 0 then
                Y= Y+3
            end

            temp= {} -- I may want to pick thisInput instead of the frame list.
            if pl == players+1 then 
            	qtemp = 1;
            else
               qtemp = pl;
            end;
            if i == 0 then
                temp= thisInput[qtemp]
            else
                temp= Pin[qtemp][fc+i]
            end

            color= nil
            for j= 1, 8 do
                if not temp then
                    color= "white"
                elseif temp[btn[j]] then
                    color= "green"
                else
                    color= "red"
                end

                gui.drawbox(dispX +4*j,Y +4*i,dispX+2 +4*j,Y+2 +4*i,color)
            end
        end
        gui.drawbox(dispX+2,Y-5,dispX+36,Y+1,"blue")

    end -- Done with "options" if

-- Display selected player. I might want to move this around.
 	if pl <= players then 
    	gui.text(30,10,pl)
   	else 
    	gui.text(30,10,"B")
   	end;

-- Feed the input to the emulation.
    if movie.mode() ~= "playback" then
        for P= 1, players do
            joypad.set(P, thisInput[P])
        end
    end

    lastfc= fc        -- Standard "this happened last time" stuff
    lastkeys= keys    -- Don't want to keep registering key hits.
end



gui.register(itisyourturn)

-- I'm think I should leave it up to the user to decide if the input should stick.
FCEU.pause();
while true do
    FCEU.frameadvance()    
    saveCount=saveCount+1;
    rewindCount = math.max(rewindCount + 1,saveMax);
    if saveCount==saveMax+1 then
			saveCount = 1;
	end
    if saveArray[saveCount] == nil then
      saver = savestate.create();
   else 
   	  saver = saveArray[saveCount];
   end;
   savestate.save(saver);
   saveArray[saveCount] = saver; -- I'm pretty sure this line is unnecessary.
end


-- Possible inconveniences include:
-- * You should reload the script every time you switch through movies.
--   Failure to reload will result in old, "junk" input showing up. It hasn't been erased yet.
-- * Won't care about what you set up for your normal player input. Though it works with it fine.
-- * Could screw up under specific circumstances involving loading a state located 1 frame later.
Its... funky. Its almost working. Let me know what you think.
Sage advice from a friend of Jim: So put your tinfoil hat back in the closet, open your eyes to the truth, and realize that the government is in fact causing austismal cancer with it's 9/11 fluoride vaccinations of your water supply.
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
    rewindCount = math.max(rewindCount + 1,saveMax);
According to this line, we pick rewindCount+1 or saveMax, whichever is higher. Something tells me that you don't want rewindCount to be stuck at the maximum or above said maximum. Something tells me you'd rather keep it somewhere between 0 and max. So, just change the instance of math.max to math.min, and things might look better.
         savestate.load(saveArray[saveCount-1]);
         saveCount = saveCount-1;
         rewindCount = rewindCount-1;
         if saveCount==0 then      
            saveCount = saveMax;
         end;
This block can potentially attempt to load from location 0. You never store anything at 0 later in your code. As it stands, this may load states from 0 ~ saveMax-1, while it may save states to 1 ~ saveMax. Something's off by 1... savestate.load(saveArray[saveCount-1]); . That -1 can go. We don't need that -1. Hopefully, these changes makes it work, instead of almost working. ... Wait, I forgot one last change!
-- Multi-track rerecording for FCEUX
-- To make up for the lack of an internal multi-track rerecording in FCEUX.
-- It may get obsolete when TASEdit comes around.
--FatRatKnight
-- Rewind added by DarkKobold
Cant miss that, can we? EDIT: As for what I think of the rewind... It's very practical here. Do stuff with one player, rewind a bit, then stuff with other player. Heck, even just playing through at normal speed instead of frame-by-frame, it's fun watching your little player dance around forwards and backwards through time without first going into read-only. I like your addition. I've started messing around with the script a bit more, now that I see some activity with it. Mostly just making things work cleaner and look more organized.
Skilled player (1637)
Joined: 11/15/2004
Posts: 2202
Location: Killjoy
This is looking damn good. Also, I probably shouldn't be given so much credit for the rewind scripting - I just copy/pasta'd the script from the SVN. Honestly, all I care about is that this script works, then any credit. Keep up the good work!
Sage advice from a friend of Jim: So put your tinfoil hat back in the closet, open your eyes to the truth, and realize that the government is in fact causing austismal cancer with it's 9/11 fluoride vaccinations of your water supply.
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
Good to know I'm making decent progress so far. As for credit, I'm not sure exactly who to give it to, right now. You're the one who added it, so I'm crediting you until someone adds in another reason. Had a sort of inspiration to work on the frame insertion and deletion I was thinking about. Never knew just how exhausting working hard on it, insisting I don't stop until I'm done could be. But at least it's coded in now. I'll rest for now... As for the desync I saw, I've tried more stress tests, making sure to set all buttons to "list" and disabling all input for the emulator itself so that the keyboard error won't happen again. However, all tests were successful, even in the more complex patterns. No desyncs spotted. I'm guessing what I saw was user error on my part. Eheh... In any case, the rewind appears to be working perfectly now.
Player (215)
Joined: 2/12/2006
Posts: 373
Location: Oregon
Would it be possible to adapt this for Snes9x? Or does Snes9x already have multi-track recording, and I just haven't noticed?
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
I have previously made an attempt at porting this script to Snes9x 1.43, but to no success. The main reason being that gui.register does not call its function multiple times while paused in Snes9x. I am not sure if changes have been made since then. A simple test, if you wish...
local i=0
function tryme()
    i= i+1
    gui.text( 1 , 10 , i )
end
gui.register(tryme)

while true do
    snes9x.frameadvance()
end
If the number continuously goes up while emulation is paused, then gui.register is called repeatedly while paused. If the number freezes along with emulation when paused, gui.register is called only once, a case which I can't port this script with its full functionality. Incidentally, I stopped putting updates here. All my updates go straight to the SVN development now...
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
The latest script tends to be here, I believe. The Multitrack.lua script has a rather steep learning curve, as evidenced by the fact that adelikat told me himself. I'll take his word for it, considering how much effort I was putting into explaining things. As of the latest revision, it only works with FCEUX 2.1.2. Earlier versions of FCEUX doesn't have all the fixes and latest lua functions that this script uses. Anyway, time for instructions... Basic edits: When you run the script, a message from the script itself lets you know you've started running it. You'll also see a mostly white bunch of symbols, with the middle of these symbols surrounded by a green rectangle. Whatever could they mean? This is the input display for the frames surrounding the current frame. You can move this around by using the mouse, just drag it around with the left mouse button! I'll explain this display later. This script uses up to as many as 26 buttons off your keyboard! However, I'll start with 8 of them. By default, the arrow keys, "J", "K", "L", and "O" are linked to the joypad buttons. If you tap one of these keys, you'll note that some red symbol inside the box turned green. It's letting you know that, if you frame advance, these buttons will be pressed. If you don't like these buttons, change them in the script itself. I will refer to them as script-joypad buttons. Try out frame advance for a bit! As you do so, you'll notice the white above the box turns red. Your green stuff may scroll upwards through the display as well. The script also has rewind built-in, default key being "R", so you can back up as well, conveniently. The white symbols mean the script has yet to even see that frame. The red means the particular button wasn't pressed, and the green means it was. Already, with the 8 buttons, frame advance, and rewind, this is a rather powerful editing tool. Keep playing around with the buttons a bit, get yourself used to it a bit. Oh, wondering how to access player 2 through this script? The player switch is default to "S", and hitting it will change some displayed number to 2. This will let the script buttons affect player 2 now. Hit it again, and if you haven't changed the script, it will display both players' input. Once more to get back to player 1. If you need player 3 or 4, edit the script, find "local players=", and change the number you see there. Control: Now, there are ways to change how the control works. Hold "space". You'll see a bunch of words show up. Tap any of the 8 script-joypad buttons, and you'll see a word change. These words mean stuff as follows: "Both": Default setting. Uses both "Keys" and "List" logic. "Keys": The script will react to the script-joypad buttons. "List": The script will read its own list to determine button-presses. "Off": The script will not interfere with the button. This is for individual players, so "Both" for player 1 doesn't mean "Both" for player 2. Changing these to "List" or "Off" prevents the script buttons from working, good for avoiding mistakes or just turning off control of the script buttons. Changing to "Keys" or "Off" makes the script ignore its list, good for getting rid of input you don't want recorded. Finally, there's the "home" and "end" keys. The script does allow for the normal joypad input through the emulator's own set-up. In fact, the controller can also negate stored input in the script's list. By tapping "end", you prevent the controllers from adding to the input list, or allowing it once again. By tapping "home", the same for removing input from the list. Display: As I already said, you can use the mouse to move the display around. With numlock on, you can also use "numpad2", "numpad4", "numpad6", and "numpad8" to move the display a bit more precisely. You can change how much input is shown with "numpad1", "numpad3", "numpad7", and "numpad9". Tapping "pageup" or "pagedown" will change how opaque the display is. If you don't want to see the display, you can do that. That's about all that needs to be said. It helps to see what you're doing, so make sure you adjust whatever is needed! Odds and ends: Buttons I didn't mention until now goes here. "numpad5" will set whether the script will immediately apply changed input to its list. For the most part, all it really does is turn that center box white. However, if you want to change things while rewinding, this will ensure that your script-joypad button presses will stick to the input list as you're going backwards. "insert" and "delete", as my default assignment suggests, will insert or delete frames from the input list. This will shove input aside so that if you really needed your input through frames 254~497 to actually be on 255~498, a single press of "insert" at frame 254 is all you need. Finally, I should have covered every button the script uses. Remember, they're all defaults, and I recommend opening the script to change the keys around to suit your needs. Silly little tricks: Stuff that I found is rather neat the script works that way is put here. You don't actually need to run a movie to run this script. I'm not sure how often the need will arise, but the possibility is there. However, the script has no long-term way of storing what you put in there, so if you do want to keep what you did, you had better darn well record a movie through FCEUX's usual features! The script will always record button presses as the emulator plays out. Whether through player input or movie, everything gets added into the list. As such, you can play a movie on read-only, run this script, then let the movie play out while you allow the script to gather its input. You could also start at any point -- Starting the script at frame 0 is entirely optional. Thanks to inserts and the fact the script picks up input on movie read-only, you can make a pseudo-macro by playing out a loop you want done, set the movie on read-only, go to start of loop, then hit "insert" a few times. Once you've inserted enough blank frames for exactly one loop, let the read-only fill in the blanks. Re-load, repeat. Nowhere near as convenient as actual macros, but it's a solution, should you need it. Remember, the script's primary purpose is its editing powers. You can record some silly movie, play it back with the script running, then make pinpoint edits as needed. The secondary purpose is multitrack recording. Yes, it's called multitrack.lua, but apparently, the multitrack part is its secondary purpose. Well... It's about time I documented how to use the darn script. I'm probably still missing some stuff, but I hope this helps...
Moderator, Senior Ambassador, Experienced player (898)
Joined: 9/14/2008
Posts: 1007
OK, so over in the Otocky thread I've been talking about how I'm using the multitrack2 variant to edit the direction the weapon is fired in and by extension the note that is played in the background music. One thing I've found is that it's easy to mess up when editing an existing movie (such as replacing left+down with just left one too many frames in a row) leading to a desync. I've had to open up a backup file and use an external diff/merge tool to stitch back in the original input after I fumbled the keys and couldn't remember the exact sequence that was there when I started. So this is where I ask for a potential feature request before attempting to implement it (poorly) myself: would it be possible to store the original input in a separate list and always display this in a different color? By original input I mean either the input that was read in from a movie or the very first input that the user entered on a given frame. From the user interface perspective, when this new feature is enabled the original input would initially still be displayed as green until the user changed something to be different than the original in which case the original keys that were pressed would be displayed in yellow. Optionally it should be possible to update these "historical" keypresses to match the new current state if that convoluted sentence made any sense. I've looked around at the code a bit and it looks like everything is keyed off of the inputList list structure. From what I can tell the only way to make the feature I'm talking about possible is to have a separate list that gets written to the very first time input is entered on a given frame but never changes after the first time there is input on that frame. The display portion looks like it's a simple matter of defining a new color at the top and inserting another if else statement in the display loop. I do not know if I am competent enough to take this on so please feel free to implement this yourself if this idea sounds interesting but if you're not interested in doing this at all yourself just let me know and I'll try to tackle it myself (although I make no guarantees :). Thanks again for this awesome script - as noted I plan on writing a very concise user guide to tighten up what's displayed to the user when they run the script. A.C. ******
I was laid off in May 2023 and could use support via Patreon or onetime donations as I work on TASBot Re: and TASBot HD. I'm dwangoAC, part of the senior staff of TASVideos as the Senior Ambassador and BDFL of the TASBot community; I post TAS content on YouTube.com/dwangoAC based on livestreams from Twitch.tv/dwangoAC.
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
Hmm, this feature, keeping a second list of input that is a backup of sorts, seems like it wouldn't be too complicated to implement. Recording to a separate list seems trivial. One tricky thing would be how to display it. Another is how to decide whether to transfer one to another. For recording, the function that catches the input can check for certain options or whether the backup even exists for the given frame. Should be rather easy to just copy what it sees to the backup. Displaying it... It can get a bit messy... In this case, there are technically four (or six, if you count the orange I use for list-disabled buttons) states of buttons: both off, main, backup, and main&backup. I have shades of blue completely unused, I can try seeing if mixing in cyan (main and backup) or purple (Backup, but not main) still keeps things intuitive. I can also have a line on the side indicating if there exists differences between main and backup, and possibly which one it's using. And how should I work at transferring one list to another? There's no mechanism in there to detect where the actual input begins (and it's possible, with various different savestates ready beforehand, to have several separate spots of input recorded). I'm thinking that transfers between the two lists will only take place at the frame you're at, since that's easiest on me, and I can see a fairly decent way I can deal with it. I'll mess around with things on my end, and let you know what I come up with. It seems like quite a good idea to try.
Post subject: Perhaps I should stick random messages here...
Editor, Skilled player (1172)
Joined: 9/27/2008
Posts: 1085
Well, the backup feature is added in. Feels a whole lot less like a prototype than the one I sent by IRC. Still could do with better coloring and some useful instructions. Should be much easier to change some of the colors, so play around with them and see how they fit in. ... Alas, poor numpad. You sure take a heavy burden from my default button scheme.