Merge remote-tracking branch 'upstream/main' into vibrato
This commit is contained in:
commit
887fb6a84c
24 changed files with 1166 additions and 192 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { getAudioContext } from './superdough.mjs';
|
||||
import { clamp } from './util.mjs';
|
||||
|
||||
export function gainNode(value) {
|
||||
const node = getAudioContext().createGain();
|
||||
|
|
@ -66,10 +67,48 @@ export const getADSR = (attack, decay, sustain, release, velocity, begin, end) =
|
|||
return gainNode;
|
||||
};
|
||||
|
||||
export const getFilter = (type, frequency, Q) => {
|
||||
const filter = getAudioContext().createBiquadFilter();
|
||||
filter.type = type;
|
||||
filter.frequency.value = frequency;
|
||||
filter.Q.value = Q;
|
||||
return filter;
|
||||
export const getParamADSR = (param, attack, decay, sustain, release, min, max, begin, end) => {
|
||||
const range = max - min;
|
||||
const peak = min + range;
|
||||
const sustainLevel = min + sustain * range;
|
||||
param.setValueAtTime(min, begin);
|
||||
param.linearRampToValueAtTime(peak, begin + attack);
|
||||
param.linearRampToValueAtTime(sustainLevel, begin + attack + decay);
|
||||
param.setValueAtTime(sustainLevel, end);
|
||||
param.linearRampToValueAtTime(min, end + Math.max(release, 0.1));
|
||||
};
|
||||
|
||||
export function createFilter(
|
||||
context,
|
||||
type,
|
||||
frequency,
|
||||
Q,
|
||||
attack,
|
||||
decay,
|
||||
sustain,
|
||||
release,
|
||||
fenv,
|
||||
start,
|
||||
end,
|
||||
fanchor = 0.5,
|
||||
) {
|
||||
const filter = context.createBiquadFilter();
|
||||
filter.type = type;
|
||||
filter.Q.value = Q;
|
||||
filter.frequency.value = frequency;
|
||||
|
||||
// Apply ADSR to filter frequency
|
||||
if (!isNaN(fenv) && fenv !== 0) {
|
||||
const offset = fenv * fanchor;
|
||||
|
||||
const min = clamp(2 ** -offset * frequency, 0, 20000);
|
||||
const max = clamp(2 ** (fenv - offset) * frequency, 0, 20000);
|
||||
|
||||
// console.log('min', min, 'max', max);
|
||||
|
||||
getParamADSR(filter.frequency, attack, decay, sustain, release, min, max, start, end);
|
||||
return filter;
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "superdough",
|
||||
"version": "0.9.6",
|
||||
"version": "0.9.7",
|
||||
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option
|
|||
const cutGroups = [];
|
||||
|
||||
export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
||||
const {
|
||||
let {
|
||||
s,
|
||||
freq,
|
||||
unit,
|
||||
|
|
@ -207,7 +207,9 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
|||
n = 0,
|
||||
note,
|
||||
speed = 1, // sample playback speed
|
||||
loopBegin = 0,
|
||||
begin = 0,
|
||||
loopEnd = 1,
|
||||
end = 1,
|
||||
} = value;
|
||||
// load sample
|
||||
|
|
@ -215,6 +217,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
|||
// no playback
|
||||
return;
|
||||
}
|
||||
loop = s.startsWith('wt_') ? 1 : value.loop;
|
||||
const ac = getAudioContext();
|
||||
// destructure adsr here, because the default should be different for synths and samples
|
||||
const { attack = 0.001, decay = 0.001, sustain = 1, release = 0.001 } = value;
|
||||
|
|
@ -242,19 +245,12 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
|||
// rather than the current playback rate, so even if the sound is playing at twice its normal speed,
|
||||
// the midway point through a 10-second audio buffer is still 5."
|
||||
const offset = begin * bufferSource.buffer.duration;
|
||||
bufferSource.start(time, offset);
|
||||
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
|
||||
/*if (loop) {
|
||||
// TODO: idea for loopBegin / loopEnd
|
||||
// if one of [loopBegin,loopEnd] is <= 1, interpret it as normlized
|
||||
// if [loopBegin,loopEnd] is bigger >= 1, interpret it as sample number
|
||||
// this will simplify perfectly looping things, while still keeping the normalized option
|
||||
// the only drawback is that looping between samples 0 and 1 is not possible (which is not real use case)
|
||||
if (loop) {
|
||||
bufferSource.loop = true;
|
||||
bufferSource.loopStart = offset;
|
||||
bufferSource.loopEnd = offset + duration;
|
||||
duration = loop * duration;
|
||||
}*/
|
||||
bufferSource.loopStart = loopBegin * bufferSource.buffer.duration - offset;
|
||||
bufferSource.loopEnd = loopEnd * bufferSource.buffer.duration - offset;
|
||||
}
|
||||
bufferSource.start(time, offset);
|
||||
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t);
|
||||
bufferSource.connect(envelope);
|
||||
const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox...
|
||||
|
|
@ -265,9 +261,10 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
|||
out.disconnect();
|
||||
onended();
|
||||
};
|
||||
const stop = (endTime, playWholeBuffer = clip === undefined) => {
|
||||
const stop = (endTime, playWholeBuffer = clip === undefined && loop === undefined) => {
|
||||
let releaseTime = endTime;
|
||||
if (playWholeBuffer) {
|
||||
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
|
||||
releaseTime = t + (end - begin) * bufferDuration;
|
||||
}
|
||||
bufferSource.stop(releaseTime + release);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import './reverb.mjs';
|
|||
import './vowel.mjs';
|
||||
import { clamp } from './util.mjs';
|
||||
import workletsUrl from './worklets.mjs?url';
|
||||
import { getFilter, gainNode } from './helpers.mjs';
|
||||
import { createFilter, gainNode } from './helpers.mjs';
|
||||
import { map } from 'nanostores';
|
||||
import { logger } from './logger.mjs';
|
||||
|
||||
|
|
@ -177,14 +177,32 @@ export const superdough = async (value, deadline, hapDuration) => {
|
|||
bank,
|
||||
source,
|
||||
gain = 0.8,
|
||||
// filters
|
||||
ftype = '12db',
|
||||
fanchor = 0.5,
|
||||
// low pass
|
||||
cutoff,
|
||||
lpenv,
|
||||
lpattack = 0.01,
|
||||
lpdecay = 0.01,
|
||||
lpsustain = 1,
|
||||
lprelease = 0.01,
|
||||
resonance = 1,
|
||||
// high pass
|
||||
hpenv,
|
||||
hcutoff,
|
||||
hpattack = 0.01,
|
||||
hpdecay = 0.01,
|
||||
hpsustain = 1,
|
||||
hprelease = 0.01,
|
||||
hresonance = 1,
|
||||
// band pass
|
||||
bpenv,
|
||||
bandf,
|
||||
bpattack = 0.01,
|
||||
bpdecay = 0.01,
|
||||
bpsustain = 1,
|
||||
bprelease = 0.01,
|
||||
bandq = 1,
|
||||
//
|
||||
coarse,
|
||||
|
|
@ -239,11 +257,76 @@ export const superdough = async (value, deadline, hapDuration) => {
|
|||
// gain stage
|
||||
chain.push(gainNode(gain));
|
||||
|
||||
// filters
|
||||
cutoff !== undefined && chain.push(getFilter('lowpass', cutoff, resonance));
|
||||
hcutoff !== undefined && chain.push(getFilter('highpass', hcutoff, hresonance));
|
||||
bandf !== undefined && chain.push(getFilter('bandpass', bandf, bandq));
|
||||
vowel !== undefined && chain.push(ac.createVowelFilter(vowel));
|
||||
if (cutoff !== undefined) {
|
||||
let lp = () =>
|
||||
createFilter(
|
||||
ac,
|
||||
'lowpass',
|
||||
cutoff,
|
||||
resonance,
|
||||
lpattack,
|
||||
lpdecay,
|
||||
lpsustain,
|
||||
lprelease,
|
||||
lpenv,
|
||||
t,
|
||||
t + hapDuration,
|
||||
fanchor,
|
||||
);
|
||||
chain.push(lp());
|
||||
if (ftype === '24db') {
|
||||
chain.push(lp());
|
||||
}
|
||||
}
|
||||
|
||||
if (hcutoff !== undefined) {
|
||||
let hp = () =>
|
||||
createFilter(
|
||||
ac,
|
||||
'highpass',
|
||||
hcutoff,
|
||||
hresonance,
|
||||
hpattack,
|
||||
hpdecay,
|
||||
hpsustain,
|
||||
hprelease,
|
||||
hpenv,
|
||||
t,
|
||||
t + hapDuration,
|
||||
fanchor,
|
||||
);
|
||||
chain.push(hp());
|
||||
if (ftype === '24db') {
|
||||
chain.push(hp());
|
||||
}
|
||||
}
|
||||
|
||||
if (bandf !== undefined) {
|
||||
let bp = () =>
|
||||
createFilter(
|
||||
ac,
|
||||
'bandpass',
|
||||
bandf,
|
||||
bandq,
|
||||
bpattack,
|
||||
bpdecay,
|
||||
bpsustain,
|
||||
bprelease,
|
||||
bpenv,
|
||||
t,
|
||||
t + hapDuration,
|
||||
fanchor,
|
||||
);
|
||||
chain.push(bp());
|
||||
if (ftype === '24db') {
|
||||
chain.push(bp());
|
||||
}
|
||||
}
|
||||
|
||||
if (vowel !== undefined) {
|
||||
const vowelFilter = ac.createVowelFilter(vowel);
|
||||
chain.push(vowelFilter);
|
||||
}
|
||||
|
||||
// effects
|
||||
coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse }));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue