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);