Skip to content

Instantly share code, notes, and snippets.

@andrewluetgers
Created February 12, 2019 17:15
Show Gist options
  • Save andrewluetgers/9bc229d9f55de72a524eab4a9a0da290 to your computer and use it in GitHub Desktop.
Save andrewluetgers/9bc229d9f55de72a524eab4a9a0da290 to your computer and use it in GitHub Desktop.
Reel to reel demo
<html>
<body>
<script>
function round(n) {
return Math.round(n * 100)/100
}
// parameters for 35mm microfilm rolls
let thickness = .147, // 0.110 to 0.180 millimeter https://photo.stackexchange.com/questions/88821/what-is-a-film-base
hubDiameter = 31.369, // 1.235 inches
//spoolDiameter = 81.788, // 100 feet = 81.788mm
spoolDiameter = 81.8760, // 100 feet = 81.788mm
spooledLength = 100 * 304.8;
// approximate spool math
// based on http://www.giangrandi.ch/soft/spiral/spiral.shtml
/* @param th number - thickness of material
* @param hd number - hub diameter
* @param sd number - spool diameter
*/
function length(th, hd, sd) {
let rot = (sd - hd) / (2 * th),
len = Math.PI * rot * (hd + th * (rot - 1)),
sa = (rot % 1) * 360;
return {
rotations: round(rot),
angle: round(sa),
length: round(len)
};
}
/* @param th number - thickness of material
* @param hd number - hub diameter
* @param len number - length of spooled material
*/
function diameter(th, hd, len) {
let rot = (th - hd + Math.sqrt((hd - th) * (hd - th) + (4 * th * len) / Math.PI)) / (2 * th),
sd = 2 * rot * th + hd,
sa = (rot % 1) * 360;
return {
rotations: round(rot),
angle: round(sa),
diameter: round(sd)
}
}
let calcDia = diameter(thickness, hubDiameter, spooledLength),
calcLen = length(thickness, hubDiameter, spoolDiameter);
console.log("diameter: ", calcDia, "vs", spoolDiameter, "input length", spooledLength, "err", calcDia.diameter - spoolDiameter);
console.log("length: ", calcLen, "vs", spooledLength, "input diameter", spoolDiameter, "err", calcLen.length - spooledLength);
</script>
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.hub {
fill: black;
stroke-width: 2px;
stroke: white;
}
.spool {
fill: #543e2a;
}
.shaft,
.shaft2 {
fill: white;
}
#reel2reel {
position: relative;
background: slategrey;
width: 400px;
height: 300px;
}
#rpms1,
#rpms2,
#spooled,
#time {
display: inline-block;
text-align: right;
position: absolute;
top: 200px;
left: 50px;
width: 80px;
}
#rpms2 {
left: 250px;
}
#spooled {
left: 150px;
}
#time {
top: 220px;
left: 150px;
}
</style>
<div id="reel2reel">
<svg width="400" height="300">
<g id="reel1" class="reel" transform="rotate(135 100 100)">
<circle id="s1" class="spool" r="40.93" cx="100" cy="100"></circle>
<circle id="h1" class="hub" r="14.68" cx="100" cy="100"></circle>
<rect class="shaft" height="8.128" width="8.128" x="95.936" y="95.936" ></rect>
<rect class="shaft2" height="12" width="4" x="98" y="98" transform="rotate(45 100 100)"></rect>
</g>
<g id="reel2" class="reel" transform="rotate(135 300 100)">
<circle id="s2" class="spool" r="40.93" cx="300" cy="100"></circle>
<circle id="h2" class="hub" r="14.68" cx="300" cy="100"></circle>
<rect class="shaft" height="8.128" width="8.128" x="295.936" y="95.936" ></rect>
<rect class="shaft2" height="12" width="4" x="298" y="98" transform="rotate(45 300 100)"></rect>
</g>
</svg>
<span id="rpms1">--</span>
<span id="rpms2">--</span>
<span id="spooled">0%</span>
<span id="time"></span>
</div>
<script>
function spoolFn(selector, x, y) {
return function spool(diameter, angle) {
document.querySelector(selector).setAttribute("transform", "rotate("+angle+" "+x+" "+y+")");
document.querySelector(selector + " .spool").setAttribute("r", diameter/2);
}
}
let spool1 = spoolFn("#reel1", 100, 100),
spool2 = spoolFn("#reel2", 300, 100);
function updateSpools(spooledDecimal, total) {
let calc1 = diameter(thickness, hubDiameter, total * (1 - spooledDecimal)),
calc2 = diameter(thickness, hubDiameter, total * spooledDecimal);
spool1(calc1.diameter, calc1.angle);
spool2(calc2.diameter, -calc2.angle);
return [calc1, calc2]
}
let now = ()=> new Date().getTime(),
playing = true,
spooled = 0,
calcTime = now(),
prevCalcTime = now(),
rpmCalcTimeDelta = 0,
startTime = 0,
runTime = 0,
travel = 0.0001,
timeStep = 16,
timeStep2 = 1,
prevCalcs,
calcs,
rpms1,
rpms2;
function render() {
if (playing) {
startTime = !startTime ? now() : startTime;
calcTime = now();
calcs = updateSpools(spooled, spooledLength, rpmCalcTimeDelta);
runTime = calcTime - startTime;
rpmCalcTimeDelta = calcTime - prevCalcTime;
prevCalcs && updateRpms(calcs, prevCalcs, rpmCalcTimeDelta, runTime);
requestAnimationFrame(render);
}
}
function driveStep() {
return setTimeout(function drive() {
spooled = playing ? spooled + travel : spooled;
playing && driveStep();
playing = spooled < 1;
}, timeStep)
}
function rpmUpdateStep() {
setTimeout(function rpmUpdate() {
if (playing) {
prevCalcs = calcs;
prevCalcTime = calcTime;
// start with a small time step then increase it to get a more stable rpm calc
if (timeStep2 < 2000) {
timeStep2 += timeStep2;
}
rpmUpdateStep();
}
}, timeStep2);
}
function updateRpms(c, p, timeDelta, runTime) {
rpms1 = (c[0].rotations - p[0].rotations) * (60000 / timeDelta);
rpms2 = (c[1].rotations - p[1].rotations) * (60000 / timeDelta);
document.querySelector("#rpms1").innerText = Math.round(rpms1*10)/10 + " RPM";
document.querySelector("#rpms2").innerText = Math.round(rpms2*10)/10 + " RPM";
document.querySelector("#spooled").innerText = (Math.round(spooled * 10000)/100).toFixed(2) + "%";
document.querySelector("#spooled").innerText = (Math.round(spooled * 10000)/100).toFixed(2) + "%";
document.querySelector("#time").innerText = formatMsDuration(runTime);
}
function formatMsDuration(ms) {
var pad = (n, z = 2) => ('00' + n).slice(-z);
return pad((ms%3.6e6)/6e4 | 0) + ':' + pad((ms%6e4)/1000|0) + '.' + pad(ms%1000, 3);
}
driveStep();
rpmUpdateStep();
render();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment