Variable stiffness

Synthesizing a plucked string instrument sound, with variable stiffness.
Can generate sounds ranging from guitar strings to bells and metal bars.

Log in to post a comment.

// A string/bell/bar with variable stiffness.
// ===========================================
//
// This code synthesizes metallic sounds based on the vibrational modes of a prestressed beam.
// It can generate sounds ranging from guitar strings to bells and metal bars.
// 
// It uses an efficient recurrence formula to compute the sinusoidal modes
// using only two multiplications per time step.
// For more info on this technique, see 
// https://dittytoy.net/ditty/59300f01a0
//
// The modal frequencies, amplitudes and decay speeds are assumed to have 
// a simplified dependence on the mode number;
// possible extensions of the code could improve on this.
//
// Feel free to reuse and customize this, and make music with it!
//
// ===========================================

// Set input parameters for the physical model
// Relative stiffness of the string, ranging from 0 to 1, 0 being an ideal string, and 1 an ideal beam.
input.stiffness = 0.5;
// Position of hammer impact/plucking along string length
input.hitPos = 0.21;
// Cutoff frequency of the impact
input.cutoff = 400; // min=100, max=3000, step=1

ditty.bpm = 60;

const stiffString = synth.def(class {
    constructor(o) {
        const freq = midi_to_hz(o.note);
        const omega_1 = 2 * Math.PI * freq; // Fundamental frequency in radians per second
        const omega_max = 2 * Math.PI * 20000; // Maximal frequency in radians per second
        const omega_cutoff = 2*Math.PI*o.cutoff; // Cutoff frequency in radians per second
        const T = ditty.dt;
        
        let omega_n = omega_1;
        this.c1 = [];
        this.c2 = [];
        this.s_nm1 = [];
        this.s_n = [];
        this.n_modes = 0;
        for(let n=1; omega_n < omega_max; n++) {
            
            // This is where the magic happens!
            // We set here the frequency, amplitude and decay speed of each mode.
            
            // Compute frequency of the nth mode
            omega_n = omega_1 * Math.sqrt((1 - o.stiffness) * n**2 + o.stiffness * n**4);
            // Compute initial amplitude of the nth mode based on hit position and cutoff frequency
            let amp_n = Math.sin(n*Math.PI*o.hitPos) * (Math.min(1, omega_cutoff/omega_n));
            // Compute damping coefficient for the nth mode based on duration and frequency
            let gamma_n = 7/o.duration * (omega_n/omega_1)**0.5;
            
            
            // Compute time stepping coefficients for the nth mode
            this.c1.push(2 * Math.exp(-gamma_n * T) * Math.cos(omega_n * T));
            this.c2.push(Math.exp(-2 * gamma_n * T));
            // Set initial conditions for the nth mode:
            //      s[-1] = exp(γT) sin(-ωT),
            //      s[0] = 0,
            this.s_nm1.push(amp_n * Math.exp(gamma_n*T) * Math.sin(-omega_n*T));
            this.s_n.push(0);
            this.n_modes++;
        }
    }
    // Process one sample of audio for the string model
    process(p,e,t,o) {
        let sig = 0;
        // Update each mode and sum them up
        for(let n=0; n<this.n_modes; n++) {
            const s_np1 = this.c1[n] * this.s_n[n] - this.c2[n] * this.s_nm1[n];
            this.s_nm1[n] = this.s_n[n];
            this.s_n[n] = s_np1;
            sig += s_np1;
        }
        return sig;
    }
}, {env:one, hitPos:0.21, stiffness:0.5, cutoff:800, duration:5, amp:0.1});

loop( () => {
    stiffString.play(c4, {stiffness: input.stiffness**4, hitPos: input.hitPos, cutoff:()=>input.cutoff});
    sleep(1);
});