SoundBox

A quick experiment to port the synth and filter code from SoundBox player-small.js to DittyToy.

SoundBox can be found here: sb.bitsnbites.eu/
code: github.com/mbitsnbites/soundbox (GPL v3)
reddit: reddit.com/r/soundboxmusic/

The song is created in SoundBox by @m_bitsnbites for the 4K demo SWAY pouet.net/prod.php?which=59203

Log in to post a comment.

//
// A quick experiment to port the synth and filter code from SoundBox player-small.js to DittyToy.
//
// SoundBox can be found here: 
//   http://sb.bitsnbites.eu/
//   code: https://github.com/mbitsnbites/soundbox (GPL v3)
//   reddit: https://www.reddit.com/r/SoundBoxMusic/
//
// The song is created in SoundBox by @m_bitsnbites for the 4K demo SWAY
//   https://www.pouet.net/prod.php?which=59203
//

const mOscillators = [
    (value) => Math.sin(value * (Math.PI * 2)),
    (value) => 2 * (value % 1) - 1,
    (value) => (value % 1) < 0.5 ? 1 : -1,
    (value) => {
        const v = (value % 1) * 4;
        return v < 2 ? v - 1 : 3 - v;
    }
];

const song = songData(),
    mLastRow = song.endPattern,
    mNumWords = song.rowLen * song.patternLen * (mLastRow + 1),
    rowLen = song.rowLen,
    patternLen = song.patternLen;

const getnotefreq = (n) => 174.6 / ditty.sampleRate * (2 ** ((n - 128) / 12));

//
// Soundbox Envelope
//
// Optimized because we know that evelope options won't change during playback
//
const sndboxenv = env.def(
    class {
        duration(options) {
            return options.attack + options.sustain + options.release;
        }
        value(tick, options) {
            if (tick < options.attack) {
                return tick / options.attack;
            } else if (tick >= options.attack + options.sustain) {
                const e = (tick - options.attack - options.sustain) / options.release;
                return (1 - e) * (3 ** (options.expDecay * e));
            }
            return 1;
        }
    },
    {attack: 0, sustain: 0, release: 1, expDecay: 0, name: 'sndboxenv'}
);

//
// Soundbox Synth
//
// Optimized because we know that synth options won't change during playback
//        
const sndboxsynth = synth.def(
    class {
        constructor(options) {
            this.c1 = 0;
            this.c2 = 0;

            const instr = options.instr;
            this.osc1 = mOscillators[instr.i[0]];
            this.o1vol = instr.i[1];
            this.o1xenv = instr.i[3] / 32;
            this.osc2 = mOscillators[instr.i[4]];
            this.o2vol = instr.i[5];
            this.o2xenv = instr.i[8] / 32;
            this.noiseVol = instr.i[9];

            // Calculate note frequencies for the oscillators
            this.o1t = getnotefreq(options.note + instr.i[2] - 128);
            this.o2t = getnotefreq(options.note + instr.i[6] - 128) * (1 + 0.0008 * instr.i[7]);
        }

        process(note, env, tick, options) {
            // Envelope
            const e = env.value;

            // Oscillator 1
            this.c1 += this.o1t * (e ** this.o1xenv);
            let rsample = this.osc1(this.c1) * this.o1vol;

            // Oscillator 2
            this.c2 += this.o2t * (e ** this.o2xenv);
            rsample += this.osc2(this.c2) * this.o2vol;

            // Noise oscillator
            if (this.noiseVol) {
                rsample += (2 * Math.random() - 1) * this.noiseVol;
            }

            return rsample * 80 * e;
        }
    }, {}
);

//
// Soundbox Filter
//  
// Optimized: filter options won't change during playback
//                
const sndboxfilter = filter.def(class {
    constructor(options) {
        this.low = 0;
        this.high = 0;
        this.band = 0;
        this.k = 0;
        this.len = mNumWords;
        this.leftBuffer = new Float32Array(this.len);
        this.rightBuffer = new Float32Array(this.len);
        
          // Put performance critical instrument properties in local variables
        const instr = options.instr;
        this.oscLFO = mOscillators[instr.i[16]],
        this.lfoAmt = instr.i[17] / 512,
        this.lfoFreq = (2 ** (instr.i[18] - 9)) / rowLen / 2,
        this.fxLFO = instr.i[19],
        this.fxFilter = instr.i[20],
        this.fxFreq = instr.i[21] * 43.23529 * 3.141592 / ditty.sampleRate,
        this.q = 1 - instr.i[22] / 255,
        this.dist = instr.i[23] * 1e-5,
        this.drive = instr.i[24] / 32,
        this.panAmt = instr.i[25] / 512,
        this.panFreq = 6.283184 * (2 ** (instr.i[26] - 9)) / rowLen / 2,
        this.dlyAmt = instr.i[27] / 255,
        this.dly = instr.i[28] * rowLen / 2 | 0;  // Must be an even number
    }

    process(input, options) {
        // Dry mono-sample
        let rsample = input[0];
        const k = this.k;

        let f = this.fxFreq;
        if (this.fxLFO) {
            f *= this.oscLFO(this.lfoFreq * k) * this.lfoAmt + 0.5;
        }
        f = 1.5 * Math.sin(f);
        this.low += f * this.band;
        this.high = this.q * (rsample - this.band) - this.low;
        this.band += f * this.high;
        rsample = this.fxFilter == 3 ? this.band : this.fxFilter == 1 ? this.high : this.low;

        // Distortion
        if (this.dist) {
            rsample *= this.dist;
            rsample = rsample < 1 ? rsample > -1 ? Math.sin(rsample * .5 * Math.PI) : -1 : 1;
            rsample /= this.dist;
        }

        // Drive
        rsample *= this.drive;

        // Panning
        let t = Math.sin(this.panFreq * k) * this.panAmt + 0.5;
        let lsample = rsample * (1 - t);
        rsample *= t;

        // Delay is always done, since it does not need sound input
        if (k >= this.dly) {
            // Left channel = left + right[-p] * t
            lsample += this.rightBuffer[(k - this.dly) % this.len] * this.dlyAmt;

            // Right channel = right + left[-p] * t
            rsample += this.leftBuffer[(k - this.dly) % this.len] * this.dlyAmt;
        }

        this.leftBuffer[k % this.len] = lsample;
        this.rightBuffer[k % this.len] = rsample;

        this.k = (this.k + 1) % mNumWords;

        lsample /= 255 * 40;
        rsample /= 255 * 40;

        return [lsample, rsample];
    }
}, {});

//
// Create a separate loop for every SoundBox column
//
for (let mCurrentCol = 0; mCurrentCol < song.numChannels; mCurrentCol++) {
    const instr = song.songData[mCurrentCol];

    // Clear effect state
    let low = 0, band = 0, high;
    let lsample, filterActive = false;

    loop( () => {
        // Patterns
        for (let p = 0; p <= mLastRow; ++p) {
            const cp = instr.p[p];

            // Pattern rows
            for (let row = 0; row < patternLen; ++row) {
                // Execute effect command.
                const cmdNo = cp ? instr.c[cp - 1].f[row] : 0;
                if (cmdNo) {
                    instr.i[cmdNo - 1] = instr.c[cp - 1].f[row + patternLen] || 0;
                }

                // Generate notes for this pattern row
                for (let col = 0; col < 4; ++col) {
                    const n = cp ? instr.c[cp - 1].n[row + col * patternLen] : 0;
                    if (n) {
                        const attack = (instr.i[10] ** 2) * 8 / (ditty.sampleRate),
                            sustain = (instr.i[11] ** 2) * 8 / (ditty.sampleRate),
                            release = (instr.i[12] ** 2) * 8 / (ditty.sampleRate);

                        sndboxsynth.play(n, {
                            instr,
                            attack,
                            sustain,
                            release,
                            expDecay: -instr.i[13] / 16,
                            env: sndboxenv
                        });
                    }
                }
                sleep(.25);
            }
        }
    })
    .connect( sndboxfilter.create( {instr} ) );
}

// This music has been exported by SoundBox. You can use it with
// http://sb.bitsnbites.eu/player-small.js in your own product.

