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