mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-11 21:58:31 +00:00
Merge pull request #683 from Bubobubobubobubo/betterfmsynth
Wave Selection and Global Envelope on the FM Synth Modulator
This commit is contained in:
commit
c54916cdfd
@ -118,7 +118,9 @@ const generic_params = [
|
||||
* @name fmh
|
||||
* @param {number | Pattern} harmonicity
|
||||
* @example
|
||||
* note("c e g b").fm(4).fmh("<1 2 1.5 1.61>")
|
||||
* note("c e g b")
|
||||
* .fm(4)
|
||||
* .fmh("<1 2 1.5 1.61>")
|
||||
*
|
||||
*/
|
||||
[['fmh', 'fmi'], 'fmh'],
|
||||
@ -130,10 +132,67 @@ const generic_params = [
|
||||
* @param {number | Pattern} brightness modulation index
|
||||
* @synonyms fmi
|
||||
* @example
|
||||
* note("c e g b").fm("<0 1 2 8 32>")
|
||||
* note("c e g b")
|
||||
* .fm("<0 1 2 8 32>")
|
||||
*
|
||||
*/
|
||||
[['fmi', 'fmh'], 'fm'],
|
||||
// fm envelope
|
||||
/**
|
||||
* Ramp type of fm envelope. Exp might be a bit broken..
|
||||
*
|
||||
* @name fmenv
|
||||
* @param {number | Pattern} type lin | exp
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* .fm(4)
|
||||
* .fmdecay(.2)
|
||||
* .fmsustain(0)
|
||||
* .fmenv("<exp lin>")
|
||||
*
|
||||
*/
|
||||
['fmenv'],
|
||||
/**
|
||||
* Attack time for the FM envelope: time it takes to reach maximum modulation
|
||||
*
|
||||
* @name fmattack
|
||||
* @param {number | Pattern} time attack time
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* .fm(4)
|
||||
* .fmattack("<0 .05 .1 .2>")
|
||||
*
|
||||
*/
|
||||
['fmattack'],
|
||||
/**
|
||||
* Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase.
|
||||
*
|
||||
* @name fmdecay
|
||||
* @param {number | Pattern} time decay time
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* .fm(4)
|
||||
* .fmdecay("<.01 .05 .1 .2>")
|
||||
* .fmsustain(.4)
|
||||
*
|
||||
*/
|
||||
['fmdecay'],
|
||||
/**
|
||||
* Sustain level for the FM envelope: how much modulation is applied after the decay phase
|
||||
*
|
||||
* @name fmsustain
|
||||
* @param {number | Pattern} level sustain level
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* .fm(4)
|
||||
* .fmdecay(.1)
|
||||
* .fmsustain("<1 .75 .5 0>")
|
||||
*
|
||||
*/
|
||||
['fmsustain'],
|
||||
// these are not really useful... skipping for now
|
||||
['fmrelease'],
|
||||
['fmvelocity'],
|
||||
|
||||
/**
|
||||
* Select the sound bank to use. To be used together with `s`. The bank name (+ "_") will be prepended to the value of `s`.
|
||||
|
||||
@ -28,6 +28,22 @@ export const getEnvelope = (attack, decay, sustain, release, velocity, begin) =>
|
||||
};
|
||||
};
|
||||
|
||||
export const getExpEnvelope = (attack, decay, sustain, release, velocity, begin) => {
|
||||
sustain = Math.max(0.001, sustain);
|
||||
velocity = Math.max(0.001, velocity);
|
||||
const gainNode = getAudioContext().createGain();
|
||||
gainNode.gain.setValueAtTime(0.0001, begin);
|
||||
gainNode.gain.exponentialRampToValueAtTime(velocity, begin + attack);
|
||||
gainNode.gain.exponentialRampToValueAtTime(sustain * velocity, begin + attack + decay);
|
||||
return {
|
||||
node: gainNode,
|
||||
stop: (t) => {
|
||||
// similar to getEnvelope, this will glitch if sustain level has not been reached
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.0001, t + release);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getADSR = (attack, decay, sustain, release, velocity, begin, end) => {
|
||||
const gainNode = getAudioContext().createGain();
|
||||
gainNode.gain.setValueAtTime(0, begin);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { midiToFreq, noteToMidi } from './util.mjs';
|
||||
import { registerSound, getAudioContext } from './superdough.mjs';
|
||||
import { gainNode, getEnvelope } from './helpers.mjs';
|
||||
import { gainNode, getEnvelope, getExpEnvelope } from './helpers.mjs';
|
||||
|
||||
const mod = (freq, range = 1, type = 'sine') => {
|
||||
const ctx = getAudioContext();
|
||||
@ -26,13 +26,20 @@ export function registerSynthSounds() {
|
||||
wave,
|
||||
(t, value, onended) => {
|
||||
// destructure adsr here, because the default should be different for synths and samples
|
||||
const {
|
||||
let {
|
||||
attack = 0.001,
|
||||
decay = 0.05,
|
||||
sustain = 0.6,
|
||||
release = 0.01,
|
||||
fmh: fmHarmonicity = 1,
|
||||
fmi: fmModulationIndex,
|
||||
fmenv: fmEnvelopeType = 'lin',
|
||||
fmattack: fmAttack,
|
||||
fmdecay: fmDecay,
|
||||
fmsustain: fmSustain,
|
||||
fmrelease: fmRelease,
|
||||
fmvelocity: fmVelocity,
|
||||
fmwave: fmWaveform = 'sine',
|
||||
} = value;
|
||||
let { n, note, freq } = value;
|
||||
// with synths, n and note are the same thing
|
||||
@ -48,15 +55,37 @@ export function registerSynthSounds() {
|
||||
// make oscillator
|
||||
const { node: o, stop } = getOscillator({ t, s: wave, freq, partials: n });
|
||||
|
||||
let stopFm;
|
||||
// FM + FM envelope
|
||||
let stopFm, fmEnvelope;
|
||||
if (fmModulationIndex) {
|
||||
const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex);
|
||||
modulator.connect(o.frequency);
|
||||
const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex, fmWaveform);
|
||||
if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) {
|
||||
// no envelope by default
|
||||
modulator.connect(o.frequency);
|
||||
} else {
|
||||
fmAttack = fmAttack ?? 0.001;
|
||||
fmDecay = fmDecay ?? 0.001;
|
||||
fmSustain = fmSustain ?? 1;
|
||||
fmRelease = fmRelease ?? 0.001;
|
||||
fmVelocity = fmVelocity ?? 1;
|
||||
fmEnvelope = getEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t);
|
||||
if (fmEnvelopeType === 'exp') {
|
||||
fmEnvelope = getExpEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t);
|
||||
fmEnvelope.node.maxValue = fmModulationIndex * 2;
|
||||
fmEnvelope.node.minValue = 0.00001;
|
||||
}
|
||||
modulator.connect(fmEnvelope.node);
|
||||
fmEnvelope.node.connect(o.frequency);
|
||||
}
|
||||
stopFm = stop;
|
||||
}
|
||||
|
||||
// turn down
|
||||
const g = gainNode(0.3);
|
||||
// envelope
|
||||
|
||||
// gain envelope
|
||||
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t);
|
||||
|
||||
o.onended = () => {
|
||||
o.disconnect();
|
||||
g.disconnect();
|
||||
@ -66,6 +95,7 @@ export function registerSynthSounds() {
|
||||
node: o.connect(g).connect(envelope),
|
||||
stop: (releaseTime) => {
|
||||
releaseEnvelope(releaseTime);
|
||||
fmEnvelope?.stop(releaseTime);
|
||||
let end = releaseTime + release;
|
||||
stop(end);
|
||||
stopFm?.(end);
|
||||
|
||||
@ -1825,6 +1825,69 @@ exports[`runs examples > example "fm" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "fmattack" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c fmi:4 fmattack:0 ]",
|
||||
"[ 1/4 → 1/2 | note:e fmi:4 fmattack:0 ]",
|
||||
"[ 1/2 → 3/4 | note:g fmi:4 fmattack:0 ]",
|
||||
"[ 3/4 → 1/1 | note:b fmi:4 fmattack:0 ]",
|
||||
"[ 1/1 → 5/4 | note:c fmi:4 fmattack:0.05 ]",
|
||||
"[ 5/4 → 3/2 | note:e fmi:4 fmattack:0.05 ]",
|
||||
"[ 3/2 → 7/4 | note:g fmi:4 fmattack:0.05 ]",
|
||||
"[ 7/4 → 2/1 | note:b fmi:4 fmattack:0.05 ]",
|
||||
"[ 2/1 → 9/4 | note:c fmi:4 fmattack:0.1 ]",
|
||||
"[ 9/4 → 5/2 | note:e fmi:4 fmattack:0.1 ]",
|
||||
"[ 5/2 → 11/4 | note:g fmi:4 fmattack:0.1 ]",
|
||||
"[ 11/4 → 3/1 | note:b fmi:4 fmattack:0.1 ]",
|
||||
"[ 3/1 → 13/4 | note:c fmi:4 fmattack:0.2 ]",
|
||||
"[ 13/4 → 7/2 | note:e fmi:4 fmattack:0.2 ]",
|
||||
"[ 7/2 → 15/4 | note:g fmi:4 fmattack:0.2 ]",
|
||||
"[ 15/4 → 4/1 | note:b fmi:4 fmattack:0.2 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "fmdecay" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c fmi:4 fmdecay:0.01 fmsustain:0.4 ]",
|
||||
"[ 1/4 → 1/2 | note:e fmi:4 fmdecay:0.01 fmsustain:0.4 ]",
|
||||
"[ 1/2 → 3/4 | note:g fmi:4 fmdecay:0.01 fmsustain:0.4 ]",
|
||||
"[ 3/4 → 1/1 | note:b fmi:4 fmdecay:0.01 fmsustain:0.4 ]",
|
||||
"[ 1/1 → 5/4 | note:c fmi:4 fmdecay:0.05 fmsustain:0.4 ]",
|
||||
"[ 5/4 → 3/2 | note:e fmi:4 fmdecay:0.05 fmsustain:0.4 ]",
|
||||
"[ 3/2 → 7/4 | note:g fmi:4 fmdecay:0.05 fmsustain:0.4 ]",
|
||||
"[ 7/4 → 2/1 | note:b fmi:4 fmdecay:0.05 fmsustain:0.4 ]",
|
||||
"[ 2/1 → 9/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0.4 ]",
|
||||
"[ 9/4 → 5/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0.4 ]",
|
||||
"[ 5/2 → 11/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0.4 ]",
|
||||
"[ 11/4 → 3/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0.4 ]",
|
||||
"[ 3/1 → 13/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0.4 ]",
|
||||
"[ 13/4 → 7/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0.4 ]",
|
||||
"[ 7/2 → 15/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0.4 ]",
|
||||
"[ 15/4 → 4/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0.4 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "fmenv" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
||||
"[ 1/4 → 1/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
||||
"[ 1/2 → 3/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
||||
"[ 3/4 → 1/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
||||
"[ 1/1 → 5/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
||||
"[ 5/4 → 3/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
||||
"[ 3/2 → 7/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
||||
"[ 7/4 → 2/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
||||
"[ 2/1 → 9/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
||||
"[ 9/4 → 5/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
||||
"[ 5/2 → 11/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
||||
"[ 11/4 → 3/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
||||
"[ 3/1 → 13/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
||||
"[ 13/4 → 7/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
||||
"[ 7/2 → 15/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
||||
"[ 15/4 → 4/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "fmh" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c fmi:4 fmh:1 ]",
|
||||
@ -1846,6 +1909,27 @@ exports[`runs examples > example "fmh" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "fmsustain" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c fmi:4 fmdecay:0.1 fmsustain:1 ]",
|
||||
"[ 1/4 → 1/2 | note:e fmi:4 fmdecay:0.1 fmsustain:1 ]",
|
||||
"[ 1/2 → 3/4 | note:g fmi:4 fmdecay:0.1 fmsustain:1 ]",
|
||||
"[ 3/4 → 1/1 | note:b fmi:4 fmdecay:0.1 fmsustain:1 ]",
|
||||
"[ 1/1 → 5/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0.75 ]",
|
||||
"[ 5/4 → 3/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0.75 ]",
|
||||
"[ 3/2 → 7/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0.75 ]",
|
||||
"[ 7/4 → 2/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0.75 ]",
|
||||
"[ 2/1 → 9/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0.5 ]",
|
||||
"[ 9/4 → 5/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0.5 ]",
|
||||
"[ 5/2 → 11/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0.5 ]",
|
||||
"[ 11/4 → 3/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0.5 ]",
|
||||
"[ 3/1 → 13/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0 ]",
|
||||
"[ 13/4 → 7/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0 ]",
|
||||
"[ 7/2 → 15/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0 ]",
|
||||
"[ 15/4 → 4/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "focus" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:sd ]",
|
||||
|
||||
@ -38,4 +38,20 @@ Now we not only pattern the notes, but the sound as well!
|
||||
|
||||
<JsDoc client:idle name="fmh" h={0} />
|
||||
|
||||
### fmattack
|
||||
|
||||
<JsDoc client:idle name="fmattack" h={0} />
|
||||
|
||||
### fmdecay
|
||||
|
||||
<JsDoc client:idle name="fmdecay" h={0} />
|
||||
|
||||
### fmsustain
|
||||
|
||||
<JsDoc client:idle name="fmsustain" h={0} />
|
||||
|
||||
### fmenv
|
||||
|
||||
<JsDoc client:idle name="fmenv" h={0} />
|
||||
|
||||
Next up: [Audio Effects](/learn/effects)...
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user