// ========================================================
// Beat It --- Michael Jackson (DittyToy groove study)
// Patterns transcribed from the provided MIDI (tempo 139, 4/4):
// drums = MIDI ch10 pitches 36/38/39/42, 2-bar loop from downbeat
// bass = fingered-bass track, octave jumps gb-bb-gb-eb (2 bars)
// riff = distorted-guitar track, same shape one octave up
// hook = bb4 stabs + gb/bb -> ab/f backing pads (chorus tracks)
// Synths: Membrane kick, noise+body snare, bandlimited hats,
// FM bass, waveshaped guitar, FM stab, FM lead w/ vibrato.
// ========================================================
ditty.bpm = 139; // matches MIDI tempo
const TWOPI = 2*Math.PI;
const msin = (p,m) => Math.sin(TWOPI*p + m);
const Rand = (a,b) => a + Math.random()*(b-a);
const exp = Math.exp, sin = Math.sin, abs = Math.abs;
const smoothstep = (a,b,x) => clamp01((x-a)/(b-a));
class BandlimitedNoise {
constructor(fmin,fmax){ this.phase=0; this.fmin=fmin; this.fmax=fmax; this.fcur=XRand(fmin,fmax); this.newTarget(); }
newTarget(){ this.ftarget=XRand(this.fmin,this.fmax); this.a0=clamp01(2*Math.PI*this.ftarget*ditty.dt); }
process(){ this.fcur+=this.a0*(this.ftarget-this.fcur); let o=this.phase; this.phase+=ditty.dt*this.fcur; if(Math.floor(this.phase)!=Math.floor(o)) this.newTarget(); return Math.sin(TWOPI*this.phase); }
}
const XRand = (a,b) => a * (b/a)**Math.random();
// ------ KICK: 160->50Hz pitch drop + click ------
const kick = synth.def(class {
process(ph,env,tick){
let t = tick_to_second(tick);
let phase = TWOPI*50*t + TWOPI*110/60*(1-exp(-t*60));
return (sin(phase)*exp(-t*18) + (Math.random()*2-1)*exp(-t*300)*0.5) * env.value;
}
}, {attack:0.001, decay:0.25, sustain:0, release:0.05, amp:0.9});
// ------ SNARE + CLAP: body + noise + click, soft-clipped ------
const snare = synth.def(class {
process(ph,env,tick){
let t = tick_to_second(tick);
let phase = TWOPI*190*t + TWOPI*90/220*(1-exp(-t*220));
let body = sin(phase)*1.4*smoothstep(0,0.004,t)*smoothstep(0.09,0,t);
let nz = (Math.random()*2-1)*exp(-t*16)*smoothstep(0.16,0,t)*0.5;
let v = body + nz + (Math.random()*2-1)*exp(-t*600)*0.4;
return (v/(1+abs(v))) * env.value;
}
}, {attack:0.001, decay:0.2, sustain:0, release:0.08, amp:0.7});
const clap = synth.def(class {
constructor(){ this.n = new BandlimitedNoise(1200,3500); }
process(ph,env,tick){ return this.n.process()*0.4*env.value; }
}, {attack:0.001, decay:0.12, sustain:0, release:0.05, amp:0.3});
// ------ HATS: bandlimited noise 8ths ------
const hat = synth.def(class {
constructor(){ this.n = new BandlimitedNoise(7000,12000); }
process(ph,env,tick){ return this.n.process()*0.35*env.value; }
}, {attack:0.001, decay:0.05, sustain:0, release:0.03, amp:0.35});
// ------ RIDE CYMBAL: sustained pattern ------
const ride = synth.def(class {
constructor(){ this.n = new BandlimitedNoise(6000,8000); this.phase=0; }
process(ph,env,tick){
let t = tick_to_second(tick);
let out = this.n.process()*0.25*env.value;
this.phase += ditty.dt*440; // steady ride tone
return out;
}
}, {attack:0.001, decay:0.1, sustain:0.7, release:0.2, amp:0.2});
// ------ CRASH CYMBAL: short sharp bursts ------
const crash = synth.def(class {
process(ph,env,tick){
let t = tick_to_second(tick);
let noise = (Math.random()*2-1)*exp(-t*500)*0.3;
let click = (Math.random()*2-1)*exp(-t*200)*0.5;
return (noise + click) * env.value;
}
}, {attack:0.001, decay:0.15, sustain:0, release:0.05, amp:0.4});
// ------ BASS: FM, high index pluck -> low sustain ------
const bass = synth.def(class {
constructor(o){ this.phase=0; this.freq=midi_to_hz(o.note); }
process(note,env,tick){
this.phase += ditty.dt*this.freq;
let iom = 3.5*exp(-tick_to_second(tick)*8) + 1.2;
let sig = msin(this.phase, iom*msin(this.phase,0)) + msin(2*this.phase,0.8*msin(this.phase,0))*0.25;
return (sig/(1+0.4*abs(sig))) * env.value;
}
}, {attack:0.005, decay:0.2, sustain:0.5, release:0.08, duration:0.22, amp:0.5});
// ------ GUITAR: stacked squares + waveshape distortion ------
const guitar = synth.def(class {
constructor(o){ this.phase=0; this.freq=midi_to_hz(o.note); }
process(note,env,tick){
this.phase += ditty.dt*this.freq;
let sq = (x) => (clamp01(x%1<0.5?1:0)*2-1);
let sig = sq(this.phase)*0.5 + sq(this.phase*1.003)*0.3 + sq(this.phase*0.5)*0.4
+ msin(this.phase, 2*msin(this.phase,0))*0.2;
return (sig/(1+0.8*abs(sig))) * env.value;
}
}, {attack:0.003, decay:0.12, sustain:0.7, release:0.06, duration:0.2, amp:0.32});
// ------ HOOK LEAD: FM + delayed vibrato ------
const lead = synth.def(class {
constructor(o){ this.phase=0; this.vibRate=Rand(5.2,5.8); this.vibDelay=Rand(0.2,0.3); }
process(note,env,tick){
let sec=tick_to_second(tick);
let vib=smoothstep(this.vibDelay,this.vibDelay+0.3,sec)*msin(this.vibRate*sec,0)*0.15;
let f=midi_to_hz(note+vib);
this.phase+=ditty.dt*f;
let sig=msin(this.phase,2*msin(this.phase,0))*0.7+msin(2*this.phase,0)*0.3;
return (sig/(1+0.4*abs(sig)))*env.value;
}
}, {attack:0.04, release:0.1, amp:0.4});
// ------ BACKING PAD: soft FM oohs ------
const pads = synth.def(class {
constructor(o){ this.phase=Rand(0,1); this.freq=midi_to_hz(o.note); }
process(note,env,tick){
this.phase += ditty.dt*this.freq;
return (msin(this.phase,1.5*msin(this.phase,0))*0.7 + msin(2*this.phase,0)*0.2)*env.value;
}
}, {attack:0.3, release:0.5, amp:0.14});
// ====== PATTERNS (all from MIDI, 16th-quantized) ======
// drums: 16 x 8th-note steps over 2 bars. kick 0,2,4,5,6.5 / snare 1,3,5,7 / clap+snare 3,7
const drumSteps = [
[1,0,1],[0,0,1],[0,1,1],[0,0,1],[1,0,1],[0,0,1],[0,1,1],[0,0,1],
[1,0,1],[0,0,1],[1,1,1],[0,0,1],[0,0,1],[1,0,1],[0,1,1],[0,0,1],
]; // [kick, snare(+clap on 3,7), hat]
loop((i) => {
let s = drumSteps.ring(i);
if(s[0]) kick.play(0);
if(s[1]) { snare.play(0); if(i%16===6||i%16===14) clap.play(0); }
if(s[2]) hat.play(0,{amp:Rand(0.35,0.55)});
sleep(0.5);
}, {name:"π₯ drums", amp:0.8});
// bass: gb1-bb1-gb2-eb2 octave run, eb pedal, db turn (2 bars = 8 beats)
const bassLine = [
[0,0.5],[gb1,0.5],[bb1,0.5],[gb2,0.5],[eb2,1.5],[eb2,1],[eb2,0.5],
[db2,0.5],[0,0.5],[db2,0.5],[0,1],[eb1,0.5],
];
loop((i) => {
let n = bassLine.ring(i);
if(n[0]) bass.play(n[0],{duration:n[1], amp:Rand(0.8,1)});
sleep(n[1]);
}, {name:"πΈ bass"});
// riff: same shape as bass, octave up (dist-guitar track)
const riffLine = [
[0,0.5],[gb2,0.5],[bb2,0.5],[gb3,0.5],[eb3,1.5],[eb3,1],[eb3,0.5],
[db3,0.5],[0,0.5],[db3,0.5],[0,1],[eb2,0.5],
];
loop((i) => {
let n = riffLine.ring(i);
if(n[0]) guitar.play(n[0],{duration:Math.min(n[1],0.22), amp:Rand(0.8,1)});
sleep(n[1]);
}, {name:"πΈ riff", amp:0.9});
// hook: bb4 stabs (lead-square track)
const hookLine = [[bb4,0.5],[bb4,1],[0,2.5],[bb4,0.5],[bb4,1],[0,2],[bb4,0.5]];
loop((i) => {
let n = hookLine.ring(i);
if(n[0]) { lead.play(n[0],{duration:n[1]}); lead.play(n[0]+12,{duration:n[1],amp:0.3}); }
sleep(n[1]);
}, {name:"π€ hook", amp:0.6});
// backing pads: gb+bb -> ab+f alternation (voice track dyads)
loop(() => {
pads.play(gb4,{duration:4}); pads.play(bb4,{duration:4}); sleep(4);
pads.play(ab4,{duration:4}); pads.play(f4,{duration:4}); sleep(4);
}, {name:"π€Άπ»β oohs"});