mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-23 03:28:33 +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
|
* @name fmh
|
||||||
* @param {number | Pattern} harmonicity
|
* @param {number | Pattern} harmonicity
|
||||||
* @example
|
* @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'],
|
[['fmh', 'fmi'], 'fmh'],
|
||||||
@ -130,10 +132,67 @@ const generic_params = [
|
|||||||
* @param {number | Pattern} brightness modulation index
|
* @param {number | Pattern} brightness modulation index
|
||||||
* @synonyms fmi
|
* @synonyms fmi
|
||||||
* @example
|
* @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'],
|
[['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`.
|
* 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) => {
|
export const getADSR = (attack, decay, sustain, release, velocity, begin, end) => {
|
||||||
const gainNode = getAudioContext().createGain();
|
const gainNode = getAudioContext().createGain();
|
||||||
gainNode.gain.setValueAtTime(0, begin);
|
gainNode.gain.setValueAtTime(0, begin);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { midiToFreq, noteToMidi } from './util.mjs';
|
import { midiToFreq, noteToMidi } from './util.mjs';
|
||||||
import { registerSound, getAudioContext } from './superdough.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 mod = (freq, range = 1, type = 'sine') => {
|
||||||
const ctx = getAudioContext();
|
const ctx = getAudioContext();
|
||||||
@ -26,13 +26,20 @@ export function registerSynthSounds() {
|
|||||||
wave,
|
wave,
|
||||||
(t, value, onended) => {
|
(t, value, onended) => {
|
||||||
// destructure adsr here, because the default should be different for synths and samples
|
// destructure adsr here, because the default should be different for synths and samples
|
||||||
const {
|
let {
|
||||||
attack = 0.001,
|
attack = 0.001,
|
||||||
decay = 0.05,
|
decay = 0.05,
|
||||||
sustain = 0.6,
|
sustain = 0.6,
|
||||||
release = 0.01,
|
release = 0.01,
|
||||||
fmh: fmHarmonicity = 1,
|
fmh: fmHarmonicity = 1,
|
||||||
fmi: fmModulationIndex,
|
fmi: fmModulationIndex,
|
||||||
|
fmenv: fmEnvelopeType = 'lin',
|
||||||
|
fmattack: fmAttack,
|
||||||
|
fmdecay: fmDecay,
|
||||||
|
fmsustain: fmSustain,
|
||||||
|
fmrelease: fmRelease,
|
||||||
|
fmvelocity: fmVelocity,
|
||||||
|
fmwave: fmWaveform = 'sine',
|
||||||
} = value;
|
} = value;
|
||||||
let { n, note, freq } = value;
|
let { n, note, freq } = value;
|
||||||
// with synths, n and note are the same thing
|
// with synths, n and note are the same thing
|
||||||
@ -48,15 +55,37 @@ export function registerSynthSounds() {
|
|||||||
// make oscillator
|
// make oscillator
|
||||||
const { node: o, stop } = getOscillator({ t, s: wave, freq, partials: n });
|
const { node: o, stop } = getOscillator({ t, s: wave, freq, partials: n });
|
||||||
|
|
||||||
let stopFm;
|
// FM + FM envelope
|
||||||
|
let stopFm, fmEnvelope;
|
||||||
if (fmModulationIndex) {
|
if (fmModulationIndex) {
|
||||||
const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex);
|
const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex, fmWaveform);
|
||||||
modulator.connect(o.frequency);
|
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;
|
stopFm = stop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// turn down
|
||||||
const g = gainNode(0.3);
|
const g = gainNode(0.3);
|
||||||
// envelope
|
|
||||||
|
// gain envelope
|
||||||
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t);
|
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t);
|
||||||
|
|
||||||
o.onended = () => {
|
o.onended = () => {
|
||||||
o.disconnect();
|
o.disconnect();
|
||||||
g.disconnect();
|
g.disconnect();
|
||||||
@ -66,6 +95,7 @@ export function registerSynthSounds() {
|
|||||||
node: o.connect(g).connect(envelope),
|
node: o.connect(g).connect(envelope),
|
||||||
stop: (releaseTime) => {
|
stop: (releaseTime) => {
|
||||||
releaseEnvelope(releaseTime);
|
releaseEnvelope(releaseTime);
|
||||||
|
fmEnvelope?.stop(releaseTime);
|
||||||
let end = releaseTime + release;
|
let end = releaseTime + release;
|
||||||
stop(end);
|
stop(end);
|
||||||
stopFm?.(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`] = `
|
exports[`runs examples > example "fmh" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c fmi:4 fmh: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`] = `
|
exports[`runs examples > example "focus" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/8 | s:sd ]",
|
"[ 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} />
|
<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)...
|
Next up: [Audio Effects](/learn/effects)...
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user