A mono synth with portamento, defined in a very mysterious way. Now it's a hip-hop instrumental track.
Log in to post a comment.
// A horrible hack to obtain my portamento synth // There's probably a better way! // My synth is defined as a filter (!) const saw = filter.def(class { constructor(options) { this.phase0 = 0; this.phase1 = 0.62; } process(input, options) { let freq = midi_to_hz(input[0]); // Two waves at slightly different frequencies this.phase0 += freq * ditty.dt * 1.003; this.phase1 += freq * ditty.dt * 0.997; let sig = (this.phase0 % 1 - 0.5)*options.sinamt; // sine wave sig += (this.phase1 % 1 > 0.5 ? 0.7 : -0.7) * options.sqramt; // square wave if(options.subamt){ sig += -Math.sin(Math.PI*this.phase1) * options.subamt; // sin, one octave lower let x = (0.5*this.phase1) % 1; //sig += 20*x*(1-x)*(x-0.5) * options.subamt; // Kind of like a sine wave, but with some timbre sig += 2.5*(x-0.5) * clamp01(10*x*(1-x)) * options.subamt; // Quite a bit of timbre //sig += 2*(x-0.5) * options.subamt; // saw } sig *= options.amp * 0.5; return [sig,sig]; } }, {sinamt: 1, sqramt: 0.5, subamt:0, amp: 1}); // My pitch smoother is also a filter (!) const smoo = filter.def(class { constructor(options){ this.a0 = ditty.dt / options.dur; this.v = 0; } process(input, options) { if(this.v) { this.v += this.a0 * (input[0] - this.v); } else { this.v = input[0]; // Initialize it at the prescribed value } return [this.v, this.v]; // Do we really have to make this stereo? } }); // And now I'm defining a "synth" that outputs constant values!! const cst = synth.def( (phase,env,tick,options) => options.nn, {name:"cst", duration:1,env:one}); // Simple melody const nns = [c4,d4,e4,b3,e4,a3,a4,e4]; input.saturatorDrive = 3; // min=0.1, max=6, step=0.01 input.saturatorMix = 0.3; // min=0, max=1, step=0.01 const saturator = filter.def(class { constructor(options){} process(input, options) { let mid = (input[0] + input[1])*0.5; let side = (input[0] - input[1]); let mid2 = mid * options.drive; let side2 = side * options.drive; mid = lerp(mid, mid2/Math.sqrt(1 + mid2*mid2), options.mix); side = lerp(side, side2/Math.sqrt(1 + side2*side2), options.mix); return [mid + 0.5*side, mid - 0.5*side]; } }, {drive:() => input.saturatorDrive, mix:() => input.saturatorMix}); const sat = saturator.create(); loop(()=>{ for(let i=0; i<nns.length; i++) { cst.play(c, {nn : nns[i]}); sleep(1); } }, {name:"🎵"}) .connect(smoo.create({dur:0.03})) .connect(saw.create()) .connect(sat); loop(()=>{ // I give it a 'c' because the syntax requires it, but the note I'm actually playing // is in the options cst.play(c, {nn:a2, duration:8}); sleep(8); cst.play(c, {nn:f2, duration:8}); sleep(8); cst.play(c, {nn:d2, duration:12}); sleep(12); cst.play(c, {nn:e2, duration:4}); sleep(4); }, {name:"🎸"}) .connect(smoo.create({dur:0.05})) .connect(saw.create({sinamt:1, sqramt:0.3, subamt:1.})) .connect(sat); loop(()=>{ cst.play(c, {nn:e4+0.05, duration:1}); sleep(1); cst.play(c, {nn:c4+0.05, duration:7}); sleep(7); cst.play(c, {nn:a3-0.05, duration:8}); sleep(8); cst.play(c, {nn:e4+0.05, duration:1}); sleep(1); cst.play(c, {nn:a3-0.05, duration:7}); sleep(7); cst.play(c, {nn:e4+0.05, duration:1}); sleep(1); cst.play(c, {nn:a3-0.05, duration:3}); sleep(3); cst.play(c, {nn:b3-0.05, duration:4}); sleep(4); }, {name:"🎶"}) .connect(smoo.create({dur:0.1})) .connect(saw.create({amp:0.5, sinamt:0.3, sqramt:1})) .connect(sat); // Is there an easier way to control the parameters while the synth runs? // What if I want to automate my synth note or options using interpolated array data? // What if I want to use the slider value as a synth option, but after smoothing it in time? // Adding percussion const hat = synth.def( (phase,env) => 2*Math.sin(2*Math.PI*phase)*env.value, {name:"CH",attack:0.001,release:0.005,amp:1}); loop( () => { for(let i=0; i<32; i++) { let nnotes = (i%8)<6 ? 2 : 3; nnotes = (i == 13) || (i == 31) ? 12 : nnotes; let amp = i%2==0 || i>=6 ? 0.15 : 0.1; for(let j=0; j<nnotes; j++){ let amp2 = amp * (j==0 ? 1.5 : nnotes >=3 ? 1.0 : 0.7); hat.play(() => 115 + 12*Math.random(), {release:0.2*amp2,amp:amp2}); sleep(1/nnotes); } } }, {name:'🥁', amp:1.5}) .connect(sat); // Below I use a copy of my "fat kick" const linexp = (x,x0,x1,y0,y1) => y0 * (y1/y0) ** ((x-x0)/(x1-x0)); const linlin = (x,x0,x1,y0,y1) => y0 + (y1 - y0) * clamp01((x-x0)/(x1-x0)); const tri = synth.def((phase, env) => 1.22*(4*Math.abs(phase % 1 - 0.5) - 1) * env.value, {name:"tri"}); const dist = synth.def((phase, env, tick, options) => Math.atan(25*(Math.abs(phase % 1 - 0.5) - 0.25) * env.value), {name:"dist"}); let rhythm = [1.5,0.5+2+0.5,1,0.5+2, 0.5,1+0.5+1,1+0.5,1,0.5+1.5,0.5]; let i = 0; loop( () => { // click sine.play((tick) => linlin(tick, 0, 0.03, 127, 0), {attack:0.0005, release:0.05, amp:0.1}); // boom sine.play( (tick) => linlin(tick, 0, 0.3, a1, f1), //{env: adsr2, attack:0.01, decay:0.1, decay_level:0.1, sustain:0.15, sustain_level:0.5, release:0.2, amp:2} {env: adsr, attack:0.26, release:0.2} ); // low thump tri.play( (tick) => linlin(tick, 0, 0.1, e3, f1), {env: adsr, attack:0.005, release:0.1} ); // high tump tri.play( (tick) => linlin(tick, 0, 0.03, a5, a3), {env: adsr, attack:0.005, release:0.03, amp:0.3} ); sleep(rhythm[i]); i = (i+1)%rhythm.length; }, {name:"💓", amp:1.5}) .connect(sat);