Working version
This commit is contained in:
parent
0ecacae71e
commit
c9381a11f9
2 changed files with 213 additions and 55 deletions
|
|
@ -72,6 +72,27 @@ export function registerControl(names, ...aliases) {
|
||||||
return bag;
|
return bag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function registerMultiControl(names, maxControls, ...aliases) {
|
||||||
|
names = Array.isArray(names) ? names : [names];
|
||||||
|
let bag = {};
|
||||||
|
for (let i = 1; i <= maxControls; i++) {
|
||||||
|
let theseAliases = [...aliases];
|
||||||
|
let theseNames = [...names];
|
||||||
|
if (i === 1) {
|
||||||
|
// adds e.g. fm1 as an alias for fm
|
||||||
|
const aliases1 = theseAliases.map((a) => `${a}1`);
|
||||||
|
const names1 = theseNames.map((n) => `${n}1`);
|
||||||
|
theseAliases = theseAliases.concat(aliases1).concat(names1);
|
||||||
|
} else {
|
||||||
|
theseAliases = theseAliases.map((a) => `${a}${i}`);
|
||||||
|
theseNames = theseNames.map((n) => `${n}${i}`);
|
||||||
|
}
|
||||||
|
const subBag = registerControl(theseNames, ...theseAliases);
|
||||||
|
bag = { ...bag, ...subBag };
|
||||||
|
}
|
||||||
|
return bag;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select a sound / sample by name. When using mininotation, you can also optionally supply 'n' and 'gain' parameters
|
* Select a sound / sample by name. When using mininotation, you can also optionally supply 'n' and 'gain' parameters
|
||||||
* separated by ':'.
|
* separated by ':'.
|
||||||
|
|
@ -444,21 +465,22 @@ export const { attack, att } = registerControl('attack', 'att');
|
||||||
* ._scope()
|
* ._scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { fmh } = registerControl(['fmh', 'fmi'], 'fmh');
|
export const { fmh, fmh1, fmh2, fmh3, fmh4, fmh5, fmh6, fmh7, fmh8 } = registerMultiControl(['fmh', 'fmi'], 8, 'fmh');
|
||||||
/**
|
/**
|
||||||
* Sets the Frequency Modulation of the synth.
|
* Sets the Frequency Modulation of the synth.
|
||||||
* Controls the modulation index, which defines the brightness of the sound.
|
* Controls the modulation index, which defines the brightness of the sound.
|
||||||
*
|
*
|
||||||
* @name fm
|
* @name fmi
|
||||||
* @param {number | Pattern} brightness modulation index
|
* @param {number | Pattern} brightness modulation index
|
||||||
* @synonyms fmi
|
* @synonyms fm
|
||||||
* @example
|
* @example
|
||||||
* note("c e g b g e")
|
* note("c e g b g e")
|
||||||
* .fm("<0 1 2 8 32>")
|
* .fm("<0 1 2 8 32>")
|
||||||
* ._scope()
|
* ._scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm');
|
export const { fmi, fmi1, fmi2, fmi3, fmi4, fmi5, fmi6, fmi7, fmi8, fm, fm1, fm2, fm3, fm4, fm5, fm6, fm7, fm8 } =
|
||||||
|
registerMultiControl(['fmi', 'fmh'], 8, 'fm');
|
||||||
// fm envelope
|
// fm envelope
|
||||||
/**
|
/**
|
||||||
* Ramp type of fm envelope. Exp might be a bit broken..
|
* Ramp type of fm envelope. Exp might be a bit broken..
|
||||||
|
|
@ -474,11 +496,15 @@ export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm');
|
||||||
* ._scope()
|
* ._scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { fmenv } = registerControl('fmenv');
|
export const { fmenv, fmenv1, fmenv2, fmenv3, fmenv4, fmenv5, fmenv6, fmenv7, fmenv8 } = registerMultiControl(
|
||||||
|
'fmenv',
|
||||||
|
8,
|
||||||
|
);
|
||||||
/**
|
/**
|
||||||
* Attack time for the FM envelope: time it takes to reach maximum modulation
|
* Attack time for the FM envelope: time it takes to reach maximum modulation
|
||||||
*
|
*
|
||||||
* @name fmattack
|
* @name fmattack
|
||||||
|
* @synonyms fmatt
|
||||||
* @param {number | Pattern} time attack time
|
* @param {number | Pattern} time attack time
|
||||||
* @example
|
* @example
|
||||||
* note("c e g b g e")
|
* note("c e g b g e")
|
||||||
|
|
@ -487,7 +513,26 @@ export const { fmenv } = registerControl('fmenv');
|
||||||
* ._scope()
|
* ._scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { fmattack } = registerControl('fmattack');
|
export const {
|
||||||
|
fmattack,
|
||||||
|
fmattack1,
|
||||||
|
fmattack2,
|
||||||
|
fmattack3,
|
||||||
|
fmattack4,
|
||||||
|
fmattack5,
|
||||||
|
fmattack6,
|
||||||
|
fmattack7,
|
||||||
|
fmattack8,
|
||||||
|
fmatt,
|
||||||
|
fmatt1,
|
||||||
|
fmatt2,
|
||||||
|
fmatt3,
|
||||||
|
fmatt4,
|
||||||
|
fmatt5,
|
||||||
|
fmatt6,
|
||||||
|
fmatt7,
|
||||||
|
fmatt8,
|
||||||
|
} = registerMultiControl('fmattack', 8, 'fmatt');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waveform of the fm modulator
|
* Waveform of the fm modulator
|
||||||
|
|
@ -500,12 +545,16 @@ export const { fmattack } = registerControl('fmattack');
|
||||||
* n("0 1 2 3".fast(4)).chord("<Dm Am F G>").voicing().s("sawtooth").fmwave("brown").fm(.6)
|
* n("0 1 2 3".fast(4)).chord("<Dm Am F G>").voicing().s("sawtooth").fmwave("brown").fm(.6)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { fmwave } = registerControl('fmwave');
|
export const { fmwave, fmwave1, fmwave2, fmwave3, fmwave4, fmwave5, fmwave6, fmwave7, fmwave8 } = registerMultiControl(
|
||||||
|
'fmwave',
|
||||||
|
8,
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase.
|
* Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase.
|
||||||
*
|
*
|
||||||
* @name fmdecay
|
* @name fmdecay
|
||||||
|
* @synonyms fmdec
|
||||||
* @param {number | Pattern} time decay time
|
* @param {number | Pattern} time decay time
|
||||||
* @example
|
* @example
|
||||||
* note("c e g b g e")
|
* note("c e g b g e")
|
||||||
|
|
@ -515,11 +564,31 @@ export const { fmwave } = registerControl('fmwave');
|
||||||
* ._scope()
|
* ._scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { fmdecay } = registerControl('fmdecay');
|
export const {
|
||||||
|
fmdecay,
|
||||||
|
fmdecay1,
|
||||||
|
fmdecay2,
|
||||||
|
fmdecay3,
|
||||||
|
fmdecay4,
|
||||||
|
fmdecay5,
|
||||||
|
fmdecay6,
|
||||||
|
fmdecay7,
|
||||||
|
fmdecay8,
|
||||||
|
fmdec,
|
||||||
|
fmdec1,
|
||||||
|
fmdec2,
|
||||||
|
fmdec3,
|
||||||
|
fmdec4,
|
||||||
|
fmdec5,
|
||||||
|
fmdec6,
|
||||||
|
fmdec7,
|
||||||
|
fmdec8,
|
||||||
|
} = registerMultiControl('fmdecay', 8, 'fmdec');
|
||||||
/**
|
/**
|
||||||
* Sustain level for the FM envelope: how much modulation is applied after the decay phase
|
* Sustain level for the FM envelope: how much modulation is applied after the decay phase
|
||||||
*
|
*
|
||||||
* @name fmsustain
|
* @name fmsustain
|
||||||
|
* @synonyms fmsus
|
||||||
* @param {number | Pattern} level sustain level
|
* @param {number | Pattern} level sustain level
|
||||||
* @example
|
* @example
|
||||||
* note("c e g b g e")
|
* note("c e g b g e")
|
||||||
|
|
@ -529,10 +598,61 @@ export const { fmdecay } = registerControl('fmdecay');
|
||||||
* ._scope()
|
* ._scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { fmsustain } = registerControl('fmsustain');
|
export const {
|
||||||
// these are not really useful... skipping for now
|
fmsustain,
|
||||||
export const { fmrelease } = registerControl('fmrelease');
|
fmsustain1,
|
||||||
export const { fmvelocity } = registerControl('fmvelocity');
|
fmsustain2,
|
||||||
|
fmsustain3,
|
||||||
|
fmsustain4,
|
||||||
|
fmsustain5,
|
||||||
|
fmsustain6,
|
||||||
|
fmsustain7,
|
||||||
|
fmsustain8,
|
||||||
|
fmsus,
|
||||||
|
fmsus1,
|
||||||
|
fmsus2,
|
||||||
|
fmsus3,
|
||||||
|
fmsus4,
|
||||||
|
fmsus5,
|
||||||
|
fmsus6,
|
||||||
|
fmsus7,
|
||||||
|
fmsus8,
|
||||||
|
} = registerMultiControl('fmsustain', 8, 'fmsus');
|
||||||
|
/**
|
||||||
|
* Release time for the FM envelope: how much modulation is applied after the note is released
|
||||||
|
*
|
||||||
|
* @name fmrelease
|
||||||
|
* @synonyms fmrel
|
||||||
|
* @param {number | Pattern} time release time
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const {
|
||||||
|
fmrelease,
|
||||||
|
fmrelease1,
|
||||||
|
fmrelease2,
|
||||||
|
fmrelease3,
|
||||||
|
fmrelease4,
|
||||||
|
fmrelease5,
|
||||||
|
fmrelease6,
|
||||||
|
fmrelease7,
|
||||||
|
fmrelease8,
|
||||||
|
fmrel,
|
||||||
|
fmrel1,
|
||||||
|
fmrel2,
|
||||||
|
fmrel3,
|
||||||
|
fmrel4,
|
||||||
|
fmrel5,
|
||||||
|
fmrel6,
|
||||||
|
fmrel7,
|
||||||
|
fmrel8,
|
||||||
|
} = registerMultiControl('fmrelease', 8, 'fmrel');
|
||||||
|
|
||||||
|
// FM Matrix
|
||||||
|
for (let i = 0; i <= 8; i++) {
|
||||||
|
for (let j = 0; j <= 8; j++) {
|
||||||
|
registerControl(`fm${i}${j}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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`.
|
||||||
|
|
|
||||||
|
|
@ -325,7 +325,7 @@ const mod = (freq, range = 1, type = 'sine') => {
|
||||||
osc.start();
|
osc.start();
|
||||||
const g = new GainNode(ctx, { gain: range });
|
const g = new GainNode(ctx, { gain: range });
|
||||||
osc.connect(g); // -range, range
|
osc.connect(g); // -range, range
|
||||||
return { node: g, stop: (t) => osc.stop(t) };
|
return { fm: osc, scaled: g, stop: (t) => osc.stop(t) };
|
||||||
};
|
};
|
||||||
const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => {
|
const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => {
|
||||||
const carrfreq = frequencyparam.value;
|
const carrfreq = frequencyparam.value;
|
||||||
|
|
@ -334,34 +334,33 @@ const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') =>
|
||||||
return mod(modfreq, modgain, wave);
|
return mod(modfreq, modgain, wave);
|
||||||
};
|
};
|
||||||
export function applyFM(param, value, begin) {
|
export function applyFM(param, value, begin) {
|
||||||
const {
|
|
||||||
fmh: fmHarmonicity = 1,
|
|
||||||
fmi: fmModulationIndex,
|
|
||||||
fmenv: fmEnvelopeType = 'exp',
|
|
||||||
fmattack: fmAttack,
|
|
||||||
fmdecay: fmDecay,
|
|
||||||
fmsustain: fmSustain,
|
|
||||||
fmrelease: fmRelease,
|
|
||||||
fmvelocity: fmVelocity,
|
|
||||||
fmwave: fmWaveform = 'sine',
|
|
||||||
duration,
|
|
||||||
} = value;
|
|
||||||
let modulator;
|
|
||||||
let stop = () => {};
|
|
||||||
|
|
||||||
if (fmModulationIndex) {
|
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const envGain = ac.createGain();
|
let target = param;
|
||||||
const fmmod = fm(param, fmHarmonicity, fmModulationIndex, fmWaveform);
|
let stop = (t) => {};
|
||||||
|
const targets = [param];
|
||||||
modulator = fmmod.node;
|
const modulators = [];
|
||||||
stop = fmmod.stop;
|
for (let i = 0; i < 8; i++) {
|
||||||
if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].some((v) => v !== undefined)) {
|
const iS = `${i === 0 ? '' : i}`;
|
||||||
|
let modulator;
|
||||||
|
const fmModulationIndex = value[`fmi${iS}`];
|
||||||
|
if (fmModulationIndex) {
|
||||||
|
const fmmod = fm(target, value[`fmh${iS}`] ?? 1, fmModulationIndex, value[`fmwave${iS}`] ?? 'sine');
|
||||||
|
modulator = fmmod.scaled;
|
||||||
|
const currStop = stop;
|
||||||
|
stop = (t) => {
|
||||||
|
currStop(t);
|
||||||
|
fmmod.stop(t);
|
||||||
|
};
|
||||||
|
const adsr = ['attack', 'decay', 'sustain', 'release'].map((s) => value[`fm${s}${iS}`]);
|
||||||
|
if (!adsr.some((v) => v !== undefined)) {
|
||||||
// no envelope by default
|
// no envelope by default
|
||||||
modulator.connect(param);
|
modulator.connect(target);
|
||||||
|
modulators.push(modulator);
|
||||||
} else {
|
} else {
|
||||||
const [attack, decay, sustain, release] = getADSRValues([fmAttack, fmDecay, fmSustain, fmRelease]);
|
const envGain = ac.createGain();
|
||||||
const holdEnd = begin + duration;
|
const [attack, decay, sustain, release] = getADSRValues(adsr);
|
||||||
|
const holdEnd = begin + value.duration;
|
||||||
|
const fmEnvelopeType = value[`fmenv${iS}`] ?? 'exp';
|
||||||
getParamADSR(
|
getParamADSR(
|
||||||
envGain.gain,
|
envGain.gain,
|
||||||
attack,
|
attack,
|
||||||
|
|
@ -375,7 +374,46 @@ export function applyFM(param, value, begin) {
|
||||||
fmEnvelopeType === 'exp' ? 'exponential' : 'linear',
|
fmEnvelopeType === 'exp' ? 'exponential' : 'linear',
|
||||||
);
|
);
|
||||||
modulator.connect(envGain);
|
modulator.connect(envGain);
|
||||||
envGain.connect(param);
|
envGain.connect(target);
|
||||||
|
modulators.push(envGain);
|
||||||
|
}
|
||||||
|
targets.push(fmmod.fm?.frequency);
|
||||||
|
if (targets[i]) {
|
||||||
|
// Cannot connect to noises, for example
|
||||||
|
target = targets[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Matrix
|
||||||
|
for (let i = 1; i <= 8; i++) {
|
||||||
|
for (let j = 0; j <= 8; j++) {
|
||||||
|
let control;
|
||||||
|
if (i === j + 1) {
|
||||||
|
// Standard fm3 -> fm2 -> fm1 -> param usage
|
||||||
|
control = `fm${i}`;
|
||||||
|
} else {
|
||||||
|
control = `fm${i}${j}`;
|
||||||
|
}
|
||||||
|
const amt = value[control];
|
||||||
|
if (!amt) continue;
|
||||||
|
const source = modulators[i - 1];
|
||||||
|
const target = targets[j];
|
||||||
|
if (!source) {
|
||||||
|
const iS = `${i === 1 ? '' : i}`;
|
||||||
|
logger(
|
||||||
|
`[superdough] failed to connect FM from source ${i} (due to control ${control}): source ${i} does not exist. Please use control fm${iS} to set it up`,
|
||||||
|
'warning',
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!target) {
|
||||||
|
logger(
|
||||||
|
`[superdough] failed to connect FM to target ${j} (due to control ${control}): target ${j} does not exist. Please use control fm${j} to set it up`,
|
||||||
|
'warning',
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
source.connect(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { stop };
|
return { stop };
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue