Smoke on the water (loud!)

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;