Merge remote-tracking branch 'upstream/main' into betterfmsynth
This commit is contained in:
commit
37c450dd83
43 changed files with 1131 additions and 277 deletions
|
|
@ -6,17 +6,6 @@ export function gainNode(value) {
|
|||
return node;
|
||||
}
|
||||
|
||||
export const getOscillator = ({ s, freq, t }) => {
|
||||
// make oscillator
|
||||
const o = getAudioContext().createOscillator();
|
||||
o.type = s || 'triangle';
|
||||
o.frequency.value = Number(freq);
|
||||
o.start(t);
|
||||
//o.stop(t + duration + release);
|
||||
const stop = (time) => o.stop(time);
|
||||
return { node: o, stop };
|
||||
};
|
||||
|
||||
// alternative to getADSR returning the gain node and a stop handle to trigger the release anytime in the future
|
||||
export const getEnvelope = (attack, decay, sustain, release, velocity, begin) => {
|
||||
const gainNode = getAudioContext().createGain();
|
||||
|
|
|
|||
|
|
@ -121,6 +121,36 @@ function getReverb(orbit, duration = 2) {
|
|||
return reverbs[orbit];
|
||||
}
|
||||
|
||||
export let analyser, analyserData /* s = {} */;
|
||||
export function getAnalyser(/* orbit, */ fftSize = 2048) {
|
||||
if (!analyser /*s [orbit] */) {
|
||||
const analyserNode = getAudioContext().createAnalyser();
|
||||
analyserNode.fftSize = fftSize;
|
||||
// getDestination().connect(analyserNode);
|
||||
analyser /* s[orbit] */ = analyserNode;
|
||||
//analyserData = new Uint8Array(analyser.frequencyBinCount);
|
||||
analyserData = new Float32Array(analyser.frequencyBinCount);
|
||||
}
|
||||
if (analyser /* s[orbit] */.fftSize !== fftSize) {
|
||||
analyser /* s[orbit] */.fftSize = fftSize;
|
||||
//analyserData = new Uint8Array(analyser.frequencyBinCount);
|
||||
analyserData = new Float32Array(analyser.frequencyBinCount);
|
||||
}
|
||||
return analyser /* s[orbit] */;
|
||||
}
|
||||
|
||||
export function getAnalyzerData(type = 'time') {
|
||||
const getter = {
|
||||
time: () => analyser?.getFloatTimeDomainData(analyserData),
|
||||
frequency: () => analyser?.getFloatFrequencyData(analyserData),
|
||||
}[type];
|
||||
if (!getter) {
|
||||
throw new Error(`getAnalyzerData: ${type} not supported. use one of ${Object.keys(getter).join(', ')}`);
|
||||
}
|
||||
getter();
|
||||
return analyserData;
|
||||
}
|
||||
|
||||
function effectSend(input, effect, wet) {
|
||||
const send = gainNode(wet);
|
||||
input.connect(send);
|
||||
|
|
@ -167,6 +197,8 @@ export const superdough = async (value, deadline, hapDuration) => {
|
|||
room,
|
||||
size = 2,
|
||||
velocity = 1,
|
||||
analyze, // analyser wet
|
||||
fft = 8, // fftSize 0 - 10
|
||||
} = value;
|
||||
gain *= velocity; // legacy fix for velocity
|
||||
let toDisconnect = []; // audio nodes that will be disconnected when the source has ended
|
||||
|
|
@ -241,12 +273,19 @@ export const superdough = async (value, deadline, hapDuration) => {
|
|||
reverbSend = effectSend(post, reverbNode, room);
|
||||
}
|
||||
|
||||
// analyser
|
||||
let analyserSend;
|
||||
if (analyze) {
|
||||
const analyserNode = getAnalyser(/* orbit, */ 2 ** (fft + 5));
|
||||
analyserSend = effectSend(post, analyserNode, analyze);
|
||||
}
|
||||
|
||||
// connect chain elements together
|
||||
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
|
||||
|
||||
// toDisconnect = all the node that should be disconnected in onended callback
|
||||
// this is crucial for performance
|
||||
toDisconnect = chain.concat([delaySend, reverbSend]);
|
||||
toDisconnect = chain.concat([delaySend, reverbSend, analyserSend]);
|
||||
};
|
||||
|
||||
export const superdoughTrigger = (t, hap, ct, cps) => superdough(hap, t - ct, hap.duration / cps, cps);
|
||||
|
|
|
|||
|
|
@ -43,17 +43,17 @@ export function registerSynthSounds() {
|
|||
} = value;
|
||||
let { n, note, freq } = value;
|
||||
// with synths, n and note are the same thing
|
||||
n = note || n || 36;
|
||||
if (typeof n === 'string') {
|
||||
n = noteToMidi(n); // e.g. c3 => 48
|
||||
note = note || 36;
|
||||
if (typeof note === 'string') {
|
||||
note = noteToMidi(note); // e.g. c3 => 48
|
||||
}
|
||||
// get frequency
|
||||
if (!freq && typeof n === 'number') {
|
||||
freq = midiToFreq(n); // + 48);
|
||||
if (!freq && typeof note === 'number') {
|
||||
freq = midiToFreq(note); // + 48);
|
||||
}
|
||||
// maybe pull out the above frequency resolution?? (there is also getFrequency but it has no default)
|
||||
// make oscillator
|
||||
const { node: o, stop } = getOscillator({ t, s: wave, freq });
|
||||
const { node: o, stop } = getOscillator({ t, s: wave, freq, partials: n });
|
||||
|
||||
// FM + FM envelope
|
||||
let stopFm, fmEnvelope;
|
||||
|
|
@ -106,3 +106,49 @@ export function registerSynthSounds() {
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function waveformN(partials, type) {
|
||||
const real = new Float32Array(partials + 1);
|
||||
const imag = new Float32Array(partials + 1);
|
||||
const ac = getAudioContext();
|
||||
const osc = ac.createOscillator();
|
||||
|
||||
const amplitudes = {
|
||||
sawtooth: (n) => 1 / n,
|
||||
square: (n) => (n % 2 === 0 ? 0 : 1 / n),
|
||||
triangle: (n) => (n % 2 === 0 ? 0 : 1 / (n * n)),
|
||||
};
|
||||
|
||||
if (!amplitudes[type]) {
|
||||
throw new Error(`unknown wave type ${type}`);
|
||||
}
|
||||
|
||||
real[0] = 0; // dc offset
|
||||
imag[0] = 0;
|
||||
let n = 1;
|
||||
while (n <= partials) {
|
||||
real[n] = amplitudes[type](n);
|
||||
imag[n] = 0;
|
||||
n++;
|
||||
}
|
||||
|
||||
const wave = ac.createPeriodicWave(real, imag);
|
||||
osc.setPeriodicWave(wave);
|
||||
return osc;
|
||||
}
|
||||
|
||||
export function getOscillator({ s, freq, t, partials }) {
|
||||
// make oscillator
|
||||
let o;
|
||||
if (!partials || s === 'sine') {
|
||||
o = getAudioContext().createOscillator();
|
||||
o.type = s || 'triangle';
|
||||
} else {
|
||||
o = waveformN(partials, s);
|
||||
}
|
||||
o.frequency.value = Number(freq);
|
||||
o.start(t);
|
||||
//o.stop(t + duration + release);
|
||||
const stop = (time) => o.stop(time);
|
||||
return { node: o, stop };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue