A small remake of the classic riff by . I wanted to create a distortion effect and it turns out that FM synthesis is quite usable for creating that effect when fiddling with the modulation frequency. Here, it has to be really close to, but not exactly a quarter of the carrier frequency to yield a somewhat convincing result. Other values just sound meh.
Log in to post a comment.
const sin = Math.sin;
const TAU = 2*Math.PI;
ditty.bpm = 120;
// synths
const kick = synth.def( (phase, env, tick, options) => sin(phase*TAU*(tick<0.5?0.5-tick:0)) * env.value);
const strat = synth.def( class {
constructor (options) {
this.t = 0
this.iom = options.iom || 13.37;
this.mul = options.multiplier || 0.251;
}
add_fm(note, env) {
// carrier frequency
const fc = midi_to_hz(note);
// modulator frequency
const fm = fc * this.mul;
return sin(
TAU*this.t*fc + this.iom*sin(TAU*this.t*fm)
) * env.value;
}
process(note, env, tick, options) {
// forward time
this.t += ditty.dt;
// overdrive the oscillators
const gain = 5;
// create power-chord, i.e. perfect fifth
let osc = this.add_fm(note, env) * gain;
osc += this.add_fm(note+7, env) * gain;
// clip
osc = osc < -1 ? -1 : osc > 1 ? 1 : osc;
return [osc, osc]; // left, right
}
});
// filters
// from https://dittytoy.net/syntax#filters
const lowpass = filter.def(class {
constructor(options) {
this.hist0 = [0, 0];
this.hist1 = [0, 0];
this.t = 0;
}
process(input, options) {
const alpha = clamp(options.cutoff, 0.01, 1);
if (input) {
for (let i = 0; i < 2; i++) {
this.hist0[i] += alpha * (input[i] - this.hist0[i]);
this.hist1[i] += alpha * (this.hist0[i] - this.hist1[i]);
}
return this.hist1;
}
}
});
// notes
const riff = [
[d4, 1], [f4, 1], [g4, 1.5],
[d4, 1], [f4, 1], [gs4, 0.5], [g4, 2],
[d4, 1], [f4, 1], [g4, 1.5],
[f4, 1], [d4, 0.5], [d4, 3],
];
let i = 0;
// loops
loop( () => {
kick.play(g4, { attack: 0.01, release: 0.05, duration: 0.2, amp: 1.2 });
sleep(1);
}, { name: 'Kick' });
loop( () => {
let [note, duration] = riff[i++];
if (i>=riff.length) i = 0;
const ds = 0.25 * duration;
const dt = second_to_tick(ds);
strat.play(note, { attack: 0.01, decay: ds, duration: dt, amp: 0.7 });
sleep(duration); // sleep in ticks
}, { name: 'Riff' }).connect( lowpass.create( { cutoff: () => input.cutoff } ));
// inputs
input.cutoff = 0.464;