A classic sound synthesis using a feedback delay, playing Bach music.
Log in to post a comment.
// Karplus-Strong plucked string synthesis
// The string is represented by a delay line, with linear interpolation for fractional delay,
// and a first-order lowpass filter, which feeds back into the delay line.
// Each note is initialized with a short burst of noise.
const ks = synth.def(class {
constructor(options) {
let freq = midi_to_hz(options.note);
let delay_samples = 1 / (freq * ditty.dt); // Duration of one period in samples
this.len = Math.floor(delay_samples) + 1; // buffer size
this.fd = delay_samples % 1; // fractional delay to interpolate between samples
this.buf = new Float32Array(this.len); // buffer used to create a delay
this.pos = 0; // current position of the reading/writing head
this.a1 = clamp01(2 * Math.PI * options.cutoff * ditty.dt); // Lowpass filter the reinjection
this.s0 = 0; // Signal value history for the lowpass filter
let offs = Math.floor(this.len * (0.2 + 0.2*Math.random())); // the offset determines the plucking position
// Initialize part of the buffer with noise
for(let i=0; i < 70 && i < this.len; i++){
this.buf[i] = Math.random();
// The following line introduces "comb filtering" in the filter input, for more interesting results
this.buf[(i+offs)%this.len] += -this.buf[i];
}
}
process(note, env, tick, options) {
let pos = this.pos;
let value = lerp(this.buf[pos],
this.buf[(pos+1)%this.len],
this.fd); // linear interpolation for the fractional delay
// Nonlinearity (optional)
// value /= 1 + Math.max(value,0);
this.s0 += this.a1 * (value - this.s0); // lowpass filter
this.buf[pos] = lerp(value, this.s0, options.lowpass_amt);
this.pos = (pos+1)%this.len;
return this.s0 * env.value; // The natural decay is a bit slow, so we still apply an envelope
}
}, {env:adsr, release:2.75, cutoff:2500, lowpass_amt:0.1});
ditty.bpm = 80;
const fs4j = fs4; // - 0.14; // just intonation
const cs4j = cs4; // - 0.14; // just intonation
loop( () => {
// Adapted from Bach's Cello Suite No. 1 in G Major BWV 1007
let notes = [d3,a3,fs4j,e4,fs4j,a3,fs4j,a3,
d3,a3,fs4j,e4,fs4j,a3,fs4j,a3,
d3,b3,g4,fs4j,g4,b3,g4,b3,
d3,b3,g4,fs4j,g4,b3,g4,b3,
d3,cs4j,g4,fs4j,g4,cs4j,g4,cs4j,
d3,cs4j,g4,fs4j,g4,cs4j,g4,cs4j,
d3,b3,fs4j,e4,fs4j,d4,cs4j,d4,
b3,d4,cs4j,d4,fs3,a3,gs3,fs3,
gs3,d4,e4,d4,e4,d4,e4,d4,
gs3,d4,e4,d4,e4,d4,e4,d4,
cs4j,e4,a4,gs4,a4,e4,d4,e4,
cs4j,e4,d4,e4,a3,cs4j,b3,a3,
b2,fs3,d4,cs4j,d4,fs3,d4,fs3,
b2,fs3,d4,cs4j,d4,fs3,d4,fs3,
b2,gs3,a3,b3,a3,gs3,fs3,e3,
d4,cs4j,b3,a4,gs4,fs4j,e4,d4,
a2,b3,cs4j,a4,e4,a4,cs4j,e4,
g4,b3,cs4j,e4,g4,e4,cs4j,a3,
];
for(let i = 0; i<notes.length; i++) {
ks.play(notes[i] + Math.random() * 0.01, {amp:Math.random() + 0.5 + (i%8==0?0.8:0), pan:0.5 * (2*((notes[i]*0.618)%1)-1)});
sleep(0.25+0.02*Math.random());
}
}, {name:"harp", amp:0.5});