⌈⌋ branch:  Bitrhythm


Artifact Content

Artifact 83cc5a3ab3743025e63a786a697846bfdfa0e71a94f583e6468b23a837a07356:

  • File public/303.js — part of check-in [fa4745a0a7] at 2021-11-08 06:21:09 on branch trunk — Added cue Changed Support link Working example for p5 Added dsl for tracking Added autocommit Various Fixes for Save Improve Docs and Bookmarks WebGL Demo (user: dev size: 5138)

function ThreeOhUnit(audio, waveform, defaults = null, output, patternLength = 16) {
    const synth = audio.ThreeOh(waveform, defaults = defaults, output);
    function step(note) {
        note = note || {
            type: "on",
            accent: false,
            glide: true,
            note: "A3",
        };
        if (note == "off") {
            synth.noteOff();
        } else {
            synth.noteOn(note.note, note.accent, note.glide);
        }
    }
    return {
        step,
        params: synth.params
    };
}

function textNoteToNumber(note) {
    const lookupTable = new Map();
    const revLook = new Map();
    (() => {
        function add(note, n) {
            lookupTable.set(note, n);
            revLook.set(n, note);
        }
        add('A', 9);
        add('A#', 10);
        add('B', 11);
        add('C', 0);
        add('C#', 1);
        add('D', 2);
        add('D#', 3);
        add('E', 4);
        add('F', 5);
        add('F#', 6);
        add('G', 7);
        add('G#', 8);
    })();

    const o = note.substring(note.length - 1);
    const n = note.substring(0, note.length - 1);
    // @ts-ignore
    return parseInt(o) * 12 + lookupTable.get(n) + 12;
}

function midiNoteToFrequency(noteNumber) {
    return 440 * Math.pow(2, (noteNumber - 69) / 12);
}

function Audio(au = new (window.AudioContext || window.webkitAudioContext)()) {

    function masterChannel() {
        const gain = au.createGain();
        gain.gain.value = 0.5;
        const limiter = au.createDynamicsCompressor();
        limiter.attack.value = 0.005;
        limiter.release.value = 0.1;
        limiter.ratio.value = 15.0;
        limiter.knee.value = 0.0;
        limiter.threshold.value = -0.5;
        gain.connect(limiter);
        limiter.connect(au.destination);
        return {
            in: gain
        };
    }

    const master = masterChannel();

    function ThreeOh(type = "sawtooth", defaults, out = master.in) {
        defaults = defaults || {
            "cutoff": 400,
            "resonance": 15,
            "envMod": 4000,
            "decay": 0.5
        }
        const filter = au.createBiquadFilter();
        filter.type = "lowpass";
        filter.Q.value = defaults.resonance;
        filter.frequency.value = defaults.cutoff;
        const pResonance = filter.Q;
        const pCutoff = filter.frequency;

        const decayTimeNode = au.createConstantSource();
        decayTimeNode.offset.value = defaults.decay;
        decayTimeNode.start();
        const pDecay = decayTimeNode.offset;

        const env = au.createConstantSource();
        env.start();
        env.offset.value = 0.0;

        const scaleNode = au.createGain();
        scaleNode.gain.value = defaults.envMod;
        const pEnvMod = scaleNode.gain;
        env.connect(scaleNode);
        scaleNode.connect(filter.detune);
        const osc = au.createOscillator();
        osc.type = type;
        osc.frequency.value = 440;
        osc.start();
        const vca = au.createGain();
        vca.gain.value = 0.0;
        osc.connect(vca);
        vca.connect(filter);
        filter.connect(out);
        function noteOn(note, accent = false, glide = false) {
            if (accent) {
                env.offset.cancelScheduledValues(au.currentTime);
                //env.offset.setTargetAtTime(1.0,au.currentTime, 0.001);
                env.offset.setValueAtTime(1.0, au.currentTime);
                env.offset.exponentialRampToValueAtTime(0.01, au.currentTime + pDecay.value / 3);
            }
            else {
                env.offset.cancelScheduledValues(au.currentTime);
                //env.offset.setTargetAtTime(1.0,au.currentTime, 0.001);
                env.offset.setValueAtTime(1.0, au.currentTime);
                env.offset.exponentialRampToValueAtTime(0.01, au.currentTime + pDecay.value);
            }
            osc.frequency.cancelScheduledValues(au.currentTime);
            if (typeof note == "number") {
                osc.frequency.setTargetAtTime(note, au.currentTime, glide ? 0.02 : 0.002);
            } else {
                osc.frequency.setTargetAtTime(midiNoteToFrequency(textNoteToNumber(note)), au.currentTime, glide ? 0.02 : 0.002);
            }
            vca.gain.cancelScheduledValues(au.currentTime);
            vca.gain.setValueAtTime(accent ? 0.2 : 0.15, au.currentTime);
            //vca.gain.setTargetAtTime(accent ? 0.5 : 0.3,au.currentTime, 0.001);
            //vca.gain.setValueAtTime(0.2, au.currentTime);
            vca.gain.linearRampToValueAtTime(0.1, au.currentTime + 0.2);
        }
        function noteOff() {
            vca.gain.cancelScheduledValues(au.currentTime);
            vca.gain.setTargetAtTime(0.0, au.currentTime, 0.01);
        }
        return {
            noteOn,
            noteOff,
            params: {
                cutoff: pCutoff,
                resonance: pResonance,
                envMod: pEnvMod,
                decay: pDecay
            }
        };
    }
    return {
        ThreeOh,
        master,
        context: au
    };
}

window.Audio = Audio;
window.ThreeOhUnit = ThreeOhUnit;