Merge pull request 'Feature: Eight FMs' (#1628) from glossing/strudel:glossing/fmsss into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1628
This commit is contained in:
commit
e01621d0cc
3 changed files with 322 additions and 101 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 ':'.
|
||||||
|
|
@ -435,6 +456,9 @@ export const { attack, att } = registerControl('attack', 'att');
|
||||||
* Whole numbers and simple ratios sound more natural,
|
* Whole numbers and simple ratios sound more natural,
|
||||||
* while decimal numbers and complex ratios sound metallic.
|
* while decimal numbers and complex ratios sound metallic.
|
||||||
*
|
*
|
||||||
|
* A number may be added afterwards to control the harmonicity of
|
||||||
|
* any of the 8 individual FMs (e.g. `fmh2`)
|
||||||
|
*
|
||||||
* @name fmh
|
* @name fmh
|
||||||
* @param {number | Pattern} harmonicity
|
* @param {number | Pattern} harmonicity
|
||||||
* @example
|
* @example
|
||||||
|
|
@ -444,25 +468,40 @@ 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
|
* A number may be added afterwards to control the modulation index of
|
||||||
|
* any of the 8 individual FMs (e.g. `fm3`). Also, FMs may be routed into
|
||||||
|
* each other with matrix commands like `fm13`, which would send `fm1` back into
|
||||||
|
* `fm3`
|
||||||
|
*
|
||||||
|
* @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()
|
||||||
|
* @example
|
||||||
|
* s("sine").note("F1").seg(8)
|
||||||
|
* .fm(4).fm2(rand.mul(4)).fm3(saw.mul(8).slow(8))
|
||||||
|
* .fmh(1.06).fmh2(10).fmh3(0.1)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
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..
|
||||||
*
|
*
|
||||||
|
* A number may be added afterwards to control the envelope of
|
||||||
|
* any of the 8 individual FMs (e.g. `fmenv4`)
|
||||||
|
*
|
||||||
* @name fmenv
|
* @name fmenv
|
||||||
* @param {number | Pattern} type lin | exp
|
* @param {number | Pattern} type lin | exp
|
||||||
* @example
|
* @example
|
||||||
|
|
@ -474,11 +513,19 @@ 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
|
||||||
*
|
*
|
||||||
|
* A number may be added afterwards to control the attack of the envelope of
|
||||||
|
* any of the 8 individual FMs (e.g. `fmatt5`)
|
||||||
|
*
|
||||||
* @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,11 +534,33 @@ 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
|
||||||
*
|
*
|
||||||
|
* A number may be added afterwards to control the waveform
|
||||||
|
* any of the 8 individual FMs (e.g. `fmwave6`)
|
||||||
|
*
|
||||||
* @name fmwave
|
* @name fmwave
|
||||||
* @param {number | Pattern} wave waveform
|
* @param {number | Pattern} wave waveform
|
||||||
* @example
|
* @example
|
||||||
|
|
@ -500,12 +569,19 @@ 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.
|
||||||
*
|
*
|
||||||
|
* A number may be added afterwards to control the decay of the envelope of
|
||||||
|
* any of the 8 individual FMs (e.g. `fmdec6`)
|
||||||
|
*
|
||||||
* @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 +591,35 @@ 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
|
||||||
*
|
*
|
||||||
|
* A number may be added afterwards to control the sustain of the envelope of
|
||||||
|
* any of the 8 individual FMs (e.g. `fmsus7`)
|
||||||
|
*
|
||||||
* @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 +629,69 @@ 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
|
||||||
|
*
|
||||||
|
* A number may be added afterwards to control the release of the envelope of
|
||||||
|
* any of the 8 individual FMs (e.g. `fmrel8`)
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
// Note: we do not declare top-level exports here since it would add
|
||||||
|
// ~162 more explicit exports. This is likely fine as the most common use-case would be to at least
|
||||||
|
// declare one other FM prior to utilizing the matrix functionality, but if we ever decide we need it,
|
||||||
|
// TODO to add it explicitly / go with the globalThis approach
|
||||||
|
for (let i = 0; i <= 8; i++) {
|
||||||
|
for (let j = 0; j <= 8; j++) {
|
||||||
|
registerControl(`fmi${i}${j}`, `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`.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { getAudioContext } from './audioContext.mjs';
|
import { getAudioContext } from './audioContext.mjs';
|
||||||
import { clamp, nanFallback, midiToFreq, noteToMidi } from './util.mjs';
|
|
||||||
import { getNoiseBuffer } from './noise.mjs';
|
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
import { getNoiseBuffer } from './noise.mjs';
|
||||||
|
import { clamp, nanFallback, midiToFreq, noteToMidi } from './util.mjs';
|
||||||
|
|
||||||
export const noises = ['pink', 'white', 'brown', 'crackle'];
|
export const noises = ['pink', 'white', 'brown', 'crackle'];
|
||||||
|
|
||||||
|
|
@ -358,7 +358,8 @@ export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
||||||
constantNode.stop(stopTime);
|
constantNode.stop(stopTime);
|
||||||
return constantNode;
|
return constantNode;
|
||||||
}
|
}
|
||||||
const mod = (freq, range = 1, type = 'sine') => {
|
|
||||||
|
const mod = (freq, type = 'sine') => {
|
||||||
const ctx = getAudioContext();
|
const ctx = getAudioContext();
|
||||||
let osc;
|
let osc;
|
||||||
if (noises.includes(type)) {
|
if (noises.includes(type)) {
|
||||||
|
|
@ -370,69 +371,89 @@ const mod = (freq, range = 1, type = 'sine') => {
|
||||||
osc.type = type;
|
osc.type = type;
|
||||||
osc.frequency.value = freq;
|
osc.frequency.value = freq;
|
||||||
}
|
}
|
||||||
|
|
||||||
osc.start();
|
osc.start();
|
||||||
const g = gainNode(range);
|
return osc;
|
||||||
osc.connect(g); // -range, range
|
|
||||||
return { node: g, stop: (t) => osc.stop(t), osc: osc };
|
|
||||||
};
|
};
|
||||||
const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => {
|
|
||||||
|
const fm = (frequencyparam, harmonicityRatio, wave = 'sine') => {
|
||||||
const carrfreq = frequencyparam.value;
|
const carrfreq = frequencyparam.value;
|
||||||
const modfreq = carrfreq * harmonicityRatio;
|
const modfreq = carrfreq * harmonicityRatio;
|
||||||
const modgain = modfreq * modulationIndex;
|
return { osc: mod(modfreq, wave), freq: modfreq };
|
||||||
return mod(modfreq, modgain, wave);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function applyFM(param, value, begin) {
|
export function applyFM(param, value, begin) {
|
||||||
const {
|
const ac = getAudioContext();
|
||||||
fmh: fmHarmonicity = 1,
|
const toStop = []; // fm oscillators we will expose `stop` for
|
||||||
fmi: fmModulationIndex,
|
const fms = {};
|
||||||
fmenv: fmEnvelopeType = 'exp',
|
// Matrix
|
||||||
fmattack: fmAttack,
|
for (let i = 1; i <= 8; i++) {
|
||||||
fmdecay: fmDecay,
|
for (let j = 0; j <= 8; j++) {
|
||||||
fmsustain: fmSustain,
|
let control;
|
||||||
fmrelease: fmRelease,
|
if (i === j + 1) {
|
||||||
fmvelocity: fmVelocity,
|
// Standard fm3 -> fm2 -> fm1 -> param usage
|
||||||
fmwave: fmWaveform = 'sine',
|
const iS = i === 1 ? '' : i;
|
||||||
duration,
|
control = `fmi${iS}`;
|
||||||
} = value;
|
} else {
|
||||||
let modulator;
|
control = `fmi${i}${j}`;
|
||||||
let stop = () => {};
|
}
|
||||||
|
const amt = value[control];
|
||||||
if (fmModulationIndex) {
|
if (!amt) continue;
|
||||||
const ac = getAudioContext();
|
let io = [];
|
||||||
const envGain = ac.createGain();
|
for (let [isMod, idx] of [
|
||||||
const fmmod = fm(param, fmHarmonicity, fmModulationIndex, fmWaveform);
|
[true, i], // source
|
||||||
|
[false, j], // target
|
||||||
modulator = fmmod.node;
|
]) {
|
||||||
stop = fmmod.stop;
|
if (idx === 0) {
|
||||||
if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].some((v) => v !== undefined)) {
|
io.push(param);
|
||||||
// no envelope by default
|
continue;
|
||||||
modulator.connect(param);
|
}
|
||||||
} else {
|
if (!fms[idx]) {
|
||||||
const [attack, decay, sustain, release] = getADSRValues([fmAttack, fmDecay, fmSustain, fmRelease]);
|
const idxS = idx === 1 ? '' : idx;
|
||||||
const holdEnd = begin + duration;
|
const { osc, freq } = fm(param, value[`fmh${idxS}`] ?? 1, value[`fmwave${idxS}`] ?? 'sine');
|
||||||
getParamADSR(
|
toStop.push(osc);
|
||||||
envGain.gain,
|
const toCleanup = [osc]; // nodes we want to cleanup after oscillator `stop`
|
||||||
attack,
|
const adsr = ['attack', 'decay', 'sustain', 'release'].map((s) => value[`fm${s}${idxS}`]);
|
||||||
decay,
|
let output = osc;
|
||||||
sustain,
|
if (adsr.some((v) => v !== undefined)) {
|
||||||
release,
|
const envGain = ac.createGain();
|
||||||
0,
|
const [attack, decay, sustain, release] = getADSRValues(adsr);
|
||||||
1,
|
const holdEnd = begin + value.duration;
|
||||||
begin,
|
const fmEnvelopeType = value[`fmenv${idxS}`] ?? 'exp';
|
||||||
holdEnd,
|
getParamADSR(
|
||||||
fmEnvelopeType === 'exp' ? 'exponential' : 'linear',
|
envGain.gain,
|
||||||
);
|
attack,
|
||||||
modulator.connect(envGain);
|
decay,
|
||||||
envGain.connect(param);
|
sustain,
|
||||||
|
release,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
begin,
|
||||||
|
holdEnd,
|
||||||
|
fmEnvelopeType === 'exp' ? 'exponential' : 'linear',
|
||||||
|
);
|
||||||
|
toCleanup.push(envGain);
|
||||||
|
output = osc.connect(envGain);
|
||||||
|
}
|
||||||
|
fms[idx] = { input: osc.frequency, output, freq, osc, toCleanup };
|
||||||
|
}
|
||||||
|
const { input, output, freq, osc, toCleanup } = fms[idx];
|
||||||
|
const g = gainNode(amt * freq);
|
||||||
|
io.push(isMod ? output.connect(g) : input);
|
||||||
|
cleanupOnEnd(osc, [...toCleanup, g]);
|
||||||
|
}
|
||||||
|
if (!io[1]) {
|
||||||
|
logger(
|
||||||
|
`[superdough] control ${control} failed to connect FM ${i} to target ${j} due to missing frequency parameter (likely because fm${j} is noise)`,
|
||||||
|
'warning',
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
io[0].connect(io[1]);
|
||||||
}
|
}
|
||||||
fmmod.osc.onended = () => {
|
|
||||||
envGain.disconnect();
|
|
||||||
modulator.disconnect();
|
|
||||||
fmmod.osc.disconnect();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return { stop };
|
return {
|
||||||
|
stop: (t) => toStop.forEach((m) => m?.stop(t)),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saturation curves
|
// Saturation curves
|
||||||
|
|
@ -548,10 +569,9 @@ export const getFrequencyFromValue = (value, defaultNote = 36) => {
|
||||||
// This helper should be used instead of the `node.onended = callback` pattern
|
// This helper should be used instead of the `node.onended = callback` pattern
|
||||||
// It adds a mechanism to help minimize gc retention
|
// It adds a mechanism to help minimize gc retention
|
||||||
export const onceEnded = (node, callback) => {
|
export const onceEnded = (node, callback) => {
|
||||||
let onended = callback;
|
const onended = callback;
|
||||||
node.onended = function cleanup() {
|
node.onended = function cleanup() {
|
||||||
onended && onended();
|
onended && onended();
|
||||||
onended = null;
|
|
||||||
this.onended = null;
|
this.onended = null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -596,3 +616,8 @@ export const releaseAudioNode = (node) => {
|
||||||
node.parameters.get('end')?.setValueAtTime(0, 0);
|
node.parameters.get('end')?.setValueAtTime(0, 0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Once the `anchor` node has ended, release all nodes in `toCleanup`
|
||||||
|
export const cleanupOnEnd = (anchor, toCleanup) => {
|
||||||
|
onceEnded(anchor, () => toCleanup.forEach((n) => releaseAudioNode(n)));
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -4092,35 +4092,6 @@ exports[`runs examples > example "floor" example index 0 1`] = `
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "fm" example index 0 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/6 | note:c fmi:0 ]",
|
|
||||||
"[ 1/6 → 1/3 | note:e fmi:0 ]",
|
|
||||||
"[ 1/3 → 1/2 | note:g fmi:0 ]",
|
|
||||||
"[ 1/2 → 2/3 | note:b fmi:0 ]",
|
|
||||||
"[ 2/3 → 5/6 | note:g fmi:0 ]",
|
|
||||||
"[ 5/6 → 1/1 | note:e fmi:0 ]",
|
|
||||||
"[ 1/1 → 7/6 | note:c fmi:1 ]",
|
|
||||||
"[ 7/6 → 4/3 | note:e fmi:1 ]",
|
|
||||||
"[ 4/3 → 3/2 | note:g fmi:1 ]",
|
|
||||||
"[ 3/2 → 5/3 | note:b fmi:1 ]",
|
|
||||||
"[ 5/3 → 11/6 | note:g fmi:1 ]",
|
|
||||||
"[ 11/6 → 2/1 | note:e fmi:1 ]",
|
|
||||||
"[ 2/1 → 13/6 | note:c fmi:2 ]",
|
|
||||||
"[ 13/6 → 7/3 | note:e fmi:2 ]",
|
|
||||||
"[ 7/3 → 5/2 | note:g fmi:2 ]",
|
|
||||||
"[ 5/2 → 8/3 | note:b fmi:2 ]",
|
|
||||||
"[ 8/3 → 17/6 | note:g fmi:2 ]",
|
|
||||||
"[ 17/6 → 3/1 | note:e fmi:2 ]",
|
|
||||||
"[ 3/1 → 19/6 | note:c fmi:8 ]",
|
|
||||||
"[ 19/6 → 10/3 | note:e fmi:8 ]",
|
|
||||||
"[ 10/3 → 7/2 | note:g fmi:8 ]",
|
|
||||||
"[ 7/2 → 11/3 | note:b fmi:8 ]",
|
|
||||||
"[ 11/3 → 23/6 | note:g fmi:8 ]",
|
|
||||||
"[ 23/6 → 4/1 | note:e fmi:8 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "fmattack" example index 0 1`] = `
|
exports[`runs examples > example "fmattack" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/6 | note:c fmi:4 fmattack:0 ]",
|
"[ 0/1 → 1/6 | note:c fmi:4 fmattack:0 ]",
|
||||||
|
|
@ -4237,6 +4208,72 @@ exports[`runs examples > example "fmh" example index 0 1`] = `
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "fmi" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/6 | note:c fmi:0 ]",
|
||||||
|
"[ 1/6 → 1/3 | note:e fmi:0 ]",
|
||||||
|
"[ 1/3 → 1/2 | note:g fmi:0 ]",
|
||||||
|
"[ 1/2 → 2/3 | note:b fmi:0 ]",
|
||||||
|
"[ 2/3 → 5/6 | note:g fmi:0 ]",
|
||||||
|
"[ 5/6 → 1/1 | note:e fmi:0 ]",
|
||||||
|
"[ 1/1 → 7/6 | note:c fmi:1 ]",
|
||||||
|
"[ 7/6 → 4/3 | note:e fmi:1 ]",
|
||||||
|
"[ 4/3 → 3/2 | note:g fmi:1 ]",
|
||||||
|
"[ 3/2 → 5/3 | note:b fmi:1 ]",
|
||||||
|
"[ 5/3 → 11/6 | note:g fmi:1 ]",
|
||||||
|
"[ 11/6 → 2/1 | note:e fmi:1 ]",
|
||||||
|
"[ 2/1 → 13/6 | note:c fmi:2 ]",
|
||||||
|
"[ 13/6 → 7/3 | note:e fmi:2 ]",
|
||||||
|
"[ 7/3 → 5/2 | note:g fmi:2 ]",
|
||||||
|
"[ 5/2 → 8/3 | note:b fmi:2 ]",
|
||||||
|
"[ 8/3 → 17/6 | note:g fmi:2 ]",
|
||||||
|
"[ 17/6 → 3/1 | note:e fmi:2 ]",
|
||||||
|
"[ 3/1 → 19/6 | note:c fmi:8 ]",
|
||||||
|
"[ 19/6 → 10/3 | note:e fmi:8 ]",
|
||||||
|
"[ 10/3 → 7/2 | note:g fmi:8 ]",
|
||||||
|
"[ 7/2 → 11/3 | note:b fmi:8 ]",
|
||||||
|
"[ 11/3 → 23/6 | note:g fmi:8 ]",
|
||||||
|
"[ 23/6 → 4/1 | note:e fmi:8 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "fmi" example index 1 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/8 | s:sine note:F1 fmi:4 fmi2:0 fmi3:0 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 1/8 → 1/4 | s:sine note:F1 fmi:4 fmi2:2.7408622801303864 fmi3:0.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 1/4 → 3/8 | s:sine note:F1 fmi:4 fmi2:1.479038767516613 fmi3:0.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 3/8 → 1/2 | s:sine note:F1 fmi:4 fmi2:1.605570062994957 fmi3:0.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 1/2 → 5/8 | s:sine note:F1 fmi:4 fmi2:1.041922464966774 fmi3:0.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 5/8 → 3/4 | s:sine note:F1 fmi:4 fmi2:0.5425434708595276 fmi3:0.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 3/4 → 7/8 | s:sine note:F1 fmi:4 fmi2:0.7833059206604958 fmi3:0.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 7/8 → 1/1 | s:sine note:F1 fmi:4 fmi2:1.590524323284626 fmi3:0.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 1/1 → 9/8 | s:sine note:F1 fmi:4 fmi2:2.078168660402298 fmi3:1 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 9/8 → 5/4 | s:sine note:F1 fmi:4 fmi2:2.689958058297634 fmi3:1.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 5/4 → 11/8 | s:sine note:F1 fmi:4 fmi2:2.914912812411785 fmi3:1.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 11/8 → 3/2 | s:sine note:F1 fmi:4 fmi2:0.7312150076031685 fmi3:1.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 3/2 → 13/8 | s:sine note:F1 fmi:4 fmi2:2.4336322993040085 fmi3:1.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 13/8 → 7/4 | s:sine note:F1 fmi:4 fmi2:1.987018845975399 fmi3:1.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 7/4 → 15/8 | s:sine note:F1 fmi:4 fmi2:0.8189515843987465 fmi3:1.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 15/8 → 2/1 | s:sine note:F1 fmi:4 fmi2:2.3782287165522575 fmi3:1.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 2/1 → 17/8 | s:sine note:F1 fmi:4 fmi2:3.838108479976654 fmi3:2 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 17/8 → 9/4 | s:sine note:F1 fmi:4 fmi2:3.613561175763607 fmi3:2.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 9/4 → 19/8 | s:sine note:F1 fmi:4 fmi2:1.3819300457835197 fmi3:2.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 19/8 → 5/2 | s:sine note:F1 fmi:4 fmi2:3.346026562154293 fmi3:2.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 5/2 → 21/8 | s:sine note:F1 fmi:4 fmi2:1.8344642966985703 fmi3:2.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 21/8 → 11/4 | s:sine note:F1 fmi:4 fmi2:0.6407739371061325 fmi3:2.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 11/4 → 23/8 | s:sine note:F1 fmi:4 fmi2:2.5329310819506645 fmi3:2.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 23/8 → 3/1 | s:sine note:F1 fmi:4 fmi2:0.06501668691635132 fmi3:2.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 3/1 → 25/8 | s:sine note:F1 fmi:4 fmi2:0.8691564425826073 fmi3:3 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 25/8 → 13/4 | s:sine note:F1 fmi:4 fmi2:1.3729755133390427 fmi3:3.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 13/4 → 27/8 | s:sine note:F1 fmi:4 fmi2:3.9694602862000465 fmi3:3.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 27/8 → 7/2 | s:sine note:F1 fmi:4 fmi2:1.6347672045230865 fmi3:3.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 7/2 → 29/8 | s:sine note:F1 fmi:4 fmi2:1.639795258641243 fmi3:3.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 29/8 → 15/4 | s:sine note:F1 fmi:4 fmi2:3.7565943151712418 fmi3:3.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 15/4 → 31/8 | s:sine note:F1 fmi:4 fmi2:3.2486692890524864 fmi3:3.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 31/8 → 4/1 | s:sine note:F1 fmi:4 fmi2:2.9446661546826363 fmi3:3.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "fmsustain" example index 0 1`] = `
|
exports[`runs examples > example "fmsustain" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/6 | note:c fmi:4 fmdecay:0.1 fmsustain:1 ]",
|
"[ 0/1 → 1/6 | note:c fmi:4 fmdecay:0.1 fmsustain:1 ]",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue