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;