Post subject: Multitrack script for DeSmuME. Nearly there! (31 KB code)
Editor, Skilled player (1203)
Joined: 9/27/2008
Posts: 1085
Here's my Work-In-Progress script. Some stuff I planned is partly implemented but not working. There are ways to switch options around in massive quantities, but only a few of them can be switched individually at this moment. I'm using a way to handle input similar to what I did in this subtitles script. It works as a repeater for certain functions just fine. This is yet another rewrite of my Multitrack script. Hopefully, I've organized things even better than before. The rewrite is incomplete, but keeping it private wouldn't generate any interest in it (not as though posting it publicly here did any better...), so here it is now: Download Multitrack_DeSmuME.lua
Language: lua

--Multitracker v3 --Wow, I rewrite this script a LOT local PresetSwitch= "E" -- Switch between a few different presets --Display local solid= "numpad*" -- Make the display less local clear= "numpad/" -- or more transparant. local DispUp= "numpad8" local DispDn= "numpad2" -- For moving the local DispRt= "numpad6" -- display around. local DispLf= "numpad4" local MoreFutr= "numpad3" local LessFutr= "numpad9" -- These will determine local MorePast= "numpad7" -- how many frames you local LessPast= "numpad1" -- want to display. local ResetFP= "numpad5" local opt= "numpad+" -- For changing control options local Insert= "A" local Delete= "S" local BackupReadSwitch= "R" -- Revert to backup copy local BackupWriteSwitch= "F" -- Copy input into backup local BackupDisplaySwitch= "V" local MessageDuration= 400 local OptionPreset= { {name= "Edit Mode", DispX= 190, DispY= -70, Past= -12, Future= 12, opaque= 1, TJ= true , FJ= true , RJ= true , SJ= true , CS= true , RS= true , SS= true , StyP= -30, StyF= 30}, {name= "Standby", DispX= 150, DispY= -10, Past= 0, Future= 0, opaque= 0, TJ= true , FJ= true , RJ= false, SJ= false, CS= true , RS= false, SS= false, StyP= 0, StyF= 0}, {name= "Joypad Overwrite", DispX= 190, DispY= -90, Past= -12, Future= 16, opaque= 1, TJ= true , FJ= true , RJ= false, SJ= false, CS= true , RS= true , SS= true , StyP= -60, StyF= 60}, {name= "Stylus Overwrite", DispX= 190, DispY= -70, Past= -6, Future= 6, opaque= 1, TJ= true , FJ= true , RJ= true , SJ= true , CS= true , RS= false, SS= false, StyP= -10, StyF= 10} } local shade= 0x00000080 local white= 0xFFFFFFFF local red= 0xFF2000FF local green= 0x00FF00FF local mint= 0xC0FFC0FF local blue= 0x0040FFFF local orange=0xFFC000FF local yellow=0xFFFF00FF local cyan= 0x00FFFFFF local purple=0x8020FFFF local redish=0xC00000FF local ltgrey=0xC0C0C0FF local Clr= { white ,red ,orange,green, white ,red ,orange,green, mint ,purple,yellow,cyan, ltgrey,redish,orange,green, --Watermark ltgrey,redish,orange,green, mint ,purple,yellow,cyan } local WtrInterval= 10 --############################################################################# --############################################################################# --Global necessities local NULLTBL= {} -- I prefer not constructing a new table every time. local function NULLFN() end --You never know when you need to call nothing. local btn={"left","up","right","down","A","B","X","Y","L","R","start","select", "lid","debug"} local ThisInput= {} local ThisStylus={} local JoypadList= {} local StylusList= {} local BackupList= {} --Joypad only local ShowBackup= true local BackupOverride= false local JoypadInsert= 0 local JoypadBuffer= {} local StylusInsert= 0 local StylusBuffer= {} --local CopyBuffer_J= {} --local CupyBuffer_S= {} local TrueSwitch, FalseSwitch= {},{} local ReadJoy, StickyJoy= {},{} local DispX,DispY , Past,Future , StyP,StyF , Opaque local ControlStylus, ReadStylus, StickyStylus local WID , HGT= 256 , 192 local fc, lastfc= 0, 0 --***************************************************************************** local function UpdateFC() lastfc= fc; fc= movie.framecount() end --***************************************************************************** --My global "clock", so to speak. --***************************************************************************** local function Within(V,l,h) return (V >= l) and (V <= h) end local function Limits(V,l,h) return math.max(math.min(V,h),l) end --***************************************************************************** --Range checking and range limiting functions. local MsgTmr= 0 local MsgTxt= "" local MsgClr= white --***************************************************************************** local function DisplayMessage() --***************************************************************************** if MsgTmr < MessageDuration then gui.text(1,-190,MsgTxt,MsgClr) MsgTmr= MsgTmr+1 end end --***************************************************************************** local function SetMessage(str,clr) --***************************************************************************** if not str then MsgTmr= math.huge; return end MsgTmr= 0 MsgTxt= str if clr then MsgClr= clr else MsgClr= white end end --############################################################################# --############################################################################# --Handle outside scripts --require("YummyButton") --local CreateBtn= {text= "Y", } --for i= 1, #btn do --end --############################################################################# --############################################################################# --Joypad handler --***************************************************************************** local function JoyToNum(Joys) -- Expects a table containing joypad buttons --***************************************************************************** -- Returns a number representing button presses. -- These numbers are the primary storage for this version of this script. local joynum= 0 for i= 1, #btn do if Joys[btn[i]] then joynum= bit.bor(joynum, bit.lshift(1,i)) end end return joynum end --***************************************************************************** local function ReadJoynum(inp, button) -- Expects... Certain numbers! --***************************************************************************** -- Returns true or false. True if the indicated button is pressed -- according to the input. False otherwise. return ( bit.band(inp , bit.lshift( 1, button )) ~= 0 ) end --***************************************************************************** local function LoadJoypad() --***************************************************************************** local joys= JoypadList[fc] if BackupOverride == "read" then joys= BackupList[fc] end for i= 1, #btn do if joys and ReadJoy[i] and ReadJoynum(joys, i) then ThisInput[btn[i]]= TrueSwitch[i] else ThisInput[btn[i]]= FalseSwitch[i] end end end --***************************************************************************** local function RegBeforeHandleJoypad() --***************************************************************************** --DeSmuME, emu.registerbefore. Not portable across emulators. --If the "invert" option existed like in FCEUX, this function would be one line local joys= joypad.peek() for i= 1, #btn do if type(ThisInput[btn[i]]) == "string" then joys[btn[i]]= not joys[btn[i]] else joys[btn[i]]= ThisInput[btn[i]] end end joypad.set(joys) end --***************************************************************************** local function RegAfterHandleJoypad() --***************************************************************************** --Just say fc is properly updated by now. local joys= JoyToNum(joypad.read()) JoypadList[fc-1]= joys if (not BackupList[fc-1]) or BackupOverride == "write" then BackupList[fc-1]= joys end LoadJoypad() end Jo, LastJo= joypad.peek(), joypad.peek() --***************************************************************************** local function ScanJoypad() --***************************************************************************** LastJo= Jo Jo= joypad.peek() for i= 1, #btn do if (not Jo[btn[i]]) and LastJo[btn[i]] and StickyJoy[i] then if ThisInput[btn[i]] then ThisInput[btn[i]]= FalseSwitch[i] else ThisInput[btn[i]]= TrueSwitch[i] end end end end --############################################################################# --############################################################################# --Stylus handler --***************************************************************************** local function StyToNum(Stys) -- Expects a table containing stylus control --***************************************************************************** -- Returns a number representing stylus position. -- Low numbers are x, high is y. if not Stys.touch then return false end return Stys.x + bit.lshift(Stys.y,8) end --***************************************************************************** local function ReadStynum(inp) --***************************************************************************** -- Returns two values, x,y. If not pressed, returns false,false instead. if not inp then return false , false end return bit.band(inp,0xFF) , bit.rshift(bit.band(inp,0xFF00),8) end --***************************************************************************** local function ClearStylus() --***************************************************************************** ThisStylus.touch= false ThisStylus.x= 0 ThisStylus.y= 0 end --***************************************************************************** local function LoadStylus() --***************************************************************************** local x,y= ReadStynum(StylusList[fc]) if x then ThisStylus.touch= true; ThisStylus.x= x; ThisStylus.y= y else ClearStylus() end end --***************************************************************************** local function RegBeforeHandleStylus() --***************************************************************************** local Stys= stylus.peek() if not ControlStylus then Stys.touch= false end if (not Stys.touch) and ThisStylus.touch then stylus.set(ThisStylus) end end --***************************************************************************** local function RegAfterHandleStylus() --***************************************************************************** --fc is updated by this point. Joy. StylusList[fc-1]= StyToNum(stylus.get()) if ReadStylus then LoadStylus() end end --***************************************************************************** local function ScanStylus() --***************************************************************************** if StickyStylus then local Poke= stylus.peek() if Poke.touch then ThisStylus.x= Poke.x ThisStylus.y= Poke.y ThisStylus.touch= true end end end --############################################################################# --############################################################################# --Display --***************************************************************************** local function FakeBox(x1,y1,x2,y2,fill) --***************************************************************************** --Gets around DeSmuME's problem of boxes that cross over top and touch screen. --Fills only. No support for borders, yet. local test = (y1 < 0) if (y2 < 0) then test= not test end if test then gui.box(x1,y1,x2, 0,fill) gui.box(x1, 0,x2,y2,fill) else gui.box(x1,y1,x2,y2,fill) end end --***************************************************************************** local function FakeBox2(x1,y1,x2,y2,fill,border) --***************************************************************************** local test = (y1 < 0) if (y2 < 0) then test= not test end if test then gui.line(x1,y1,x2,y1,border) gui.line(x1,y2,x2,y2,border) if not within(y1,y2-1,y2+1) then -- ... Forget it... end else gui.box(x1,y1,x2,y2,fill,border) end end --***************************************************************************** local Draw= {} --***************************************************************************** function Draw.left(x,y,color) -- ## gui.line(x+1,y ,x+2,y ,color) -- # gui.line(x+1,y+2,x+2,y+2,color) -- ## gui.pixel(x ,y+1,color) end function Draw.up(x,y,color) -- # gui.line(x ,y+1,x ,y+2,color) -- # # gui.line(x+2,y+1,x+2,y+2,color) -- # # gui.pixel(x+1,y ,color) end function Draw.right(x,y,color) -- ## gui.line(x ,y ,x+1,y ,color) -- # gui.line(x ,y+2,x+1,y+2,color) -- ## gui.pixel(x+2,y+1,color) end function Draw.down(x,y,color) -- # # gui.line(x ,y ,x ,y+1,color) -- # # gui.line(x+2,y ,x+2,y+1,color) -- # gui.pixel(x+1,y+2,color) end function Draw.A(x,y,color) -- ### gui.box(x ,y ,x+2,y+1,color) -- ### gui.pixel(x ,y+2,color) -- # # gui.pixel(x+2,y+2,color) end function Draw.B(x,y,color) -- ### gui.line(x ,y ,x+2,y ,color) -- # gui.line(x ,y+2,x+2,y+2,color) -- ### gui.pixel(x ,y+1,color) end function Draw.X(x,y,color) -- # # gui.line(x ,y ,x+2,y+2,color) -- # gui.pixel(x ,y+2,color) -- # # gui.pixel(x+2,y ,color) end function Draw.Y(x,y,color) gui.line(x+1,y+1,x+1,y+2,color) -- # # gui.pixel(x ,y ,color) -- # gui.pixel(x+2,y ,color) -- # end function Draw.L(x,y,color) -- # gui.line(x ,y+2,x+2,y+2,color) -- # gui.line(x ,y ,x ,y+1,color) -- ### end function Draw.R(x,y,color) gui.line(x ,y ,x ,y+2,color) -- ## gui.line(x+1,y ,x+1,y+1,color) -- ## gui.pixel(x+2,y+2,color) -- # # end function Draw.start(x,y,color) -- # gui.line(x+1,y ,x+1,y+2,color) -- ### gui.pixel(x ,y+1,color) -- # gui.pixel(x+2,y+1,color) end function Draw.select(x,y,color) -- ### gui.line(x ,y ,x+2,y ,color) -- # # gui.line(x ,y+2,x+2,y+2,color) -- ### gui.pixel(x ,y+1,color) gui.pixel(x+2,y+1,color) end function Draw.lid(x,y,color) -- # gui.line(x ,y ,x+2,y+2,color) -- ## gui.line(x ,y+1,x+1,y+2,color) -- ## end function Draw.debug(x,y,color) -- # gui.pixel(x+1,y ,color) gui.pixel(x ,y+2,color) -- # # gui.pixel(x+2,y+2,color) end function Draw.stylus(x,y,color) -- ### gui.line(x ,y ,x+2,y ,color) -- # gui.line(x ,y+2,x+2,y+2,color) -- ### gui.pixel(x+1,y+1,color) end --***************************************************************************** local function ShowFrame(x,y,input) --***************************************************************************** --DeSmuME specific display. It has no separate players to scan. --input = Absolute frame local WtrMrk= 1 if input%WtrInterval == 0 then WtrMrk= 13 end x= x-4 for i= 1, #btn do local clrnum= WtrMrk --Display is rough... Use this color, but if Tuesday, that color instead! if input == fc then local test= ThisInput[btn[i]] if test == "inv" then test= not Jo[btn[i]] elseif test == nil then test= Jo[btn[i]] end if test then clrnum= clrnum+3 else clrnum= clrnum+1 end elseif JoypadList[input] then if ReadJoynum(JoypadList[input], i) then if (input > fc) and not ReadJoy[i] then clrnum= clrnum+2 -- Ignored else clrnum= clrnum+3 -- Pressed end else clrnum= clrnum+1 -- Not pressed end end if ShowBackup and BackupList[input] then if ReadJoynum(BackupList[input], i) then clrnum= clrnum+8 -- Oh, there's a backup else clrnum= clrnum+4 -- Backup not pressed end end Draw[btn[i]](x+i*4,y,Clr[clrnum]) end end local MaxRange= math.floor(HGT/2 - 3) local width= #btn*4 --***************************************************************************** local function JoypadDisplay() --***************************************************************************** local up, dn if Past <= -1 then up= DispY + Past*4 -3 dn= DispY + math.min(-1,Future)*4 +1 FakeBox(DispX,up,DispX+width,dn,shade) end if Future >= 1 then up= DispY + math.max(1,Past)*4 +1 dn= DispY + Future*4 +5 FakeBox(DispX,up,DispX+width,dn,shade) end if Past <= 0 and Future >= 0 then local color= blue for i= 1, #btn do if not JoypadList[fc] then color= yellow; break end local test = ThisInput[btn[i]] if ReadJoynum(JoypadList[fc], i) then test= not test end if test then color= green; break end end gui.box(DispX-1, DispY-2, DispX+width+1, DispY+4, shade,color) end local X= DispX+1 for i= Past, Future do local Y= DispY + i*4 if i < 0 then Y= Y-2 elseif i > 0 then Y= Y+2 end local FC= fc+i ShowFrame(X,Y,FC) if ShowBackup and BackupList[FC] then local color= green if (BackupList[FC] == JoypadList[FC]) then color= blue end gui.line(DispX-2,Y,DispX-2,Y+2,color) end end end --***************************************************************************** local function StylusDisplay1() --***************************************************************************** -- Pixel trail version -- Anyone with better ideas? It's not particularly easy to see these pixels. local x,y,color local cmp local tot if StyP == -1 then x,y= ReadStynum(StylusList[fc-1]) if x then gui.pixel(x,y,0xFF8000FF) end else cmp= (-256)/(StyP+1) tot= 0 for i= StyP, -1 do x,y= ReadStynum(StylusList[fc+i]) color= (0xFF00007F + math.floor(math.min(tot,0x80)) + bit.lshift(math.floor(math.max(tot-0x80,0)),16) ) if x then gui.pixel(x,y,color) end tot= tot+cmp end end if StyF == 1 then x,y= ReadStynum(StylusList[fc+1]) if x then gui.pixel(x,y,0x0080FFFF) end else cmp= (256)/(StyF-1) tot= 0 for i= StyF, 1, -1 do x,y= ReadStynum(StylusList[fc+i]) color= (0x0000FF7F + math.floor(math.min(tot,0x80)) + bit.lshift(math.floor(math.max(tot-0x80,0)),16) ) if x then gui.pixel(x,y,color) end tot= tot+cmp end end x,y= ReadStynum(StylusList[fc]) if x then gui.pixel(x,y,green) end if ThisStylus.touch then gui.pixel(ThisStylus.x,ThisStylus.y,white) end end --***************************************************************************** local function NormalizeDisplay() --***************************************************************************** DispX= Limits(DispX,2,WID-width-2) DispY= Limits(DispY,-HGT-Past*4+3,HGT-Future*4-6) end local btnswitches= {TrueSwitch, FalseSwitch, ReadJoy, StickyJoy} local TrueVals= { "inv" , nil , true , true } local Mat= {} --***************************************************************************** local function OptionsDisplay() --***************************************************************************** for i= 1, #btnswitches do for j= 1, #btn do local txt= "N" if btnswitches[i][j] == TrueVals[i] then txt= "Y" end gui.text(10*j, 8*i, txt) end end end --############################################################################# --############################################################################# --Handle user input local lastkeys, keys= input.get(), input.get() --***************************************************************************** local function UpdateKeys() lastkeys= keys; keys= input.get() end --***************************************************************************** local repeater= 0 local KF, RF --KeyFunction, RepeaterFunction --***************************************************************************** local function KeyReader() --***************************************************************************** UpdateKeys() for k,v in pairs(keys) do local ThisKey= k if keys.control then ThisKey= "c+" .. k end if not lastkeys[k] then repeater= 0 if KF[ThisKey] then KF[ThisKey]() end if RF[ThisKey] then RF[ThisKey]() end end if repeater >= 3 then if RF[ThisKey] then RF[ThisKey]() end end end repeater= repeater+1 end --***************************************************************************** local function MouseHandler() --***************************************************************************** --KeyReader should be called first, and that updates our keys already! if (not keys.leftclick) or (not lastkeys.leftclick) then return end if keys.control then DispX= DispX + keys.xmouse - lastkeys.xmouse DispY= DispY + keys.ymouse - lastkeys.ymouse else ScanStylus() end end --***************************************************************************** local function MouseHandler2() --***************************************************************************** if keys.rightclick and lastkeys.rightclick then DispX= DispX + keys.xmouse - lastkeys.xmouse DispY= DispY + keys.ymouse - lastkeys.ymouse end ScanStylus() end local KF1, KF2, RF1= {}, {}, {} ------------------------------------------------------------------------------- RF1[solid]= function() Opaque= math.min(Opaque + .125 , 1) end RF1[clear]= function() Opaque= math.max(Opaque - .125 , 0) end ------------------------------------------------------------------------------- RF1[DispUp]= function() DispY= DispY-1 end RF1[DispDn]= function() DispY= DispY+1 end RF1[DispRt]= function() DispX= DispX+1 end RF1[DispLf]= function() DispX= DispX-1 end ------------------------------------------------------------------------------- RF1[MoreFutr]= function() Future= Future+1; Past= math.max(Past,Future-MaxRange) end RF1[LessFutr]= function() Future= Future-1; Past= math.min(Past,Future) end RF1[MorePast]= function() Past = Past -1; Future= math.min(Future,Past+MaxRange) end RF1[LessPast]= function() Past = Past +1; Future= math.max(Future,Past) end KF1[ResetFP]= function() Future= 12; Past= -12 end ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- KF1.space= function() --Mostly a hack ... I'm out of joypad stuff... ------------------------------------------------------------------------------- if ThisStylus.touch then ClearStylus() else LoadStylus() end end if false then ------------------------------------------------------------------------------- KF1[BackupDisplaySwitch]= function() ------------------------------------------------------------------------------- end KF2[OptUp]= function() end KF2[OptDn]= function() end KF2[OptRt]= function() end KF2[OptLf]= function() end KF2[OptHit]= function() end end local HandleDisplay, UsualDisplay ------------------------------------------------------------------------------- KF1[opt]= function() ------------------------------------------------------------------------------- KF= KF2 RF= NULLTBL HandleDisplay= OptionsDisplay end --Cheap functions. Bad on runtime with enough frames stacked up. ------------------------------------------------------------------------------- KF1[Insert]= function() ------------------------------------------------------------------------------- local frame= fc while JoypadList[frame] do frame=frame+1 end for i= frame, (fc+1), -1 do JoypadList[i]= JoypadList[i-1] StylusList[i]= StylusList[i-1] end JoypadList[fc]= 0 StylusList[fc]= false LoadJoypad() LoadStylus() end ------------------------------------------------------------------------------- KF1[Delete]= function() ------------------------------------------------------------------------------- local i= fc while JoypadList[i] do JoypadList[i]= JoypadList[i+1] StylusList[i]= StylusList[i+1] i=i+1 end LoadJoypad() LoadStylus() end ------------------------------------------------------------------------------- KF1[BackupReadSwitch]= function() ------------------------------------------------------------------------------- if BackupOverride == "read" then BackupOverride= false SetMessage("End backup read",white) else BackupOverride= "read" SetMessage("Reading from backup list...",green) end end ------------------------------------------------------------------------------- KF1[BackupWriteSwitch]= function() ------------------------------------------------------------------------------- if BackupOverride == "write" then BackupOverride= false SetMessage("End backup overwrite",white) else BackupOverride= "write" SetMessage("Merging input into backup...",yellow) end end ------------------------------------------------------------------------------- KF1[BackupDisplaySwitch]= function() ShowBackup= not ShowBackup end ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- KF2[opt]= function() ------------------------------------------------------------------------------- KF= KF1 RF= RF1 HandleDisplay= UsualDisplay end local OC= 0 ------------------------------------------------------------------------------- KF1[PresetSwitch]= function() ------------------------------------------------------------------------------- OC= (OC % #OptionPreset)+1 SetMessage(OptionPreset[OC].name,green) DispX= OptionPreset[OC].DispX DispY= OptionPreset[OC].DispY Past= OptionPreset[OC].Past Future=OptionPreset[OC].Future Opaque=OptionPreset[OC].opaque StyP= OptionPreset[OC].StyP StyF= OptionPreset[OC].StyF if OptionPreset[OC].TJ then for i= 1, #btn do TrueSwitch[i]= "inv" end else for i= 1, #btn do TrueSwitch[i]= true end end if OptionPreset[OC].FJ then for i= 1, #btn do FalseSwitch[i]= nil end else for i= 1, #btn do FalseSwitch[i]= false end end for i= 1, #btn do ReadJoy[i]= OptionPreset[OC].RJ StickyJoy[i]= OptionPreset[OC].SJ end ControlStylus= OptionPreset[OC].CS ReadStylus= OptionPreset[OC].RS StickyStylus= OptionPreset[OC].SS LoadJoypad() LoadStylus() end KF1[PresetSwitch]() --############################################################################# --############################################################################# --Top level routines to actually run my stuff. KF= KF1 RF= RF1 --***************************************************************************** function UsualDisplay() --***************************************************************************** if Opaque > 0 then gui.opacity(Opaque) NormalizeDisplay() JoypadDisplay() StylusDisplay1() gui.opacity(1) end end HandleDisplay= UsualDisplay --***************************************************************************** local function UnpausedSanityScan() --***************************************************************************** --For unpaused stateload with this script running. --Ensures we have the proper input when the frame count spontaneously changes. UpdateFC() if fc ~= lastfc then if BackupOverride then SetMessage("Auto-ended backup read/write",white) BackupOverride= false end LoadJoypad() LoadStylus() end end --***************************************************************************** local function LoadFix() --***************************************************************************** local _fc= movie.framecount() if Within(_fc,fc,fc+1) then return end UpdateFC() if BackupOverride then SetMessage("Auto-ended backup read/write",white) BackupOverride= false end LoadJoypad() LoadStylus() end --***************************************************************************** local function RegBefore() --***************************************************************************** UnpausedSanityScan() RegBeforeHandleJoypad() RegBeforeHandleStylus() end emu.registerbefore(RegBefore) --***************************************************************************** local function RegAfter() --***************************************************************************** UpdateFC() RegAfterHandleJoypad() RegAfterHandleStylus() if keys.space then KF1.space() end --Hack end emu.registerafter(RegAfter) --***************************************************************************** local function RegGui() --***************************************************************************** LoadFix() KeyReader() MouseHandler2() ScanJoypad() HandleDisplay() DisplayMessage() end gui.register(RegGui)
I found a glitch with DeSmuME's frameadvance. Have this as the only piece of code: while true do emu.frameadvance() end Now run the one-line script and use the frame advance key. The emulator's frame counter will increment, but no actual emulation will take place. Pausing and unpausing the emulation still works fine, however. The window header of the version I'm using reads DeSmuME 0.9.6 x86.