Merge pull request #913 from tidalcycles/pitch-envelopes
pitch envelope
This commit is contained in:
commit
bd83d19197
7 changed files with 261 additions and 39 deletions
|
|
@ -891,6 +891,82 @@ const generic_params = [
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
['freq'],
|
['freq'],
|
||||||
|
// pitch envelope
|
||||||
|
/**
|
||||||
|
* Attack time of pitch envelope.
|
||||||
|
*
|
||||||
|
* @name pattack
|
||||||
|
* @synonyms patt
|
||||||
|
* @param {number | Pattern} time time in seconds
|
||||||
|
* @example
|
||||||
|
* note("<c eb g bb>").pattack("<0 .1 .25 .5>")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
['pattack', 'patt'],
|
||||||
|
/**
|
||||||
|
* Decay time of pitch envelope.
|
||||||
|
*
|
||||||
|
* @name pdecay
|
||||||
|
* @synonyms pdec
|
||||||
|
* @param {number | Pattern} time time in seconds
|
||||||
|
* @example
|
||||||
|
* note("<c eb g bb>").pdecay("<0 .1 .25 .5>")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
['pdecay', 'pdec'],
|
||||||
|
// TODO: how to use psustain?!
|
||||||
|
['psustain', 'psus'],
|
||||||
|
/**
|
||||||
|
* Release time of pitch envelope
|
||||||
|
*
|
||||||
|
* @name prelease
|
||||||
|
* @synonyms prel
|
||||||
|
* @param {number | Pattern} time time in seconds
|
||||||
|
* @example
|
||||||
|
* note("<c eb g bb> ~")
|
||||||
|
* .release(.5) // to hear the pitch release
|
||||||
|
* .prelease("<0 .1 .25 .5>")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
['prelease', 'prel'],
|
||||||
|
/**
|
||||||
|
* Amount of pitch envelope. Negative values will flip the envelope.
|
||||||
|
* If you don't set other pitch envelope controls, `pattack:.2` will be the default.
|
||||||
|
*
|
||||||
|
* @name penv
|
||||||
|
* @param {number | Pattern} semitones change in semitones
|
||||||
|
* @example
|
||||||
|
* note("c")
|
||||||
|
* .penv("<12 7 1 .5 0 -1 -7 -12>")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
['penv'],
|
||||||
|
/**
|
||||||
|
* Curve of envelope. Defaults to linear. exponential is good for kicks
|
||||||
|
*
|
||||||
|
* @name pcurve
|
||||||
|
* @param {number | Pattern} type 0 = linear, 1 = exponential
|
||||||
|
* @example
|
||||||
|
* note("g1*2")
|
||||||
|
* .s("sine").pdec(.5)
|
||||||
|
* .penv(32)
|
||||||
|
* .pcurve("<0 1>")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
['pcurve'],
|
||||||
|
/**
|
||||||
|
* Sets the range anchor of the envelope:
|
||||||
|
* - anchor 0: range = [note, note + penv]
|
||||||
|
* - anchor 1: range = [note - penv, note]
|
||||||
|
* If you don't set an anchor, the value will default to the psustain value.
|
||||||
|
*
|
||||||
|
* @name panchor
|
||||||
|
* @param {number | Pattern} anchor anchor offset
|
||||||
|
* @example
|
||||||
|
* note("c").penv(12).panchor("<0 .5 1 .5>")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
['panchor'],
|
||||||
// TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate
|
// TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate
|
||||||
['gate', 'gat'],
|
['gate', 'gat'],
|
||||||
// ['hatgrain'],
|
// ['hatgrain'],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
import { noteToMidi, freqToMidi, getSoundIndex } from '@strudel.cycles/core';
|
import { noteToMidi, freqToMidi, getSoundIndex } from '@strudel.cycles/core';
|
||||||
import { getAudioContext, registerSound, getParamADSR, getADSRValues } from '@strudel.cycles/webaudio';
|
import {
|
||||||
|
getAudioContext,
|
||||||
|
registerSound,
|
||||||
|
getParamADSR,
|
||||||
|
getADSRValues,
|
||||||
|
getPitchEnvelope,
|
||||||
|
getVibratoOscillator,
|
||||||
|
} from '@strudel.cycles/webaudio';
|
||||||
import gm from './gm.mjs';
|
import gm from './gm.mjs';
|
||||||
|
|
||||||
let loadCache = {};
|
let loadCache = {};
|
||||||
|
|
@ -149,10 +156,16 @@ export function registerSoundfonts() {
|
||||||
getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, time, holdEnd, 'linear');
|
getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, time, holdEnd, 'linear');
|
||||||
let envEnd = holdEnd + release + 0.01;
|
let envEnd = holdEnd + release + 0.01;
|
||||||
|
|
||||||
|
// vibrato
|
||||||
|
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, time);
|
||||||
|
// pitch envelope
|
||||||
|
getPitchEnvelope(bufferSource.detune, value, time, holdEnd);
|
||||||
|
|
||||||
bufferSource.stop(envEnd);
|
bufferSource.stop(envEnd);
|
||||||
const stop = (releaseTime) => {};
|
const stop = (releaseTime) => {};
|
||||||
bufferSource.onended = () => {
|
bufferSource.onended = () => {
|
||||||
bufferSource.disconnect();
|
bufferSource.disconnect();
|
||||||
|
vibratoOscillator?.stop();
|
||||||
node.disconnect();
|
node.disconnect();
|
||||||
onended();
|
onended();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,10 @@ export const getParamADSR = (
|
||||||
decay = nanFallback(decay);
|
decay = nanFallback(decay);
|
||||||
sustain = nanFallback(sustain);
|
sustain = nanFallback(sustain);
|
||||||
release = nanFallback(release);
|
release = nanFallback(release);
|
||||||
|
|
||||||
const ramp = curve === 'exponential' ? 'exponentialRampToValueAtTime' : 'linearRampToValueAtTime';
|
const ramp = curve === 'exponential' ? 'exponentialRampToValueAtTime' : 'linearRampToValueAtTime';
|
||||||
if (curve === 'exponential') {
|
if (curve === 'exponential') {
|
||||||
min = Math.max(0.0001, min);
|
min = min === 0 ? 0.001 : min;
|
||||||
|
max = max === 0 ? 0.001 : max;
|
||||||
}
|
}
|
||||||
const range = max - min;
|
const range = max - min;
|
||||||
const peak = max;
|
const peak = max;
|
||||||
|
|
@ -42,12 +42,17 @@ export const getParamADSR = (
|
||||||
const duration = end - begin;
|
const duration = end - begin;
|
||||||
|
|
||||||
const envValAtTime = (time) => {
|
const envValAtTime = (time) => {
|
||||||
|
let val;
|
||||||
if (attack > time) {
|
if (attack > time) {
|
||||||
let slope = getSlope(min, peak, 0, attack);
|
let slope = getSlope(min, peak, 0, attack);
|
||||||
return time * slope + (min > peak ? min : 0);
|
val = time * slope + (min > peak ? min : 0);
|
||||||
} else {
|
} else {
|
||||||
return (time - attack) * getSlope(peak, sustainVal, 0, decay) + peak;
|
val = (time - attack) * getSlope(peak, sustainVal, 0, decay) + peak;
|
||||||
}
|
}
|
||||||
|
if (curve === 'exponential') {
|
||||||
|
val = val || 0.001;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
};
|
};
|
||||||
|
|
||||||
param.setValueAtTime(min, begin);
|
param.setValueAtTime(min, begin);
|
||||||
|
|
@ -144,3 +149,40 @@ export function drywet(dry, wet, wetAmount = 0) {
|
||||||
wet_gain.connect(mix);
|
wet_gain.connect(mix);
|
||||||
return mix;
|
return mix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let curves = ['linear', 'exponential'];
|
||||||
|
export function getPitchEnvelope(param, value, t, holdEnd) {
|
||||||
|
// envelope is active when any of these values is set
|
||||||
|
const hasEnvelope = value.pattack ?? value.pdecay ?? value.psustain ?? value.prelease ?? value.penv;
|
||||||
|
if (!hasEnvelope) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const penv = nanFallback(value.penv, 1, true);
|
||||||
|
const curve = curves[value.pcurve ?? 0];
|
||||||
|
let [pattack, pdecay, psustain, prelease] = getADSRValues(
|
||||||
|
[value.pattack, value.pdecay, value.psustain, value.prelease],
|
||||||
|
curve,
|
||||||
|
[0.2, 0.001, 1, 0.001],
|
||||||
|
);
|
||||||
|
let panchor = value.panchor ?? psustain;
|
||||||
|
const cents = penv * 100; // penv is in semitones
|
||||||
|
const min = 0 - cents * panchor;
|
||||||
|
const max = cents - cents * panchor;
|
||||||
|
getParamADSR(param, pattack, pdecay, psustain, prelease, min, max, t, holdEnd, curve);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVibratoOscillator(param, value, t) {
|
||||||
|
const { vibmod = 0.5, vib } = value;
|
||||||
|
let vibratoOscillator;
|
||||||
|
if (vib > 0) {
|
||||||
|
vibratoOscillator = getAudioContext().createOscillator();
|
||||||
|
vibratoOscillator.frequency.value = vib;
|
||||||
|
const gain = getAudioContext().createGain();
|
||||||
|
// Vibmod is the amount of vibrato, in semitones
|
||||||
|
gain.gain.value = vibmod * 100;
|
||||||
|
vibratoOscillator.connect(gain);
|
||||||
|
gain.connect(param);
|
||||||
|
vibratoOscillator.start(t);
|
||||||
|
return vibratoOscillator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { noteToMidi, valueToMidi, getSoundIndex } from './util.mjs';
|
import { noteToMidi, valueToMidi, getSoundIndex } from './util.mjs';
|
||||||
import { getAudioContext, registerSound } from './index.mjs';
|
import { getAudioContext, registerSound } from './index.mjs';
|
||||||
import { getADSRValues, getParamADSR } from './helpers.mjs';
|
import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
|
||||||
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
||||||
|
|
@ -244,8 +244,6 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
||||||
loopEnd = 1,
|
loopEnd = 1,
|
||||||
end = 1,
|
end = 1,
|
||||||
duration,
|
duration,
|
||||||
vib,
|
|
||||||
vibmod = 0.5,
|
|
||||||
} = value;
|
} = value;
|
||||||
// load sample
|
// load sample
|
||||||
if (speed === 0) {
|
if (speed === 0) {
|
||||||
|
|
@ -263,17 +261,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
||||||
const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank, resolveUrl);
|
const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank, resolveUrl);
|
||||||
|
|
||||||
// vibrato
|
// vibrato
|
||||||
let vibratoOscillator;
|
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t);
|
||||||
if (vib > 0) {
|
|
||||||
vibratoOscillator = getAudioContext().createOscillator();
|
|
||||||
vibratoOscillator.frequency.value = vib;
|
|
||||||
const gain = getAudioContext().createGain();
|
|
||||||
// Vibmod is the amount of vibrato, in semitones
|
|
||||||
gain.gain.value = vibmod * 100;
|
|
||||||
vibratoOscillator.connect(gain);
|
|
||||||
gain.connect(bufferSource.detune);
|
|
||||||
vibratoOscillator.start(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// asny stuff above took too long?
|
// asny stuff above took too long?
|
||||||
if (ac.currentTime > t) {
|
if (ac.currentTime > t) {
|
||||||
|
|
@ -310,6 +298,9 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
||||||
|
|
||||||
getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear');
|
getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear');
|
||||||
|
|
||||||
|
// pitch envelope
|
||||||
|
getPitchEnvelope(bufferSource.detune, value, t, holdEnd);
|
||||||
|
|
||||||
const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox...
|
const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox...
|
||||||
node.connect(out);
|
node.connect(out);
|
||||||
bufferSource.onended = function () {
|
bufferSource.onended = function () {
|
||||||
|
|
|
||||||
|
|
@ -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, getADSRValues, getParamADSR } from './helpers.mjs';
|
import { gainNode, getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
|
||||||
import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
|
import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
|
||||||
|
|
||||||
const mod = (freq, range = 1, type = 'sine') => {
|
const mod = (freq, range = 1, type = 'sine') => {
|
||||||
|
|
@ -105,15 +105,11 @@ export function waveformN(partials, type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// expects one of waveforms as s
|
// expects one of waveforms as s
|
||||||
export function getOscillator(
|
export function getOscillator(s, t, value) {
|
||||||
s,
|
let {
|
||||||
t,
|
|
||||||
{
|
|
||||||
n: partials,
|
n: partials,
|
||||||
note,
|
note,
|
||||||
freq,
|
freq,
|
||||||
vib = 0,
|
|
||||||
vibmod = 0.5,
|
|
||||||
noise = 0,
|
noise = 0,
|
||||||
// fm
|
// fm
|
||||||
fmh: fmHarmonicity = 1,
|
fmh: fmHarmonicity = 1,
|
||||||
|
|
@ -126,8 +122,7 @@ export function getOscillator(
|
||||||
fmvelocity: fmVelocity,
|
fmvelocity: fmVelocity,
|
||||||
fmwave: fmWaveform = 'sine',
|
fmwave: fmWaveform = 'sine',
|
||||||
duration,
|
duration,
|
||||||
},
|
} = value;
|
||||||
) {
|
|
||||||
let ac = getAudioContext();
|
let ac = getAudioContext();
|
||||||
let o;
|
let o;
|
||||||
// If no partials are given, use stock waveforms
|
// If no partials are given, use stock waveforms
|
||||||
|
|
@ -184,17 +179,10 @@ export function getOscillator(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional oscillator for vibrato effect
|
// Additional oscillator for vibrato effect
|
||||||
let vibratoOscillator;
|
let vibratoOscillator = getVibratoOscillator(o.detune, value, t);
|
||||||
if (vib > 0) {
|
|
||||||
vibratoOscillator = getAudioContext().createOscillator();
|
// pitch envelope
|
||||||
vibratoOscillator.frequency.value = vib;
|
getPitchEnvelope(o.detune, value, t, t + duration);
|
||||||
const gain = getAudioContext().createGain();
|
|
||||||
// Vibmod is the amount of vibrato, in semitones
|
|
||||||
gain.gain.value = vibmod * 100;
|
|
||||||
vibratoOscillator.connect(gain);
|
|
||||||
gain.connect(o.detune);
|
|
||||||
vibratoOscillator.start(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
let noiseMix;
|
let noiseMix;
|
||||||
if (noise) {
|
if (noise) {
|
||||||
|
|
|
||||||
|
|
@ -3332,6 +3332,55 @@ exports[`runs examples > example "pan" example index 0 1`] = `
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "panchor" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/1 | note:c penv:12 panchor:0 ]",
|
||||||
|
"[ 1/1 → 2/1 | note:c penv:12 panchor:0.5 ]",
|
||||||
|
"[ 2/1 → 3/1 | note:c penv:12 panchor:1 ]",
|
||||||
|
"[ 3/1 → 4/1 | note:c penv:12 panchor:0.5 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "pattack" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/1 | note:c pattack:0 ]",
|
||||||
|
"[ 1/1 → 2/1 | note:eb pattack:0.1 ]",
|
||||||
|
"[ 2/1 → 3/1 | note:g pattack:0.25 ]",
|
||||||
|
"[ 3/1 → 4/1 | note:bb pattack:0.5 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "pcurve" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
|
||||||
|
"[ 1/2 → 1/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
|
||||||
|
"[ 1/1 → 3/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
|
||||||
|
"[ 3/2 → 2/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
|
||||||
|
"[ 2/1 → 5/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
|
||||||
|
"[ 5/2 → 3/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
|
||||||
|
"[ 3/1 → 7/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
|
||||||
|
"[ 7/2 → 4/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "pdecay" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/1 | note:c pdecay:0 ]",
|
||||||
|
"[ 1/1 → 2/1 | note:eb pdecay:0.1 ]",
|
||||||
|
"[ 2/1 → 3/1 | note:g pdecay:0.25 ]",
|
||||||
|
"[ 3/1 → 4/1 | note:bb pdecay:0.5 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "penv" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/1 | note:c penv:12 ]",
|
||||||
|
"[ 1/1 → 2/1 | note:c penv:7 ]",
|
||||||
|
"[ 2/1 → 3/1 | note:c penv:1 ]",
|
||||||
|
"[ 3/1 → 4/1 | note:c penv:0.5 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "perlin" example index 0 1`] = `
|
exports[`runs examples > example "perlin" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | s:hh cutoff:512.5097280354112 ]",
|
"[ 0/1 → 1/4 | s:hh cutoff:512.5097280354112 ]",
|
||||||
|
|
@ -3660,6 +3709,15 @@ exports[`runs examples > example "postgain" example index 0 1`] = `
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "prelease" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/2 | note:c release:0.5 prelease:0 ]",
|
||||||
|
"[ 1/1 → 3/2 | note:eb release:0.5 prelease:0.1 ]",
|
||||||
|
"[ 2/1 → 5/2 | note:g release:0.5 prelease:0.25 ]",
|
||||||
|
"[ 3/1 → 7/2 | note:bb release:0.5 prelease:0.5 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "press" example index 0 1`] = `
|
exports[`runs examples > example "press" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/2 | s:hh ]",
|
"[ 0/1 → 1/2 | s:hh ]",
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,60 @@ There is one filter envelope for each filter type and thus one set of envelope f
|
||||||
|
|
||||||
<JsDoc client:idle name="lpenv" h={0} />
|
<JsDoc client:idle name="lpenv" h={0} />
|
||||||
|
|
||||||
|
# Pitch Envelope
|
||||||
|
|
||||||
|
You can also control the pitch with envelopes!
|
||||||
|
Pitch envelopes can breathe life into static sounds:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:idle
|
||||||
|
tune={`n("<-4,0 5 2 1>*<2!3 4>")
|
||||||
|
.scale("<C F>/8:pentatonic")
|
||||||
|
.s("gm_electric_guitar_jazz")
|
||||||
|
.penv("<.5 0 7 -2>*2").vib("4:.1")
|
||||||
|
.phaser(2).delay(.25).room(.3)
|
||||||
|
.size(4).fast(.75)`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
You also create some lovely chiptune-style sounds:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:idle
|
||||||
|
tune={`n(run("<4 8>/16")).jux(rev)
|
||||||
|
.chord("<C^7 <Db^7 Fm7>>")
|
||||||
|
.dict('ireal')
|
||||||
|
.voicing().add(note("<0 1>/8"))
|
||||||
|
.dec(.1).room(.2)
|
||||||
|
.segment("<4 [2 8]>")
|
||||||
|
.penv("<0 <2 -2>>").patt(.02)`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
Let's break down all pitch envelope controls:
|
||||||
|
|
||||||
|
## pattack
|
||||||
|
|
||||||
|
<JsDoc client:idle name="pattack" h={0} />
|
||||||
|
|
||||||
|
## pdecay
|
||||||
|
|
||||||
|
<JsDoc client:idle name="pdecay" h={0} />
|
||||||
|
|
||||||
|
## prelease
|
||||||
|
|
||||||
|
<JsDoc client:idle name="prelease" h={0} />
|
||||||
|
|
||||||
|
## penv
|
||||||
|
|
||||||
|
<JsDoc client:idle name="penv" h={0} />
|
||||||
|
|
||||||
|
## pcurve
|
||||||
|
|
||||||
|
<JsDoc client:idle name="pcurve" h={0} />
|
||||||
|
|
||||||
|
## panchor
|
||||||
|
|
||||||
|
<JsDoc client:idle name="panchor" h={0} />
|
||||||
|
|
||||||
# Dynamics
|
# Dynamics
|
||||||
|
|
||||||
## gain
|
## gain
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue