#12 Shared Filters

In Dittytoy, each loop runs in a separate thread/worker. All filters chained to the loop run, by default, in the same thread.

Sometimes, however, you want to run a filter in a separate thread. The most likely situation is that you want to process the output of multiple loops through the same filter. You can enforce this by creating a filter with the createShared() method (instead of create()).

You can connect a (shared) filter to other (shared) filters using the connect method.

dittytoy.net/syntax#filters

#dittytoy #tutorial

Log in to post a comment.

// #12 Shared Filters. DittyToy 2022.
// The MIT License.
//
// https://dittytoy.net/ditty/ccd086456e
//
// In Dittytoy, each loop runs in a separate thread/worker. All filters chained to the loop run, by default, 
// in the same thread.
//
// Sometimes, however, you want to run a filter in a separate thread. This can be for performance reasons
// (although each additional thread also gives additional overhead). However, the most likely situation is 
// that you want to process the output of multiple loops through the same filter. You can enforce this by 
// creating a filter with the createShared() method (instead of create()).
//
// You can connect a (shared) filter to other (shared) filters using the connect method.
//
// Note 1! After you connect a loop to a shared filter, you cannot connect it to subsequent filters.
// Note 2! Using shared filters will increase latency a bit.
//
// https://dittytoy.net/syntax#filters
//

// Compressor by athibaul - https://dittytoy.net/ditty/9d16ed4102

const compressor = filter.def(class {
    constructor(options) {
        this.gain = 1;
    }
    process(input, options) {
        let sigdb = 10 * Math.log10(input[0]**2 + input[1]**2);
        let overdb = sigdb - options.threshold;
        if(overdb < 0) {
            overdb = 0;
        }
        
        let target_gaindb = overdb * (1 - options.ratio)/options.ratio;
        let target_gain = 10 ** (target_gaindb / 20);
        let time = this.gain > target_gain ? options.attack : options.release;
        let a0 = clamp01(2.3 * ditty.dt / time); // 'time' to go to within 10% of the final value
        this.gain = lerp(this.gain, target_gain, a0);
        let gr = 20 * Math.log10(this.gain);
        
        return [input[0] * this.gain, input[1] * this.gain];
    }
}, {threshold:-18, ratio:2, attack:0.005, release:0.2});

// create a shared filter
let sharedCompressor = compressor.createShared();

// synth and loops
const kick = synth.def( (phase, env, tick, options) => Math.sin(phase * 2 * Math.PI * (1.5 - tick * 4)) * env.value, { amp: 4 } );
const tri  = synth.def( (phase, env, tick, options) => { const v = (phase % 1) * 4; return (v < 2 ? v - 1 : 3 - v) * env.value }, { attack: 0.0125, release: 0.25 });

ditty.bpm = 140;

loop( (loopCount) => {
    kick.play(c3, { attack: 0.025, release: 0.05 });
}, { sync: 1, name: 'kick' })
.connect( sharedCompressor ); // connect loop to shared filter

loop( (loopCount) => {
    scale(c3, scales.minor_pentatonic, 1).forEach( note => {
        tri.play(note, { pan: Math.random() * 2 - 1 });
        sleep(0.25);
    });
}, { name: 'melody' })
.connect( sharedCompressor ); // connect another loop to the same shared filter