function songData() {
    return {
        songData: [{
            i: [2, 100, 128, 0, 3, 201, 128, 0, 0, 0, 0, 6, 29, 0, 0, 0, 0, 194, 4, 1, 3, 25, 191, 115, 244, 147, 6, 84, 6],
            p: [5, 1, 2, 1, 1, 1, 1, 3, 4, 1, 2, 1, 2, 1, 2, , , 7, 8, 7, 8, 7, 8, 7, 8, 6, 9],
            c: [{
                n: [123, 123, 135, 128, 123, 123, 135, 130, 126, 125, 126, 128, 123, 123, 135, 128, 123, 123, 135, 130, 126, 125, 126, 128, 123, 123, 135, 128, 123, 123, 135, 130],
                f: [22, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 48]
            }, {
                n: [123, 123, 135, 128, 123, 123, 135, 130, 126, 125, 126, 128, 123, 123, 135, 128, 123, 123, 135, 130, 126, 125, 126, 128, 123, , 99, , , , 99, , , , , , , , , , , , , , , , , , , , , , , , , , 111],
                f: [, , , , , , , , , , , , , , , , , , , , , , , , , , , 11, 13, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 31]
            }, {
                n: [111, , , , , , , , 123, , , , , , , , 111, , , , , , , , 123],
                f: []
            }, {
                n: [114, , , , , , , , 126, , , , , , , , 114, , , , , , , , 114, 126, 111, 123, 114, 126, 118, 130],
                f: [13, , , , , , , , , , , , , , , , , , , , , , , , 13, , 13, , 13, , 13, 11, 29, , , , , , , , , , , , , , , , , , , , , , , , 32, , 41, , 29, , 25, 15]
            }, {
                n: [123, 123, 135, 128, 123, 123, 135, 130, 126, 125, 126, 128, 123, 123, 135, 128, 123, 123, 135, 130, 126, 125, 126, 128, 123, , 99, , , , 99, , , , , , , , , , , , , , , , , , , , , , , , , , 111],
                f: [13, 11, 22, 18, , , , , , , , , , , , , , , , , , , , , , , , , , , , 18, 29, , 25, 113, , , , , , , , , , , , , , , , , , , , , , , , , , , , 194]
            }, {
                n: [111, , , , , , , , 123, , , , , , , , 111, , , , , , , , 99],
                f: []
            }, {
                n: [111, 123, , 123, 111, , 123, 111, 111, 123, , 123, 111, , 123, 111, 111, 123, , 123, 111, , 123, 111, 111, 123, , 123, 111, , 123, 111],
                f: []
            }, {
                n: [114, 126, , 126, 114, , 126, 114, 114, 126, , 126, 114, , 126, 114, 114, 126, , 126, 114, , 126, 114, 116, 128, , 128, 116, , 114, 121],
                f: []
            }, {n: [], f: []}]
        }, {
            i: [0, 255, 117, 64, 0, 255, 110, 0, 64, 0, 4, 6, 35, 0, 0, 0, 0, 0, 0, 0, 2, 14, 1, 1, 39, 76, 5, 0, 0],
            p: [, 1, 1, 1, 1, 1, 1, 2, , , , 1, 1, 1, 1, 1, 1, , , 1, 1, 1, 1, 1, 1],
            c: [{
                n: [147, , , , , , 147, , , , 147, , , , , , 147, , , , , , 147, , , , 147, , , , 147],
                f: []
            }, {n: [147], f: []}]
        }, {
            i: [0, 0, 140, 0, 0, 0, 140, 0, 0, 60, 4, 10, 68, 0, 0, 0, 0, 187, 5, 0, 1, 239, 135, 0, 32, 108, 5, 16, 4],
            p: [, 1, 1, 2, 3, 2, 3, 4, , , , 2, 3, 2, 3, 2, 3, , , 5, 5, 5, 5, 5, 5, 4],
            c: [{
                n: [, , , , 147, , , , , , , , 148, , , , , , , , 147, , , , , , , , 147],
                f: [13, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 35]
            }, {
                n: [, , , , 147, , , 147, , , , , 148, , , , , , , , 147, , , 147, , , 147, , , , 147],
                f: [13, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 35]
            }, {
                n: [, , , , 147, , , 147, , , , , 148, , , , , , , , 147, , , 147, , , 147, , , 147, 147, 147],
                f: [13, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 35]
            }, {
                n: [147],
                f: [13, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 68]
            }, {
                n: [147, , , 147, , , 147, , 147, , , 147, , 147, , 147, 147, , , 147, , , 147, , 147, , , 147, , 147, , 147],
                f: [13, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 35]
            }]
        }, {
            i: [2, 192, 128, 0, 2, 192, 140, 18, 0, 0, 107, 115, 138, 0, 0, 0, 0, 136, 5, 1, 2, 8, 92, 21, 56, 148, 5, 85, 8],
            p: [3, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2],
            c: [{n: [111], f: []}, {n: [114], f: []}, {
                n: [111],
                f: [25, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 25, 5, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 56]
            }]
        }, {
            i: [3, 0, 127, 0, 3, 68, 127, 0, 64, 218, 4, 4, 40, 0, 0, 0, 1, 55, 4, 1, 2, 67, 115, 124, 190, 67, 6, 39, 1],
            p: [, , , 1, 2, 1, 2, 3, , , , 1, 2, 1, 2, 1, 2, , , 1, 4, 1, 4, 1, 4, 3],
            c: [{
                n: [, , , , 147, , , , , , , , 147, , , , , , , , 147, , , , , , , , 147],
                f: []
            }, {
                n: [, , , , 147, , , , , , , , 147, , , , , , , , 147, , , , , , , , 147, , 147, 147],
                f: []
            }, {n: [147], f: []}, {
                n: [, , , , 147, , , , , , , , 147, , , , , , , , 147, , , , , , , , 147, , , 147],
                f: []
            }]
        }, {
            i: [0, 91, 128, 0, 0, 95, 128, 12, 0, 0, 12, 0, 67, 0, 0, 0, 0, 0, 0, 0, 2, 255, 15, 0, 32, 83, 3, 134, 4],
            p: [, , , 1, 2, 3, 2, 1, 2, , , 1, 2, 3, 2, 1, 2, , , , , 4, 5, 4, 5],
            c: [{
                n: [159, , 147, , 154, , 147, , 157, , 147, , 154, , 150, , 159, , 147, , 154, , 147, , 162, , 147, , 154, , 150, , 123],
                f: [5, 13, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 67]
            }, {
                n: [159, , 147, , 154, , 147, , 157, , 147, , 154, , 150, , 159, , 147, , 154, , 147, , 162, , 147, , 157, , 162, , 126],
                f: []
            }, {
                n: [159, , 147, , 154, , 147, , 157, , 147, , 154, , 150, , 159, , 147, , 154, , 147, , 162, , 147, , 154, , 150, , 123],
                f: [5, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 3]
            }, {
                n: [159, , 162, , 164, , , , 159, , 162, 164, , , 162, , 159, , 162, , 164, , , , 159, , 162, 164, , , 162],
                f: [13, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 25]
            }, {
                n: [157, , 162, , 164, , , , 157, , 162, 164, , , 162, , 157, , 162, , 164, , , , 157, , 162, 164, , , 162],
                f: [, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 13, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 67]
            }]
        }, {
            i: [3, 146, 140, 0, 1, 224, 128, 3, 0, 0, 92, 0, 95, 0, 0, 0, 3, 179, 5, 1, 3, 37, 135, 63, 67, 150, 3, 157, 6],
            p: [, , , , , , , , , 1, 2, 3, , 1, 2, 1, 2, 3, , 4, 5, , , , , 3],
            c: [{
                n: [123, , , , , , , , , , , , , , , , 130],
                f: [11, 25, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 92, 67]
            }, {
                n: [133, , , , , , , , , , , , , , , 138, 126, , , , , , , , , , , , 125],
                f: [11, , , , , , , , , , , , , , , , , , , , , , , , , , , , 11, , , , 95, , , , , , , , , , , , , , , , , , , , , , , , , , , , 29]
            }, {
                n: [123],
                f: [25, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 25, 52, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 67]
            }, {
                n: [123, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 138, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 116],
                f: [11, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 95]
            }, {
                n: [133, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 126, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , 118],
                f: []
            }]
        }, {
            i: [0, 255, 106, 64, 0, 255, 106, 0, 64, 0, 5, 7, 164, 0, 0, 0, 0, 0, 0, 0, 2, 255, 0, 2, 16, 83, 5, 53, 1],
            p: [, , , , , , , 1, , , , , , , , , , 1, , , , , , , , 1],
            c: [{n: [147], f: []}]
        },], rowLen: 5513, patternLen: 32, endPattern: 26, numChannels: 8
    }
}