1// Dimensions in Gallery metres. Arc-length parameterization keeps chain speed 2// constant through both turnarounds. Carriers translate; they never rotate. 3export const JUKEBOX_CAPACITY=120; 4export const JUKEBOX=Object.freeze({radius:.34,low:.76,high:1.90,trayDepth:.28,trayHeight:.05,slotPitch:.255,photoDepth:.23,depth:1.16}); 5export const JUKEBOX_LOOP=2*(JUKEBOX.high-JUKEBOX.low)+2*Math.PI*JUKEBOX.radius; 6export function jukeboxLayout(count,groups=[count]){ 7 if(!Number.isSafeInteger(count)||count<0||count>JUKEBOX_CAPACITY)throw new Error('Invalid Jukebox population.'); 8 if(groups.some(n=>!Number.isSafeInteger(n)||n<0)||groups.reduce((a,b)=>a+b,0)!==count)throw new Error('Invalid Jukebox tray groups.'); 9 const columns=[6,7,8,9,10].find(c=>groups.reduce((n,g)=>n+Math.ceil(g/c),0)<=12); 10 if(!columns)throw new Error('Too many Jukebox tray groups.'); 11 const trays=Math.max(6,groups.reduce((n,g)=>n+Math.ceil(g/columns),0)); 12 return {columns,trays,width:columns*JUKEBOX.slotPitch+.30,depth:2.8,pitch:JUKEBOX_LOOP/trays}; 13} 14export function jukeboxPoint(distance){ 15 const {radius:r,low,high}=JUKEBOX,h=high-low,arc=Math.PI*r; 16 let s=((distance%JUKEBOX_LOOP)+JUKEBOX_LOOP)%JUKEBOX_LOOP; 17 if(s<h)return {y:low+s,z:r,front:true}; 18 s-=h;if(s<arc){const a=s/r;return {y:high+r*Math.sin(a),z:r*Math.cos(a),front:false};} 19 s-=arc;if(s<h)return {y:high-s,z:-r,front:false}; 20 const a=(s-h)/r;return {y:low-r*Math.sin(a),z:-r*Math.cos(a),front:false}; 21} 22// A held motor ramps velocity with a smoothstep curve. Integrating the curve 23// analytically keeps the same travelled distance at 30, 60, 120 or 144 Hz. 24export function driveJukebox(motion,direction){ 25 if(![-1,0,1].includes(direction)||motion.drive===direction)return; 26 motion.drive=direction;motion.fromVelocity=motion.velocity; 27 motion.toVelocity=direction*.34;motion.elapsed=0; 28 motion.duration=direction===0?.12:.20; 29} 30export function advanceJukebox(motion,dt){ 31 if(!(dt>0)||!Number.isFinite(dt))return false; 32 const before=motion.position; 33 if(motion.elapsed<motion.duration){ 34 const step=Math.min(dt,motion.duration-motion.elapsed),t0=motion.elapsed/motion.duration,t1=(motion.elapsed+step)/motion.duration; 35 const primitive=t=>t*t*t-.5*t*t*t*t,difference=motion.toVelocity-motion.fromVelocity; 36 motion.position+=motion.fromVelocity*step+difference*motion.duration*(primitive(t1)-primitive(t0)); 37 motion.elapsed+=step;motion.velocity=motion.fromVelocity+difference*(3*t1*t1-2*t1*t1*t1);dt-=step; 38 if(motion.elapsed>=motion.duration)motion.velocity=motion.toVelocity; 39 } 40 motion.position+=motion.velocity*dt; 41 return motion.position!==before; 42} 43export function jukeboxMotor(){return {position:0,velocity:0,drive:0,fromVelocity:0,toVelocity:0,elapsed:0,duration:0};} 44 45// Adjacent groups of one maker/country/category/presentation policy may share 46// a cabinet. Start every group on a fresh tray; never consume another group's 47// remaining row slots. Split a large group only at a fixture boundary. 48export function packJukeboxGroups(groups,start=0){ 49 const units=[];let parts=[]; 50 const flush=()=>
50{if(!parts.length)return;const count=parts.reduce((n,p)=>n+p.count,0),layout=jukeboxLayout(count,parts.map(p=>p.count));units.push({start:parts[0].start,count,parts,layout});parts=[];}; 51 for(const group of groups){ 52 let remaining=group.total; 53 while(remaining){ 54 const free=(12-parts.reduce((n,p)=>n+Math.ceil(p.count/10),0))*10; 55 if(!free){flush();continue;} 56 const count=Math.min(remaining,free),{items,...metadata}=group;parts.push({...metadata,start,count});start+=count;remaining-=count; 57 if(remaining)flush(); 58 } 59 } 60 flush();return units; 61}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.