/* ================= ENGINE (portable) ================= No DOM, no Math.random, no floats in decisions. 840 ticks per quarter, 210 per 16th (840 divides by 2,3,4,5,7 for tuplets). MIDI clock every 35 ticks. The seed builds 11 looped tracks; the player brings them in and out by hand. Energy = pattern density (hats only lightly). Drift = how often a phrase swaps in a variation of the detail. Musical layer: a 4-chord progression per phrase shared by bass, pad and melody; a melodic motif that is restated, sequenced and answered across the phrase; swing and metric velocity on every lane; B/C sections are transformations of A instead of unrelated patterns; a phrase arc (flat, build, or break in bar 3). Lanes: 0 = kit (v = voice 0 kick, 1 snare, 2 hat, 3 perc), 1 = synth (v = MIDI pitch). */ var Engine = (function(){ var TPB=840, TPS=210, CLOCK_TICKS=35, PHRASE_BARS=4; function mix(a,b){ var h=(a ^ Math.imul(b|0,0x9E3779B1))>>>0; h^=h>>>16; h=Math.imul(h,0x85EBCA6B)>>>0; h^=h>>>13; h=Math.imul(h,0xC2B2AE35)>>>0; h^=h>>>16; return (h>>>0)||0x6D2B79F5; } function Rng(s){ this.s=(s>>>0)||0x6D2B79F5; } Rng.prototype.next=function(){ var x=this.s; x=(x^(x<<13))>>>0; x=(x^(x>>>17))>>>0; x=(x^(x<<5))>>>0; this.s=x; return x; }; Rng.prototype.below=function(n){ return n<=0?0:(((this.next()>>>16)*n)>>>16); }; Rng.prototype.chance=function(p){ return (this.next()>>>16) < p; }; Rng.prototype.pickW=function(w){ var t=0,i; for(i=0;ib?b:x; } function P(x){ return clamp(x|0,0,127)*516; } // 0..127 -> 0..65532 function fdiv(a,b){ return Math.floor(a/b); } // floor division (C port: handle negatives explicitly) function mod(a,b){ return ((a%b)+b)%b; } var TRACKS=[ {name:'KICK', lane:0,voice:0}, {name:'KICK 2',lane:0,voice:0}, {name:'SNARE', lane:0,voice:1}, {name:'GHOST', lane:0,voice:1}, {name:'HAT', lane:0,voice:2}, {name:'HAT 16',lane:0,voice:2}, {name:'PERC', lane:0,voice:3}, {name:'BASS', lane:1,voice:0}, {name:'MEL A', lane:1,voice:0}, {name:'MEL B', lane:1,voice:0}, {name:'PAD', lane:1,voice:0} ]; var NT=TRACKS.length; var T_KICK=0,T_KICK2=1,T_SNARE=2,T_GHOST=3,T_HAT=4,T_HAT16=5,T_PERC=6,T_BASS=7,T_MELA=8,T_MELB=9,T_PAD=10; var STEPS=[16,12,14,20], STEPS_W=[12,3,2,2]; var FORMS=[[0,0,0,1],[0,0,1,0],[0,1,0,1],[0,0,0,0],[0,1,0,2],[0,1,0,0]], FORMS_W=[5,5,3,2,3,2]; var FORM_NAMES=['AAAB','AABA','ABAB','AAAA','ABAC','ABAA']; /* Menu order = index. 5 to 8 notes; chords stack every other scale note, so pentatonics give open chords, whole tone augmented, octatonic diminished. Names fit the 10-character screen line. */ var SCALES=[[0,2,4,5,7,9,11],[0,2,3,5,7,8,10],[0,2,3,5,7,9,10],[0,2,4,5,7,9,10],[0,2,4,6,7,9,11],[0,1,3,5,7,8,10],[0,2,3,5,7,8,11], [0,3,5,7,10],[0,2,4,7,9], [0,1,4,5,7,8,10],[0,1,4,5,7,8,11],[0,2,3,6,7,8,11],[0,2,3,6,7,9,10],[0,2,4,6,7,9,10], [0,2,3,7,8],[0,1,5,7,10],[0,1,5,6,10],[0,2,4,6,9,10],[0,2,4,6,8,10],[0,1,3,4,6,7,9,10],[0,1,3,5,6,8,10]]; var SCALE_NAMES=['MAJOR','MINOR','DORIAN','MIXOLYDIAN','LYDIAN','PHRYGIAN','HARM MINOR','MINOR PENT','MAJOR PENT', 'PHRYG DOM','DBL HARM','HUNG MINOR','UKR DORIAN','LYD DOM','HIRAJOSHI','IN SEN','IWATO','PROMETHEUS','WHOLE TONE','OCTATONIC','LOCRIAN']; var NOTE_NAMES=['C','C#','D','D#','E','F','F#','G','G#','A','A#','B']; var GEN_NAMES=['EUCLID','SKELETON','SUBDIV','AUTOMATA','AMENIZER']; var HOCKETS=[0xAA,0x55,0xB4,0x6C,0x96,0xD2,0x5A,0xA5]; /* chord roots as scale-degree indices, one per bar of the phrase */ var PROGS=[[0,0,5,4],[0,5,3,4],[0,3,0,4],[0,6,5,4],[0,2,5,4],[0,0,3,3],[0,5,0,5],[0,3,5,6],[0,1,0,6],[0,4,5,3]]; var SWINGS=[0,21,35,50,70], SWING_W=[4,3,3,2,1]; // delay of every second 16th, in ticks (70 = triplet feel) var ARC_NAMES=['flat','build','break']; var SPLITS=[1,2,3,4,5,7]; var SKEL=[ {k:[0,10],s:[4,12],g:[7,15]}, // two-step {k:[0,2,10,11],s:[4,12],g:[7,9,15]}, // amen-ish {k:[0,11],s:[8],g:[14,3]}, // half-time {k:[0,3,10],s:[4,12],g:[7]}, // think-ish {k:[0,6,11],s:[4,13],g:[9,15]}, // broken {k:[0,7,10],s:[4,12],g:[2,14]} // steppy ]; /* Rhythmic frames: where the main kick(s) and snare(s) live. Positions in 16ths of a 16-step bar, scaled to the seed's bar. One frame per seed, shared by every generator, so the generators vary the detail and the frame varies the feel. */ var FRAMES=[ {n:'backbeat', k:[0], s:[4,12]}, {n:'half-time', k:[0], s:[8]}, {n:'four-floor', k:[0,4,8,12], s:[4,12], layer:1}, {n:'3-3-2', k:[0,3,8,11], s:[6,14]}, {n:'broken', k:[0,10], s:[6,12]}, {n:'displaced', k:[3,10], s:[8], noOne:1}, {n:'over the bar',k:null, s:null, cyc:12}, {n:'free snare', k:[0], s:null, free:1} ], FRAME_W=[3,3,2,2,2,1,1,2]; function frameSets(g,steps,pos){ var F=FRAMES[g.frame], K={}, S={}, i; function sc(x){ return Math.floor(x*steps/16); } if(F.cyc){ for(i=0;i>1)) S[i]=1; } } else { for(i=0;i0) w[0]+=depth*3; var f=SPLITS[r.pickW(w)]; if(f>1){ for(var i=0;i= 85) become HAT, the rest HAT 16. ---------- */ function euclidParams(coreSeed,steps,d,hd){ var c=new Rng(coreSeed), kOpts=[3,5,4,2,7], k=kOpts[c.pickW([4,3,2,1,1+(d>>5)])]; k=clamp(k,1,steps>>1); var rots=[], r0; for(r0=0;r0>2)))>>7,1,hn),n:hn,rot:c.below(hn)}; var pn=POLY_N[c.below(POLY_N.length)]; var perc={k:clamp(1+((pn*d)>>9)+c.below(2),1,pn-1),n:pn,rot:c.below(pn)}; return {kick:kick,ghost:ghost,hat:hat,perc:perc,accent:c.below(hat.n)}; } function kitEuclid(coreSeed,varSeed,steps,d,hd,pos,fs){ var E=euclidParams(coreSeed,steps,d,hd), vr=new Rng(varSeed), hr=new Rng(mix(varSeed,0x484154+pos)); var ev=[], back=fs.S, i; for(i=0;i>1))) ev.push({t:t,v:1,vel:38+vr.below(20)}); if(fs.K[i]) ev.push({t:t,v:0,vel:i===0?120:112,a:1}); else if(eu(i,E.kick.k,E.kick.n,E.kick.rot) && !back[i] && !(i===0 && fs.noOne)) ev.push({t:t,v:0,vel:104}); if(eu(x,E.hat.k,E.hat.n,E.hat.rot)){ var acc=eu(x,Math.max(1,E.hat.k>>1),E.hat.n,E.accent); ev.push({t:t,v:2,vel:acc?92:66}); if(hr.chance(P(hd>>3))) ev.push({t:t+(TPS>>1),v:2,vel:44}); } if(eu(x,E.perc.k,E.perc.n,E.perc.rot)) ev.push({t:t,v:3,vel:70+vr.below(40)}); } return ev; } function kitSubdiv(coreSeed,varSeed,steps,d,hd,pos,fs){ var core=new Rng(coreSeed), vr=new Rng(varSeed), hr=new Rng(mix(varSeed,0x484154+pos)); var ev=[], gs=groups(steps), snT=[], gi, j; var burst = core.chance(P(d>>1)) ? core.below(gs.length) : -1; for(j=0;j>2)),tmp,0,function(r){ return 96+r.below(16); }); for(j=0;j>3),ev,1,function(r){ return 34+r.below(22); }); if(gi===burst) divide(hr,s,L,0,2,[1,3,4,2,3,2],P(90),ev,2,function(r){ return 50+r.below(30); }); else divide(hr,s,L,0,1,[3,6,0,2,0,0],P(36+(hd>>2)),ev,2,function(r,dp){ return dp===0?90:58+r.below(20); }); divide(vr,s,L,0,1,[6,2,3,0,1,1],P(d>>2),ev,3,function(r){ return 60+r.below(45); }); } return ev; } function kitSkeleton(coreSeed,varSeed,steps,d,hd,pos,fs,frame){ var core=new Rng(coreSeed), vr=new Rng(varSeed), hr=new Rng(mix(varSeed,0x484154+pos)); var ev=[], i, sk=SKEL[core.below(SKEL.length)]; function sc(x){ return Math.floor(x*steps/16); } var sn=[]; if(frame===0) for(i=0;i>2))) s=clamp(s+(core.below(2)?1:-1),1,steps-1); if(sn.indexOf(s)<0) ev.push({t:s*TPS,v:0,vel:104+core.below(12)}); } for(i=0;i>1))){ var xs=1+vr.below(steps-1); if(sn.indexOf(xs)<0) ev.push({t:xs*TPS,v:0,vel:90+vr.below(20)}); } var div=[2,2,1][core.below(3)], hp=P(36+(hd>>2)); for(i=0;i>3))){ var L=TPS*div; ev.push({t:i*TPS,v:2,vel:vel}); ev.push({t:i*TPS+(L>>1),v:2,vel:vel-8}); } else ev.push({t:i*TPS,v:2,vel:vel}); } } var ek=clamp(1+((d*steps)>>8)+core.below(3),1,steps-1), rot=core.below(steps); for(i=0;i>((l<<2)|(c<<1)|r))&1); } row=nx; } return row; } function randRow(r,steps,p){ var a=[]; for(var i=0;i>1))) ev.push({t:t,v:1,vel:40+thin.below(20)}); if(fs.K[i]) ev.push({t:t,v:0,vel:118,a:1}); else if(K[i] && !back && !(i===0 && fs.noOne) && thin.chance(P(40+(d>>1)))) ev.push({t:t,v:0,vel:96+thin.below(20)}); if(H[i] && thin.chance(P(56+(hd>>2)))){ var nb=H[(i+steps-1)%steps]+H[(i+1)%steps]; ev.push({t:t,v:2,vel:58+nb*16}); } if(C[i] && !C[(i+1)%steps] && thin.chance(P(d))) ev.push({t:t,v:3,vel:70+thin.below(40)}); } return ev; } /* AMENIZER: the four bars of the Amen break, velocities included, played on the kit tracks. [step, voice (0 kick, 1 snare, 2 ride/hat, 3 crash/perc), velocity, main kick flag]. Ride: on-beat 8ths go to HAT, off-beat to HAT 16. Snares under 90 land on GHOST, kicks without the flag on KICK 2. This is a by-ear transcription: adjust the table to taste. */ /* [step, voice, velocity, main flag]. Main flag = KICK / SNARE / HAT track; the rest land on KICK 2 / GHOST / HAT 16. Velocities leave headroom: backbeats ~110, offbeat kicks in the 80s, ghosts in the 50s, ride 82 / 60. */ var AMEN=(function(){ function ride(b,crashAt){ for(var s=0;s<16;s+=2) if(s!==crashAt) b.push(s%4===0?[s,2,82,1]:[s,2,60,0]); return b; } var A=ride([[0,0,112,1],[2,0,84,0],[10,0,104,1],[11,0,80,0],[4,1,112,1],[7,1,52,0],[9,1,86,1],[12,1,110,1],[15,1,58,0]],-1); var C=ride([[0,0,112,1],[2,0,84,0],[10,0,104,1],[4,1,112,1],[7,1,52,0],[9,1,86,1],[14,1,110,1]],-1); var D=ride([[2,0,104,1],[3,0,84,0],[10,0,108,1],[1,1,48,0],[4,1,112,1],[7,1,54,0],[9,1,88,1],[14,1,112,1],[10,3,110,0]],10); return [A,A,C,D]; })(); /* A drummer, not a grid: every hit moves a little in level, a hit that follows the same drum closely comes in softer, and the ride hand has a slightly different weight every bar. */ function humanizeAmen(ev,seed,bar){ ev.sort(function(a,b){ return a.t-b.t || a.v-b.v; }); var last=[-1e6,-1e6,-1e6,-1e6], lastV=[0,0,0,0], br=new Rng(mix(mix(seed,0x48554D),bar)), rideBias=br.below(11)-5; for(var i=0;i=base*9) v=fdiv(v*80,100); // a quick second stroke on a kick or snare that was just hit hard: softer last[x.v]=x.t; lastV[x.v]=base; x.vel=clamp(v,1,127); } return ev; } /* Timing feel per drum, in ticks (210 per 16th): snares sit back, ghosts float, the ride pushes slightly. */ function amenFeel(seed,bar,t,v,a){ var h=mix(mix(mix(seed,0x46454C),bar),t*4+v); function j(n){ return (h%(2*n+1))-n; } if(v===0) return j(4); if(v===1) return a ? 10+j(5) : j(10); if(v===2) return -4+j(6); return j(4); } function amenSlice(bar,beat){ var b=AMEN[bar&3], out=[], i; for(i=0;i=beat*4 && b[i][0]80?1:0]); if(op===0){ sl=amenSlice(r.below(4),j); } // same beat from another bar: the classic edit else if(op===4){ sl=amenSlice(r.below(4),r.below(4)); op=0; } // any beat of the break else if(op===1 && prev){ sl=prev; } var base=j*4*TPS; for(k=0;k>1),v:1,vel:Math.max(1,s[2]-24),a:0}); } } prev=sl; } return ev; } /* Sections B and C reuse A's core pattern and transform it, so the phrase hangs together. */ function transformKit(ev,steps,sec,seed,fs){ if(sec===0) return ev; var r=new Rng(mix(seed,0x5345+sec)), ops=[r.below(8)], i, out=[]; if(r.chance(P(64))) ops.push(r.below(8)); var half=(steps>>1)*TPS; for(i=0;i=0; } if(has(0)){ // kick answer: push the last off-beat kick a 16th later, add one before the backbeat var lastK=-1; for(i=0;i0) lastK=i; if(lastK>=0 && out[lastK].t+TPS=half && out[i].t%TPS===0) hs.push(out[i].t); for(i=0;i0) out.push({t:(sp-1)*TPS,v:1,vel:44}); } } if(has(5)){ // snare drag: the last main snare lands a 16th late var ls=-1; for(i=0;i=90) ls=i; if(ls>=0 && out[ls].t+TPS1) out=out.filter(function(e){ return !(e.v===0 && e.t===0); }); } if(has(7)){ // half-time switch: main snares collapse to one in the middle of the bar out=out.filter(function(e){ return !(e.v===1 && e.vel>=90); }); out.push({t:(steps>>1)*TPS,v:1,vel:116}); } return out; } /* Phrase arc. build: bars 1-2 drop their offbeat clutter. break: bar 3 strips to kick-on-one, backbeat, hats. */ function arcKit(ev,g,pos){ if(g.arc===1 && pos<2) return ev.filter(function(e){ return !((e.v===3 || (e.v===1 && e.vel<90)) && (pos===0 || e.t%(2*TPS)!==0)); }); if(g.arc===2 && pos===2){ var k0=1e9; for(var i=0;i=90) || (e.v===2 && e.t%(2*TPS)===0); }); } return ev; } /* Velocity by metric position (beat > 8th > 16th), plus an accent cycle of 3/5/7 sixteenths that runs across the bar line. */ function shapeKit(ev,g,pos,aoff,jit,jr){ for(var i=0;i>5); for(i=0;i>1),k++) out.push({t:t,v:(k%2)?3:1,vel:(k%2)?70+k*4:92+k*3}); } else if(type===2){ out.push({t:st,v:0,vel:116}); out.push({t:st+L-TPS,v:1,vel:100}); out.push({t:st+L-(TPS>>1),v:1,vel:116}); } // drop-out, then a two-hit pickup else { var prev=gs.length>1?gs[gs.length-2]:last, ps=prev[0]*TPS; // previous beat, stuttered for(i=0;i=ps && ev[i].t>1)){ out.push({t:st+(ev[i].t-ps),v:ev[i].v,vel:ev[i].vel}); out.push({t:st+(L>>1)+(ev[i].t-ps),v:ev[i].v,vel:clamp(ev[i].vel+10,1,127)}); } } return out; } /* ---------- synth tracks ---------- */ function chordDeg(g,pos){ return g.prog[pos%PHRASE_BARS]; } function degPitch(g,base,idx){ var sc=SCALES[g.scale], n=sc.length; return base+g.root+12*fdiv(idx,n)+sc[mod(idx,n)]; } function melPitch(g,idx){ var p=degPitch(g,60,idx); while(p<60) p+=12; while(p>96) p-=12; return p; } // keep the melody above the pad function isChordTone(g,pos,idx){ var n=SCALES[g.scale].length, c=chordDeg(g,pos), x=mod(idx-c,n); return x===0 || x===2 || x===4; } function nearestChordTone(g,pos,idx){ for(var d=0;d<4;d++){ if(isChordTone(g,pos,idx-d)) return idx-d; if(isChordTone(g,pos,idx+d)) return idx+d; } return idx; } /* Motif: 3-5 notes inside an 8-step window, contour in scale steps. Same motif across the whole seed. */ function makeMotif(rs,g){ var r=new Rng(mix(rs,0x4D4F54)), n=g.motifLen, pos=[0], used={0:1}, cont=[0], i; if(r.chance(P(40))){ pos[0]=r.below(2)?1:2; used={}; used[pos[0]]=1; } while(pos.length47) p-=12; while(p<36) p+=12; return p; } /* MOTIF: a motif stated on the bar's chord and answered, bass on the kicks with a colour tone, the triad on the pad. */ function keysMotif(rs,variant,g,steps,d,sec,pos,kit,fill){ var core=new Rng(mix(rs,0x4D4F+sec)), vr=new Rng(mix(mix(rs,0x5641+sec),variant)); var n=SCALES[g.scale].length, barT=steps*TPS, mel=[], bass=[], pad=[], i, j; var gs=groups(steps), last=gs[gs.length-1], kickList=[], M=makeMotif(rs,g), c=chordDeg(g,pos); for(i=0;i=steps) break; if(fill && s>=last[0]) break; var idx=anchor+cont[k]; if(s%4===0) idx=nearestChordTone(g,pos,idx); // strong positions land on chord tones if(vr.chance(P(d>>3))) continue; // drift/energy drops the odd note mel.push({t:s*TPS,v:melPitch(g,idx),vel:velBase+(s%4===0?18:0)+vr.below(12)}); if(k+1>2))){ // passing note between motif notes var s2=start+M.pos[k+1]; if(s2-s>=2 && (s+1)cont[k]?1:-1)),vel:velBase-14}); } } } var anchor=c+(core.chance(P(50))?2:0), half=steps>>1; place(0,M.cont,anchor,82); if(d>=40 || steps>=16){ var c2=M.cont.slice(), a2=anchor, st2=half; if(M.resp===0) a2=anchor+2; else if(M.resp===1) for(j=0;j>(i&7))&1)?T_MELA:T_MELB; // bass: chord root on the kicks, the seed's colour tone on alternate kicks, an approach note into the next bar function bp(idx){ return bassPitch(g,idx); } var nextC=chordDeg(g,pos+1); for(i=0;i0 && (i%2)===1 && core.chance(P(70))) idx=c+g.bassDeg; bass.push({t:t,v:bp(idx),vel:t===0?112:98,tr:T_BASS}); } if(steps>=8 && core.chance(P(60))){ var at=(steps-1)*TPS; if(kickList.indexOf(at)<0) bass.push({t:at,v:bp(nextC+(core.below(2)?1:-1)),vel:84,tr:T_BASS}); } // pad: the bar's triad (in scale), voiced inside C3-B3 var tones=[c,c+2,c+4], seen={}; for(i=0;i0) rank=r.below(128); var off=[0,2,4,6][r.pickW([10,2,3,1])]; var up=r.chance(P(24)), acc=r.chance(P(40)), sl=r.chance(P(28)); pat.push({rank:rank,off:off,up:up,acc:acc,sl:sl}); } var drone=r.chance(P(64)); if(variant){ var vr=new Rng(mix(mix(rs,0x5641+sec),variant)); for(i=0;i<16;i++){ if(vr.chance(P(20))) pat[i].acc=!pat[i].acc; if(vr.chance(P(16))) pat[i].sl=!pat[i].sl; if(vr.chance(P(16))) pat[i].up=!pat[i].up; if(i>0 && vr.chance(P(16))) pat[i].rank=127-pat[i].rank; } } var thr=clamp(d+16,0,128), on=[], bass=[], mel=[]; for(s=0;s>1}); } var ar=new Rng(mix(rs,0x414E53+sec)); for(s=0;s=2 && ar.chance(P(d+32))){ var tr=((g.hocket>>(s&7))&1)?T_MELA:T_MELB; // the hocket split, by step mel.push({t:s*TPS,v:melPitch(g,c+[0,2,4][ar.below(3)]),vel:84,tr:tr,len:TPS-40}); if(e2-s>=3 && ar.chance(P(64))) mel.push({t:(s+1)*TPS,v:melPitch(g,c+[0,2,4][ar.below(3)]),vel:72,tr:tr,len:TPS-40}); } s=e2; } return {mel:mel,bass:bass,pad:drone?[{t:0,v:48+g.root,vel:52,tr:T_PAD}]:[]}; } /* TURING: an 8- or 16-bit looping shift register, one step per 16th. DRIFT flips the bit coming round (0 = locked). Whether absolute step k flips is a hash of (seed, k); the register is rebuilt over the last 16 bars, so flips stick for 16 bars and any bar renders on its own. Melody = the register read as a scale degree; bass = the chord root where bit 0 is set (another bit lifts it an octave); pad = the triad. */ function rotl(v,s,L){ return s?(((v<>>(L-s)))&((1<>2), fs=mix(rs,0x464C50); var from=Math.max(0,x.bar-16)*steps, to=x.bar*steps, reg=rotl(base,from%L,L), k, s, j=0; function step(v,k){ var top=(v>>>(L-1))&1; if(flipP && (mix(fs,k)>>>16)>>2)&7)*18>>8)),vel:70+((reg>>>5)&3)*10+(s%4===0?12:0),tr:((g.hocket>>(j&7))&1)?T_MELA:T_MELB}); j++; } if(reg&1) bass.push({t:s*TPS,v:bassPitch(g,c)+(((reg>>>(L>>1))&1)?12:0),vel:s===0?110:94,tr:T_BASS}); reg=step(reg,to+s); } return {mel:mel,bass:bass,pad:padChord(g,[c,c+2,c+4])}; } /* ARP: the bar's chord (triad or 7th) arpeggiated up, down, up-down or in a fixed shuffle over 1-2 octaves, 8ths at low density and 16ths at high, alternating MEL A / B. Bass = the root on every kick; pad = the same chord. */ function keysArp(rs,variant,g,steps,d,sec,pos,kit,fill,x){ var r=new Rng(mix(rs,0x415250)), n=SCALES[g.scale].length, c=chordDeg(g,pos), i, o; var seventh=r.chance(P(64)), dir=r.below(4), chord=seventh?[c,c+2,c+4,c+6]:[c,c+2,c+4], up=[], seq; for(o=0;o0;i--) seq.push(up[i]); } else { seq=up.slice(); for(i=seq.length-1;i>0;i--){ var j=r.below(i+1), tmp=seq[i]; seq[i]=seq[j]; seq[j]=tmp; } } var off=0; if(variant) off=new Rng(mix(mix(rs,0x5641+sec),variant)).below(seq.length); var rate=d>=64?1:2, gs=groups(steps), last0=gs[gs.length-1][0], mel=[], bass=[], s=0, k=0; while(s=last0)?1:rate; mel.push({t:s*TPS,v:melPitch(g,seq[(k+off)%seq.length]),vel:s%4===0?96:78,tr:(k&1)?T_MELB:T_MELA,len:stp*TPS-20}); k++; s+=stp; } var kicks=kickTimes(kit); for(i=0;ip.vel) p.vel=ev[i].vel; } else out.push({t:ev[i].t,v:ev[i].v,vel:ev[i].vel,tr:ev[i].tr,a:ev[i].a}); } for(i=0;i=90)?T_SNARE:T_GHOST; if(e.v===2) return (e.a || e.vel>=85)?T_HAT:T_HAT16; return T_PERC; } /* Groove: every second 16th is delayed by the seed's swing; main snares can sit slightly behind. */ function groove(t,g,isSnare,barTicks,swing){ if(t%TPS===0 && (t/TPS)%2===1) t+=swing; if(isSnare && g.layback) t+=12; return Math.min(t,barTicks-1); } /* DRIFT, one knob with overlapping layers that each fade in smoothly (0 = locked loop): 0-42 phrase variations go from never to every phrase 20-127 microtiming: hits lean off the grid, hats drag, snares rush, kick and bass move together 40-127 swing wobbles from bar to bar 55-127 accents slip: the strong 16ths of the hats and perc move, velocities loosen 80-127 whole parts slide a 16th early or late for a bar (hats, perc, ghosts, second kick, melody) 105-127 a beat now and then is squeezed onto a triplet grid */ function amt(d,a,b){ return clamp(fdiv((d-a)*127,b-a),0,127); } var NUDGE_GRP=[0,0,1,3,2,2,3,0,4,4,5]; // per track: 0 low end (kick, bass), 1 snare, 2 hats, 3 ghost/perc, 4 melody, 5 pad function nudge(barSeed,t,grp,micro){ if(!micro || grp===5) return 0; var mx=fdiv(micro*(grp===4?30:48),127), h=mix(mix(barSeed,t),grp), n=(h%(2*mx+1))-mx; if(grp===2) n+=fdiv(micro*10,127); // hats drag if(grp===1) n-=fdiv(micro*8,127); // snares rush return n; } function shiftGroup(ev,test,by,barT){ for(var i=0;i=s && e.t=0 && p.scale=0 && p.root<12) g.root=p.root; return g; } /* All tracks are rendered every bar; which ones sound is up to the player. */ function renderBar(st,p,bar){ var g=applyKey(genome(st.seed,st.salts),p), amen=p.gen===4; if(amen){ g.steps=16; g.swing=0; g.layback=0; } // the break: 16 steps, straight; drift adds the swing var pos=bar%PHRASE_BARS, phrase=(bar/PHRASE_BARS)|0, sec=g.form[pos], barTicks=g.steps*TPS, i, j; var fr=new Rng(mix(mix(st.seed,0x46494C),phrase)); var fill = !!p.fill || (!amen && pos===PHRASE_BARS-1 && fr.chance(P(g.fillP>>1))); var variants=[], r; var dr=clamp(p.drift|0,0,127), dVar=Math.min(127,dr*3), micro=amt(dr,20,127), wob=amt(dr,40,127), slip=amt(dr,55,127), disp=amt(dr,80,127), warp=amt(dr,105,127); if(amen){ micro=fdiv(micro,2); wob=fdiv(wob,2); slip=fdiv(slip,2); disp=fdiv(amt(dr,90,127),2); warp=amt(dr,115,127); } // the break already has its feel: the generic layers come in later and softer var dRng=new Rng(mix(mix(st.seed,0x445249),bar)), barSeed=mix(st.seed,0x4E55+bar); for(r=0;r<2;r++) variants.push(phrase>0 && new Rng(mix(mix(roleSeed(st,r),0xD1F7),phrase)).chance(P(dVar)) ? phrase : 0); var e=clamp(p.energy|0,0,127), krs=roleSeed(st,0), cS=mix(krs,0x434F), vS=mix(mix(krs,0x5641+sec),variants[0]); var arcD=(g.arc===1)?(pos*10-15):0; var kd=clamp(g.dens[0]+e-64+arcD,0,127), hd=clamp(g.dens[0]+((e-64)>>2)+arcD,0,127); // energy moves hats only a quarter as much var fs=frameSets(g,g.steps,pos); var kit; if(amen){ kit=humanizeAmen(kitAmen(krs,bar,pos,dr,fill),krs,bar); // the break's own velocities, humanized, untouched by the shaping below if(slip){ var jt=fdiv(slip*24,127); for(i=0;i>1))) shiftGroup(kit,function(x){ return x.v===1 && x.vel<90; },dirs[dRng.below(2)],barTicks); if(dRng.chance(P(disp>>1))) shiftGroup(kit,function(x){ return x.v===0 && !x.a && x.t>0; },dirs[dRng.below(2)],barTicks); if(dRng.chance(P(disp>>2))) shiftGroup(kit,function(x){ return x.v===1 && x.vel>=90; },TPS,barTicks); } var warpBeat = warp && dRng.chance(P(warp)) ? dRng.below(Math.max(1,fdiv(g.steps,4))) : -1; if(warpBeat>=0) kit=tripletWarp(kit,warpBeat); kit=sortDedupe(kit); var melShift = disp && dRng.chance(P(disp>>1)) ? (dRng.below(2)?TPS:-TPS) : 0; var swingBar = wob ? clamp(g.swing+dRng.below(2*fdiv(wob*40,127)+1)-fdiv(wob*40,127),0,90) : g.swing; var keysFn=KEYS[clamp(p.keys|0,0,KEYS.length-1)]; var lt=keysFn(roleSeed(st,1),variants[1],g,g.steps,clamp(g.dens[1]+e-64,0,127),sec,pos,kit,fill,{bar:bar,drift:dr}), out=[]; for(i=0;it?nt-t:105)); out.push({t:clamp(groove(t,g,tr===T_SNARE,barTicks,swingBar)+nudge(barSeed,t,NUDGE_GRP[tr],micro)+(amen?amenFeel(krs,bar,t,kit[i].v,kit[i].a):0),0,barTicks-1),lane:0,v:kit[i].v,vel:kit[i].vel,len:len,tr:tr}); } function mono(list,lenRule){ for(var k=0;k>1):((nx && nx.v!==cur.v)?gap+20:gap-5); } mono(melA,melLen); mono(melB,melLen); mono(lt.bass,function(gap){ return gap-10; }); for(i=0;i>2),0,127)); s.push('kick E('+E.kick.k+','+E.kick.n+') hat E('+E.hat.k+','+E.hat.n+') perc E('+E.perc.k+','+E.perc.n+')'); } if(g.rot) s.push('melody + perc shifted '+g.rot+'/16'); return s.join(' · '); } function checksum(rows){ var h=0x811C9DC5>>>0; for(var i=0;i>>0; } return h; } return {TPB:TPB,TPS:TPS,CLOCK_TICKS:CLOCK_TICKS,PHRASE_BARS:PHRASE_BARS,TRACKS:TRACKS,NT:NT, genome:genome,applyKey:applyKey,renderBar:renderBar, describe:describe,checksum:checksum,mix:mix,Rng:Rng,SCALE_NAMES:SCALE_NAMES,NOTE_NAMES:NOTE_NAMES,GEN_NAMES:GEN_NAMES,KEYS_NAMES:KEYS_NAMES}; })(); if(typeof module!=='undefined') module.exports=Engine;