spenibus.net
2019-07-10 19:57:32 GMT

PC steering wheel viewer update, now with gearbox

You can check the [original post][1]. Just a little code update, now with the gearbox, pure CSS rendering, adaptive resolution and pixel perfect alignment. Demo ---- <center><iframe width="560" height="315" src="https://www.youtube.com/embed/vJhXbPwUJ-w" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></center> wheel.html ---------- ```` <!DOCTYPE html> <html> <head> <title>Wheel</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <script src="wheel.js" type="text/javascript"></script> <link href="wheel2.css" rel="stylesheet" type="text/css" /> <link href="wheelScalable.css" rel="stylesheet" type="text/css" /> </head> <body class="index"> <div id="viewer"> <span id="unitRef"></span> <span class="viewerCol"> <span id="wheel"> <span id="wheelA"> <span id="wheelB"> <span id="wheelC"> <span class="partA"></span> <span class="partB"></span> <span class="partC"></span> </span> </span> <span id="wheelTopNotch"></span> </span> </span> </span> <span class="viewerCol"> <span id="gears"> <span id="gearRowBack"><span></span></span> <span id="gearCol1" class="gearCol forward"><span></span></span> <span id="gearCol2" class="gearCol forward"><span></span></span> <span id="gearCol3" class="gearCol forward"><span></span></span> <span id="gearCol4" class="gearCol reverse"><span></span></span> <span id="gearColNegative1" class="gearColNegative gearColNegativeTop "><span></span></span> <span id="gearColNegative2" class="gearColNegative gearColNegativeTop "><span></span></span> <span id="gearColNegative3" class="gearColNegative gearColNegativeTop "><span></span></span> <span id="gearColNegative4" class="gearColNegative gearColNegativeBottom"><span></span></span> <span id="gearColNegative5" class="gearColNegative gearColNegativeBottom"><span></span></span> <span id="gearColNegative6" class="gearColNegative gearColNegativeBottom"><span></span></span> <span id="gearRowFront"><span></span></span> <span id="gearStick"><span class="gearN"></span></span> </span> <span id="pedals"> <span id="clutch" class="pedal"><span><span></span></span></span> <span id="brake" class="pedal"><span><span></span></span></span> <span id="throttle" class="pedal"><span><span></span></span></span> </span> </span> </div> <div> <legend><input type="checkbox" id="showDisplay"> Show device data</legend> </div> <div id="dataDisplay">waiting for input</div> </body> </html> ```` wheelScalable.css --------- ```` body { background-color:transparent; margin:0; padding:0; } #unitRef { display:block; width:1vw; height:1vw; background-color:green; position:fixed; left:-2vw; } #viewer { display:inline-block; height:50vw; background-color:rgba(0,0,0,0.2); font-size:0; white-space:nowrap; border-bottom:1vw red solid; } .viewerCol { display:inline-block; vertical-align:bottom; height:100%; } /******************************************************************************/ #wheel_old { display:inline-block; background-color:rgba(0,255,0,0.2); height:46vw; margin:2vw; display:none; } #wheel { display:inline-block; position:relative; } #wheelTopNotch { width:2vw; height:4vw; background-color:#468; position:absolute; left:18vw; top: -1vw; } #wheelA { display:inline-block; width:38vw; height:38vw; margin:4vw; border:1vw solid #468; border:1vw solid black; border-radius:22vw; overflow:hidden; position:relative; } #wheelB { margin:-1vw; display:inline-block; width:34vw; height:34vw; border:3vw solid #ACF; border:3vw solid #AAA; border-radius:22vw; overflow:hidden; position:relative; } #wheelC { margin:-1vw; display:inline-block; width:32vw; height:32vw; border:2vw solid #468; border:2vw solid black; border-radius:22vw; overflow:hidden; position:relative; } #wheel .partA { display:inline-block; width:60vw; height:60vw; border:5vw solid #468; border-radius:35vw; position:absolute; left:-19vw; top: 15vw; } #wheel .partB { display:inline-block; background-color:#468; width:8vw; height:20vw; position:absolute; left:12vw; top: 16vw; } /******************************************************************************/ #gears { display:inline-block; margin:2vw 2vw 0vw 0vw; position:relative; } .gearCol { display:inline-block; vertical-align:bottom; width:4vw; height:18vw; margin:1vw; background-color:black; border-radius:2vw; position:relative; } .gearCol > span { display:inline-block; vertical-align:top; background-color:#AAA; width:2vw; height:16vw; margin:1vw; border-radius:1vw; } #gearRowBack { display:block; background-color:black; width:22vw; height:4vw; border-radius:2vw; position:absolute; left:1vw; top:8vw; } #gearRowFront { display:block; background-color:#AAA; width:20vw; height:2vw; border-radius:2vw; position:absolute; left:2vw; top:9vw; } #gearCol4 { height:11vw; } #gearCol4 > span { height:9vw; } .gearColNegative { position:absolute; display:inline-block; border:1vw solid #AAA; } .gearColNegative > span { width:2vw; height:4vw; display:inline-block; border:1vw solid black; } .gearColNegativeTop ,.gearColNegativeTop > span { border-radius: 0vw 0vw 20vw 20vw; border-top:none; } .gearColNegativeBottom ,.gearColNegativeBottom > span { border-radius: 20vw 20vw 0vw 0vw; border-bottom:none; } #gearColNegative1 { top:4vw; left:3vw; } #gearColNegative2 { top:4vw; left:9vw; } #gearColNegative3 { top:4vw; left:15vw; border-right:none; border-radius: 0vw 0vw 0vw 20vw; } #gearColNegative3 > span { border-right:none; border-radius: 0vw 0vw 0vw 20vw; width:1vw; } #gearColNegative4 { top:10vw; left:3vw; } #gearColNegative5 { top:10vw; left:9vw; } #gearColNegative6 { top:10vw; left:15vw; } #gearStick { display:block; background-color:#246; width: 6vw; height:6vw; border-radius:3vw; position:absolute; top: 7vw; left:6vw; } #gearStick > span { display:block; background-color:#ACF; width:4vw; height:4vw; border-radius:2vw; margin:1vw; } #gearStick.gearF1 { top: 0vw; left:0vw; } #gearStick.gearF2 { top: 14vw; left:0vw; } #gearStick.gearF3 { top: 0vw; left:6vw; } #gearStick.gearF4 { top: 14vw; left:6vw; } #gearStick.gearF5 { top: 0vw; left:12vw; } #gearStick.gearF6 { top: 14vw; left:12vw; } #gearStick.gearR1 { top: 14vw; left:18vw; } /******************************************************************************/ #pedals { display:block; margin:2vw 2vw 0vw 0vw; } .pedal { display:inline-block; vertical-align:bottom; background-color:#AAA; width:4vw; height:22vw; margin:0 1vw; border:1vw solid black; } .pedal > span { display:inline-block; vertical-align:top; background-color:#AAA; width:100%; height:50%; } #throttle { background-color:#0A0; } #brake { background-color:#C00; } #clutch { background-color:#FA0; } ```` wheel.js -------- ```` /******************************************************************************* pc steering wheel animated viewer probably firefox only 2019-08-10 19:55 +0000 /******************************************************************************/ console.log('wheel'); let interval; /* // chrome compatibility if(!('ongamepadconnected' in window)) { console.log('no event'); interval = setInterval( z=>{ console.log('polling'); window.dispatchEvent(new CustomEvent('gamepadconnected')) }, 1000); } */ let device; let dataDisplay; let showDisplay; // mapping the axes let axisIndexWheel = 0; let axisIndexThrottle = 1; let axisIndexBrake = 2; let axisIndexClutch = 3; let axisIndexClutchAlt = 5; // mapping the buttons let btnIndexR1 = 11; let btnIndexF1 = 12; let btnIndexF2 = 13; let btnIndexF3 = 14; let btnIndexF4 = 15; let btnIndexF5 = 16; let btnIndexF6 = 17; // elements references let elemWheel; let elemThrottle; let elemBrake; let elemClutch; let elemGearStick; // misc let axisWheelFullRotation = 900; // degrees let loopStartTime = (new Date()).getTime(); let loopEndTime = (new Date()).getTime(); //let renderRate = 1000/100; //****************************************************************************** window.addEventListener('DOMContentLoaded', function(e) { console.log('loaded'); // style: convert vw into px for scalability with pixel perfect alignment let xhr = new XMLHttpRequest(); xhr.open('GET', './wheelScalable.css', true); xhr.responseType = 'text'; xhr.onload = function(data){ let val = data.target.responseText; // get unit ref for pixel perfect consistency let unit = Math.floor( window.getComputedStyle( document.getElementById('unitRef') ) .getPropertyValue('width') .match(/([\d\.]+)/)[1] ); let r = /([^\d])([\d\.]+)(\s*vw)/g; let m; while(m = r.exec(val)) { val = val.replace( new RegExp(m[0], 'g') ,m[1]+ unit * parseInt(m[2]) +'px' ); } let s = document.createElement('style'); s.textContent = val; document.head.appendChild(s); } xhr.send(); dataDisplay = document.getElementById('dataDisplay'); showDisplay = document.getElementById('showDisplay'); elemWheel = document.getElementById('wheel'); elemThrottle = document.querySelector('#throttle > span'); elemBrake = document.querySelector('#brake > span'); elemClutch = document.querySelector('#clutch > span'); elemGearStick = document.querySelector('#gearStick'); /* window.addEventListener('gamepadconnected', function(e) { if(e.gamepad.id.match(/g920.*wheel/i)) { wheelDevice = e.gamepad; gameLoop(); return; } }); */ gameLoop(); }); //****************************************************************************** function gameLoop() { /* framerate limiter * loopStartTime = (new Date()).getTime(); let delta = loopStartTime-loopEndTime; if(delta < renderRate) { requestAnimationFrame(gameLoop); return; } console.log('render delta: '+delta+'ms') */ wheelDevice = null; let gps = navigator.getGamepads(); for(let gp of gps) { if(gp.id.match(/g920.*wheel/i)) { wheelDevice = gp; break; } } if(wheelDevice) { //******************************************************* data gathering let axisValueWheel = wheelDevice.axes[axisIndexWheel]; let axisValueThrottle = wheelDevice.axes[axisIndexThrottle]; let axisValueBrake = wheelDevice.axes[axisIndexBrake]; //let axisValueClutch = wheelDevice.axes[axisIndexClutch]; let axisValueClutch = wheelDevice.axes[axisIndexClutch] || wheelDevice.axes[axisIndexClutchAlt]; // convert wheel axis to angle axisValueWheel = Math.round(axisWheelFullRotation / 2 * axisValueWheel * 1000) / 1000; // normalize pedals axes to range 0-1 axisValueThrottle = (axisValueThrottle * -1 + 1) / 2; axisValueBrake = (axisValueBrake * -1 + 1) / 2; axisValueClutch = (axisValueClutch * -1 + 1) / 2; //********************************************************* data display let debugText = '-'; if(showDisplay.checked) { debugText = '\n[id]\n '+wheelDevice.id+'\n'; debugText += '\n[Axes]'; wheelDevice.axes.forEach((a,i)=>{ debugText += "\n "+i+' : '+a; }); debugText += '\n\n[Buttons]'; wheelDevice.buttons.forEach((b,i)=>{ debugText += "\n "+i+' : pressed:'+b.pressed+' touched:'+b.touched; }); debugText = '<pre>\n[Computed]\n' +' wheel: '+axisValueWheel +'deg\n' +' thottle: '+axisValueThrottle+'\n' +' brake: '+axisValueBrake +'\n' +' clutch: '+axisValueClutch +'\n' +'</pre>' +'<pre>'+debugText+'</pre>' ; } dataDisplay.innerHTML = debugText; //************************************************************** visuals axisValueWheel = 'rotate('+axisValueWheel+'deg)'; if(elemWheel.style.transform != axisValueWheel) { elemWheel.style.transform = axisValueWheel; } axisValueThrottle = (100 - axisValueThrottle * 100)+'%'; if(elemThrottle.style.height != axisValueThrottle) { elemThrottle.style.height = axisValueThrottle; } axisValueBrake = (100 - axisValueBrake * 100)+'%'; if(elemBrake.style.height != axisValueBrake) { elemBrake.style.height = axisValueBrake; } axisValueClutch = (100 - axisValueClutch * 100)+'%'; if(elemClutch.style.height != axisValueClutch) { elemClutch.style.height = axisValueClutch; } let gearStickClass = '' +(wheelDevice.buttons[btnIndexR1].pressed ? ' gearR1' : '') +(wheelDevice.buttons[btnIndexF1].pressed ? ' gearF1' : '') +(wheelDevice.buttons[btnIndexF2].pressed ? ' gearF2' : '') +(wheelDevice.buttons[btnIndexF3].pressed ? ' gearF3' : '') +(wheelDevice.buttons[btnIndexF4].pressed ? ' gearF4' : '') +(wheelDevice.buttons[btnIndexF5].pressed ? ' gearF5' : '') +(wheelDevice.buttons[btnIndexF6].pressed ? ' gearF6' : '') ; if(elemGearStick.classList != gearStickClass) { elemGearStick.classList = gearStickClass } } //loopEndTime = (new Date()).getTime(); requestAnimationFrame(gameLoop); /* let angle = Math.round(wheelFullRotation / 2 * axis0value * 1000) / 1000; nodeAxis0.textContent = axis0value+' : '+angle+'deg'; wheelGfx.style.transform = 'rotate('+angle+'deg)'; */ //transform: rotate(20deg); /* var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []); if (!gamepads) { return; } var gp = gamepads[0]; if (buttonPressed(gp.buttons[0])) { b--; } else if (buttonPressed(gp.buttons[2])) { b++; } if (buttonPressed(gp.buttons[1])) { a++; } else if (buttonPressed(gp.buttons[3])) { a--; } ball.style.left = a * 2 + "px"; ball.style.top = b * 2 + "px"; */ } ```` [1]: http://spenibus.net/b/p/F/PC-steering-wheel-viewer-prototype-in-html-javascript
2019-03-08 01:11:36 GMT

Extracting the audio from a Metal Gear Solid Ground Zeroes cutscene (FSM file)

I couldn't find the piece of music playing when the helicopters are taking off from camp omega, neither on soundtracks, be they regular or extended, nor in the game audio files. You can hear it here at 7m40s: <center> <iframe width="560" height="315" src="https://www.youtube.com/embed/G80bgC6koq8?start=460" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> </center> My last hope was to rip the audio from the cutscene itself, hoping effects and music were mixed dynamically. They weren't. Just making the code public for posterity. Using php (command line) 'cause I'm a weirdo. <?php /******************************************************************************* Rips the audio from a Metal Gear Solid Ground Zeroes cutscene (FSM file) http://metalgearmodding.wikia.com/wiki/FSM ground-zeroes-fsm-sound-extractor │ ground-zeroes-fsm-sound-extractor.phpcli │ ├───files └───tools ├───revorb │ revorb.exe │ └───ww2ogg packed_codebooks_aoTuV_603.bin ww2ogg.exe *******************************************************************************/ echo "Start\n"; $fileIn = './files/p11_010100_000.fsm'; $fileOut = $fileIn.'.wem'; $bufSize = 256*1024; $chunksPos = array(); $fh = fopen($fileIn, "rb"); //********************************************** build sound chunks offsets list echo "Building chunks list\n"; while($fh && !feof($fh)) { $bufPos = ftell($fh); $buf = fread($fh, $bufSize); $off = 0; while(true) { $pos = strpos($buf, 'SND ', $off); if($pos === false) { break; } $off = $pos+1; $chunksPos[] = $bufPos + $pos; } } echo "Found chunks: ".count($chunksPos)."\n"; //*************************************************** recompose wwise audio file echo "Recomposing sound file\n"; $file = 0; file_put_contents($fileOut, ''); foreach($chunksPos as $pos) { ++$file; // first chunk has a larger header $headerSize = ($file === 1) ? 32 : 16; fseek($fh, $pos); $buf = fread($fh, $headerSize); $data = unpack( 'A4sig/Vclen/Vs1/Vs2/H*', $buf ); $chunk = stream_get_line($fh, $data['clen'] - $headerSize); file_put_contents( $fileOut, $chunk, FILE_APPEND ); } fclose($fh); //********************************************************* convert wwise to ogg echo "Converting sound file\n"; `"./tools/ww2ogg/ww2ogg.exe" "$fileOut" -o "$fileOut.ww2ogg.ogg" --pcb "./tools/ww2ogg/packed_codebooks_aoTuV_603.bin"`; //***************************************************************** optimize ogg echo "Optimizing sound file\n"; `"./tools/revorb/revorb.exe" "$fileOut.ww2ogg.ogg" "$fileOut.revorb.ogg"`; echo "done\n";
2019-02-27 01:11:30 GMT

Restoring the upholstery of a desk chair

It would probably have been cheaper to buy a new one, but much less fun. There isn't much to explain here, it's cutting foam and sewing fabric. You just stab the fabric with some thread and put the foam inside, quite easy. There is also some stapling, sure. Just marvel at the pictures, unlike me, who can only see the flaws. Part 1, the armrests -------------------- <center> [![](http://spenibus.net/f/t/cY)](http://spenibus.net/f/g/cY) [![](http://spenibus.net/f/t/cZ)](http://spenibus.net/f/g/cZ) [![](http://spenibus.net/f/t/d0)](http://spenibus.net/f/g/d0) [![](http://spenibus.net/f/t/d1)](http://spenibus.net/f/g/d1) [![](http://spenibus.net/f/t/d2)](http://spenibus.net/f/g/d2) [![](http://spenibus.net/f/t/d3)](http://spenibus.net/f/g/d3) [![](http://spenibus.net/f/t/cX)](http://spenibus.net/f/g/cX) </center> Part 2, the headrest -------------------- <center> [![](http://spenibus.net/f/t/d4)](http://spenibus.net/f/g/d4) [![](http://spenibus.net/f/t/d5)](http://spenibus.net/f/g/d5) [![](http://spenibus.net/f/t/d6)](http://spenibus.net/f/g/d6) [![](http://spenibus.net/f/t/d7)](http://spenibus.net/f/g/d7) [![](http://spenibus.net/f/t/d9)](http://spenibus.net/f/g/d9) </center> Part 3, the backrest -------------------- <center> [![](http://spenibus.net/f/t/da)](http://spenibus.net/f/g/da) [![](http://spenibus.net/f/t/db)](http://spenibus.net/f/g/db) [![](http://spenibus.net/f/t/dc)](http://spenibus.net/f/g/dc) [![](http://spenibus.net/f/t/dd)](http://spenibus.net/f/g/dd) [![](http://spenibus.net/f/t/de)](http://spenibus.net/f/g/de) [![](http://spenibus.net/f/t/df)](http://spenibus.net/f/g/df) [![](http://spenibus.net/f/t/dh)](http://spenibus.net/f/g/dh) [![](http://spenibus.net/f/t/dg)](http://spenibus.net/f/g/dg) </center> Part 4, the seat ---------------- <center> [![](http://spenibus.net/f/t/dp)](http://spenibus.net/f/g/dp) [![](http://spenibus.net/f/t/di)](http://spenibus.net/f/g/di) [![](http://spenibus.net/f/t/dj)](http://spenibus.net/f/g/dj) [![](http://spenibus.net/f/t/dk)](http://spenibus.net/f/g/dk) [![](http://spenibus.net/f/t/dl)](http://spenibus.net/f/g/dl) [![](http://spenibus.net/f/t/dm)](http://spenibus.net/f/g/dm) [![](http://spenibus.net/f/t/do)](http://spenibus.net/f/g/do) [![](http://spenibus.net/f/t/dn)](http://spenibus.net/f/g/dn) </center> All done -------- [![](http://spenibus.net/f/t/dq)](http://spenibus.net/f/g/dq) The end.