mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-26 13:08:28 +00:00
commit
e4ea786956
@ -121,6 +121,7 @@ const generic_params = [
|
|||||||
* note("c e g b")
|
* note("c e g b")
|
||||||
* .fm(4)
|
* .fm(4)
|
||||||
* .fmh("<1 2 1.5 1.61>")
|
* .fmh("<1 2 1.5 1.61>")
|
||||||
|
* .scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
[['fmh', 'fmi'], 'fmh'],
|
[['fmh', 'fmi'], 'fmh'],
|
||||||
@ -134,6 +135,7 @@ const generic_params = [
|
|||||||
* @example
|
* @example
|
||||||
* note("c e g b")
|
* note("c e g b")
|
||||||
* .fm("<0 1 2 8 32>")
|
* .fm("<0 1 2 8 32>")
|
||||||
|
* .scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
[['fmi', 'fmh'], 'fm'],
|
[['fmi', 'fmh'], 'fm'],
|
||||||
@ -149,6 +151,7 @@ const generic_params = [
|
|||||||
* .fmdecay(.2)
|
* .fmdecay(.2)
|
||||||
* .fmsustain(0)
|
* .fmsustain(0)
|
||||||
* .fmenv("<exp lin>")
|
* .fmenv("<exp lin>")
|
||||||
|
* .scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
['fmenv'],
|
['fmenv'],
|
||||||
@ -161,6 +164,7 @@ const generic_params = [
|
|||||||
* note("c e g b")
|
* note("c e g b")
|
||||||
* .fm(4)
|
* .fm(4)
|
||||||
* .fmattack("<0 .05 .1 .2>")
|
* .fmattack("<0 .05 .1 .2>")
|
||||||
|
* .scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
['fmattack'],
|
['fmattack'],
|
||||||
@ -174,6 +178,7 @@ const generic_params = [
|
|||||||
* .fm(4)
|
* .fm(4)
|
||||||
* .fmdecay("<.01 .05 .1 .2>")
|
* .fmdecay("<.01 .05 .1 .2>")
|
||||||
* .fmsustain(.4)
|
* .fmsustain(.4)
|
||||||
|
* .scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
['fmdecay'],
|
['fmdecay'],
|
||||||
@ -187,6 +192,7 @@ const generic_params = [
|
|||||||
* .fm(4)
|
* .fm(4)
|
||||||
* .fmdecay(.1)
|
* .fmdecay(.1)
|
||||||
* .fmsustain("<1 .75 .5 0>")
|
* .fmsustain("<1 .75 .5 0>")
|
||||||
|
* .scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
['fmsustain'],
|
['fmsustain'],
|
||||||
@ -861,8 +867,22 @@ const generic_params = [
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
['clip'],
|
['clip'],
|
||||||
];
|
|
||||||
|
|
||||||
|
// ZZFX
|
||||||
|
['zrand'],
|
||||||
|
['curve'],
|
||||||
|
['slide'], // superdirt duplicate
|
||||||
|
['deltaSlide'],
|
||||||
|
['pitchJump'],
|
||||||
|
['pitchJumpTime'],
|
||||||
|
['lfo', 'repeatTime'],
|
||||||
|
['noise'],
|
||||||
|
['zmod'],
|
||||||
|
['zcrush'], // like crush but scaled differently
|
||||||
|
['zdelay'],
|
||||||
|
['tremolo'],
|
||||||
|
['zzfx'],
|
||||||
|
];
|
||||||
// TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13
|
// TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13
|
||||||
|
|
||||||
controls.createParam = function (names) {
|
controls.createParam = function (names) {
|
||||||
|
|||||||
@ -29,6 +29,9 @@ export const getDrawContext = (id = 'test-canvas') => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Pattern.prototype.draw = function (callback, { from, to, onQuery } = {}) {
|
Pattern.prototype.draw = function (callback, { from, to, onQuery } = {}) {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
if (window.strudelAnimation) {
|
if (window.strudelAnimation) {
|
||||||
cancelAnimationFrame(window.strudelAnimation);
|
cancelAnimationFrame(window.strudelAnimation);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -161,5 +161,6 @@ Then just make sure your first call of `superdough` happens after a click of som
|
|||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
- [ZZFX](https://github.com/KilledByAPixel/ZzFX) used for synths starting with z
|
||||||
- [SuperDirt](https://github.com/musikinformatik/SuperDirt)
|
- [SuperDirt](https://github.com/musikinformatik/SuperDirt)
|
||||||
- [WebDirt](https://github.com/dktr0/WebDirt)
|
- [WebDirt](https://github.com/dktr0/WebDirt)
|
||||||
|
|||||||
@ -8,4 +8,5 @@ export * from './superdough.mjs';
|
|||||||
export * from './sampler.mjs';
|
export * from './sampler.mjs';
|
||||||
export * from './helpers.mjs';
|
export * from './helpers.mjs';
|
||||||
export * from './synth.mjs';
|
export * from './synth.mjs';
|
||||||
|
export * from './zzfx.mjs';
|
||||||
export * from './logger.mjs';
|
export * from './logger.mjs';
|
||||||
|
|||||||
@ -167,6 +167,8 @@ export const superdough = async (value, deadline, hapDuration) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// duration is passed as value too..
|
||||||
|
value.duration = hapDuration;
|
||||||
// calculate absolute time
|
// calculate absolute time
|
||||||
let t = ac.currentTime + deadline;
|
let t = ac.currentTime + deadline;
|
||||||
// destructure
|
// destructure
|
||||||
|
|||||||
124
packages/superdough/zzfx.mjs
Normal file
124
packages/superdough/zzfx.mjs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
//import { ZZFX } from 'zzfx';
|
||||||
|
import { midiToFreq, noteToMidi } from './util.mjs';
|
||||||
|
import { registerSound, getAudioContext } from './superdough.mjs';
|
||||||
|
import { buildSamples } from './zzfx_fork.mjs';
|
||||||
|
|
||||||
|
export const getZZFX = (value, t) => {
|
||||||
|
let {
|
||||||
|
s,
|
||||||
|
note = 36,
|
||||||
|
freq,
|
||||||
|
//
|
||||||
|
zrand = 0,
|
||||||
|
attack = 0,
|
||||||
|
decay = 0,
|
||||||
|
sustain = 0.8,
|
||||||
|
release = 0.1,
|
||||||
|
curve = 1,
|
||||||
|
slide = 0,
|
||||||
|
deltaSlide = 0,
|
||||||
|
pitchJump = 0,
|
||||||
|
pitchJumpTime = 0,
|
||||||
|
lfo = 0,
|
||||||
|
noise = 0,
|
||||||
|
zmod = 0,
|
||||||
|
zcrush = 0,
|
||||||
|
zdelay = 0,
|
||||||
|
tremolo = 0,
|
||||||
|
duration = 0.2,
|
||||||
|
zzfx,
|
||||||
|
} = value;
|
||||||
|
const sustainTime = Math.max(duration - attack - decay, 0);
|
||||||
|
if (typeof note === 'string') {
|
||||||
|
note = noteToMidi(note); // e.g. c3 => 48
|
||||||
|
}
|
||||||
|
// get frequency
|
||||||
|
if (!freq && typeof note === 'number') {
|
||||||
|
freq = midiToFreq(note);
|
||||||
|
}
|
||||||
|
s = s.replace('z_', '');
|
||||||
|
const shape = ['sine', 'triangle', 'sawtooth', 'tan', 'noise'].indexOf(s) || 0;
|
||||||
|
curve = s === 'square' ? 0 : curve;
|
||||||
|
|
||||||
|
const params = zzfx || [
|
||||||
|
0.25, // volume
|
||||||
|
zrand,
|
||||||
|
freq,
|
||||||
|
attack,
|
||||||
|
sustainTime,
|
||||||
|
release,
|
||||||
|
shape,
|
||||||
|
curve,
|
||||||
|
slide,
|
||||||
|
deltaSlide,
|
||||||
|
pitchJump,
|
||||||
|
pitchJumpTime,
|
||||||
|
lfo,
|
||||||
|
noise,
|
||||||
|
zmod,
|
||||||
|
zcrush,
|
||||||
|
zdelay,
|
||||||
|
sustain, // sustain volume!
|
||||||
|
decay,
|
||||||
|
tremolo,
|
||||||
|
];
|
||||||
|
// console.log(redableZZFX(params));
|
||||||
|
|
||||||
|
const samples = /* ZZFX. */ buildSamples(...params);
|
||||||
|
const context = getAudioContext();
|
||||||
|
const buffer = context.createBuffer(1, samples.length, context.sampleRate);
|
||||||
|
buffer.getChannelData(0).set(samples);
|
||||||
|
const source = getAudioContext().createBufferSource();
|
||||||
|
source.buffer = buffer;
|
||||||
|
source.start(t);
|
||||||
|
return {
|
||||||
|
node: source,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function registerZZFXSounds() {
|
||||||
|
['zzfx', 'z_sine', 'z_sawtooth', 'z_triangle', 'z_square', 'z_tan', 'z_noise'].forEach((wave) => {
|
||||||
|
registerSound(
|
||||||
|
wave,
|
||||||
|
(t, value, onended) => {
|
||||||
|
const { node: o } = getZZFX({ s: wave, ...value }, t);
|
||||||
|
o.onended = () => {
|
||||||
|
o.disconnect();
|
||||||
|
onended();
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
node: o,
|
||||||
|
stop: () => {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{ type: 'synth', prebake: true },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// just for debugging
|
||||||
|
function redableZZFX(params) {
|
||||||
|
const paramOrder = [
|
||||||
|
'volume',
|
||||||
|
'zrand',
|
||||||
|
'frequency',
|
||||||
|
'attack',
|
||||||
|
'sustain',
|
||||||
|
'release',
|
||||||
|
'shape',
|
||||||
|
'curve',
|
||||||
|
'slide',
|
||||||
|
'deltaSlide',
|
||||||
|
'pitchJump',
|
||||||
|
'pitchJumpTime',
|
||||||
|
'lfo',
|
||||||
|
'noise',
|
||||||
|
'zmod',
|
||||||
|
'zcrush',
|
||||||
|
'zdelay',
|
||||||
|
'sustainVolume',
|
||||||
|
'decay',
|
||||||
|
'tremolo',
|
||||||
|
];
|
||||||
|
return Object.fromEntries(paramOrder.map((param, i) => [param, params[i]]));
|
||||||
|
}
|
||||||
120
packages/superdough/zzfx_fork.mjs
Normal file
120
packages/superdough/zzfx_fork.mjs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { getAudioContext } from './superdough.mjs';
|
||||||
|
|
||||||
|
// https://github.com/KilledByAPixel/ZzFX/blob/master/ZzFX.js#L85C5-L180C6
|
||||||
|
// changes: replaced this.volume with 1 + using sampleRate from getAudioContext()
|
||||||
|
export function buildSamples(
|
||||||
|
volume = 1,
|
||||||
|
randomness = 0.05,
|
||||||
|
frequency = 220,
|
||||||
|
attack = 0,
|
||||||
|
sustain = 0,
|
||||||
|
release = 0.1,
|
||||||
|
shape = 0,
|
||||||
|
shapeCurve = 1,
|
||||||
|
slide = 0,
|
||||||
|
deltaSlide = 0,
|
||||||
|
pitchJump = 0,
|
||||||
|
pitchJumpTime = 0,
|
||||||
|
repeatTime = 0,
|
||||||
|
noise = 0,
|
||||||
|
modulation = 0,
|
||||||
|
bitCrush = 0,
|
||||||
|
delay = 0,
|
||||||
|
sustainVolume = 1,
|
||||||
|
decay = 0,
|
||||||
|
tremolo = 0,
|
||||||
|
) {
|
||||||
|
// init parameters
|
||||||
|
let PI2 = Math.PI * 2,
|
||||||
|
sampleRate = getAudioContext().sampleRate,
|
||||||
|
sign = (v) => (v > 0 ? 1 : -1),
|
||||||
|
startSlide = (slide *= (500 * PI2) / sampleRate / sampleRate),
|
||||||
|
startFrequency = (frequency *= ((1 + randomness * 2 * Math.random() - randomness) * PI2) / sampleRate),
|
||||||
|
b = [],
|
||||||
|
t = 0,
|
||||||
|
tm = 0,
|
||||||
|
i = 0,
|
||||||
|
j = 1,
|
||||||
|
r = 0,
|
||||||
|
c = 0,
|
||||||
|
s = 0,
|
||||||
|
f,
|
||||||
|
length;
|
||||||
|
|
||||||
|
// scale by sample rate
|
||||||
|
attack = attack * sampleRate + 9; // minimum attack to prevent pop
|
||||||
|
decay *= sampleRate;
|
||||||
|
sustain *= sampleRate;
|
||||||
|
release *= sampleRate;
|
||||||
|
delay *= sampleRate;
|
||||||
|
deltaSlide *= (500 * PI2) / sampleRate ** 3;
|
||||||
|
modulation *= PI2 / sampleRate;
|
||||||
|
pitchJump *= PI2 / sampleRate;
|
||||||
|
pitchJumpTime *= sampleRate;
|
||||||
|
repeatTime = (repeatTime * sampleRate) | 0;
|
||||||
|
|
||||||
|
// generate waveform
|
||||||
|
for (length = (attack + decay + sustain + release + delay) | 0; i < length; b[i++] = s) {
|
||||||
|
if (!(++c % ((bitCrush * 100) | 0))) {
|
||||||
|
// bit crush
|
||||||
|
s = shape
|
||||||
|
? shape > 1
|
||||||
|
? shape > 2
|
||||||
|
? shape > 3 // wave shape
|
||||||
|
? Math.sin((t % PI2) ** 3) // 4 noise
|
||||||
|
: Math.max(Math.min(Math.tan(t), 1), -1) // 3 tan
|
||||||
|
: 1 - (((((2 * t) / PI2) % 2) + 2) % 2) // 2 saw
|
||||||
|
: 1 - 4 * Math.abs(Math.round(t / PI2) - t / PI2) // 1 triangle
|
||||||
|
: Math.sin(t); // 0 sin
|
||||||
|
|
||||||
|
s =
|
||||||
|
(repeatTime
|
||||||
|
? 1 - tremolo + tremolo * Math.sin((PI2 * i) / repeatTime) // tremolo
|
||||||
|
: 1) *
|
||||||
|
sign(s) *
|
||||||
|
Math.abs(s) ** shapeCurve * // curve 0=square, 2=pointy
|
||||||
|
volume *
|
||||||
|
1 * // envelope
|
||||||
|
(i < attack
|
||||||
|
? i / attack // attack
|
||||||
|
: i < attack + decay // decay
|
||||||
|
? 1 - ((i - attack) / decay) * (1 - sustainVolume) // decay falloff
|
||||||
|
: i < attack + decay + sustain // sustain
|
||||||
|
? sustainVolume // sustain volume
|
||||||
|
: i < length - delay // release
|
||||||
|
? ((length - i - delay) / release) * // release falloff
|
||||||
|
sustainVolume // release volume
|
||||||
|
: 0); // post release
|
||||||
|
|
||||||
|
s = delay
|
||||||
|
? s / 2 +
|
||||||
|
(delay > i
|
||||||
|
? 0 // delay
|
||||||
|
: ((i < length - delay ? 1 : (length - i) / delay) * // release delay
|
||||||
|
b[(i - delay) | 0]) /
|
||||||
|
2)
|
||||||
|
: s; // sample delay
|
||||||
|
}
|
||||||
|
|
||||||
|
f =
|
||||||
|
(frequency += slide += deltaSlide) * // frequency
|
||||||
|
Math.cos(modulation * tm++); // modulation
|
||||||
|
t += f - f * noise * (1 - (((Math.sin(i) + 1) * 1e9) % 2)); // noise
|
||||||
|
|
||||||
|
if (j && ++j > pitchJumpTime) {
|
||||||
|
// pitch jump
|
||||||
|
frequency += pitchJump; // apply pitch jump
|
||||||
|
startFrequency += pitchJump; // also apply to start
|
||||||
|
j = 0; // stop pitch jump time
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repeatTime && !(++r % repeatTime)) {
|
||||||
|
// repeat
|
||||||
|
frequency = startFrequency; // reset frequency
|
||||||
|
slide = startSlide; // reset slide
|
||||||
|
j ||= 1; // reset pitch jump time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b;
|
||||||
|
}
|
||||||
@ -1806,127 +1806,127 @@ exports[`runs examples > example "floor" example index 0 1`] = `
|
|||||||
|
|
||||||
exports[`runs examples > example "fm" example index 0 1`] = `
|
exports[`runs examples > example "fm" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c fmi:0 ]",
|
"[ 0/1 → 1/4 | note:c fmi:0 analyze:1 ]",
|
||||||
"[ 1/4 → 1/2 | note:e fmi:0 ]",
|
"[ 1/4 → 1/2 | note:e fmi:0 analyze:1 ]",
|
||||||
"[ 1/2 → 3/4 | note:g fmi:0 ]",
|
"[ 1/2 → 3/4 | note:g fmi:0 analyze:1 ]",
|
||||||
"[ 3/4 → 1/1 | note:b fmi:0 ]",
|
"[ 3/4 → 1/1 | note:b fmi:0 analyze:1 ]",
|
||||||
"[ 1/1 → 5/4 | note:c fmi:1 ]",
|
"[ 1/1 → 5/4 | note:c fmi:1 analyze:1 ]",
|
||||||
"[ 5/4 → 3/2 | note:e fmi:1 ]",
|
"[ 5/4 → 3/2 | note:e fmi:1 analyze:1 ]",
|
||||||
"[ 3/2 → 7/4 | note:g fmi:1 ]",
|
"[ 3/2 → 7/4 | note:g fmi:1 analyze:1 ]",
|
||||||
"[ 7/4 → 2/1 | note:b fmi:1 ]",
|
"[ 7/4 → 2/1 | note:b fmi:1 analyze:1 ]",
|
||||||
"[ 2/1 → 9/4 | note:c fmi:2 ]",
|
"[ 2/1 → 9/4 | note:c fmi:2 analyze:1 ]",
|
||||||
"[ 9/4 → 5/2 | note:e fmi:2 ]",
|
"[ 9/4 → 5/2 | note:e fmi:2 analyze:1 ]",
|
||||||
"[ 5/2 → 11/4 | note:g fmi:2 ]",
|
"[ 5/2 → 11/4 | note:g fmi:2 analyze:1 ]",
|
||||||
"[ 11/4 → 3/1 | note:b fmi:2 ]",
|
"[ 11/4 → 3/1 | note:b fmi:2 analyze:1 ]",
|
||||||
"[ 3/1 → 13/4 | note:c fmi:8 ]",
|
"[ 3/1 → 13/4 | note:c fmi:8 analyze:1 ]",
|
||||||
"[ 13/4 → 7/2 | note:e fmi:8 ]",
|
"[ 13/4 → 7/2 | note:e fmi:8 analyze:1 ]",
|
||||||
"[ 7/2 → 15/4 | note:g fmi:8 ]",
|
"[ 7/2 → 15/4 | note:g fmi:8 analyze:1 ]",
|
||||||
"[ 15/4 → 4/1 | note:b fmi:8 ]",
|
"[ 15/4 → 4/1 | note:b fmi:8 analyze:1 ]",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "fmattack" example index 0 1`] = `
|
exports[`runs examples > example "fmattack" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c fmi:4 fmattack:0 ]",
|
"[ 0/1 → 1/4 | note:c fmi:4 fmattack:0 analyze:1 ]",
|
||||||
"[ 1/4 → 1/2 | note:e fmi:4 fmattack:0 ]",
|
"[ 1/4 → 1/2 | note:e fmi:4 fmattack:0 analyze:1 ]",
|
||||||
"[ 1/2 → 3/4 | note:g fmi:4 fmattack:0 ]",
|
"[ 1/2 → 3/4 | note:g fmi:4 fmattack:0 analyze:1 ]",
|
||||||
"[ 3/4 → 1/1 | note:b fmi:4 fmattack:0 ]",
|
"[ 3/4 → 1/1 | note:b fmi:4 fmattack:0 analyze:1 ]",
|
||||||
"[ 1/1 → 5/4 | note:c fmi:4 fmattack:0.05 ]",
|
"[ 1/1 → 5/4 | note:c fmi:4 fmattack:0.05 analyze:1 ]",
|
||||||
"[ 5/4 → 3/2 | note:e fmi:4 fmattack:0.05 ]",
|
"[ 5/4 → 3/2 | note:e fmi:4 fmattack:0.05 analyze:1 ]",
|
||||||
"[ 3/2 → 7/4 | note:g fmi:4 fmattack:0.05 ]",
|
"[ 3/2 → 7/4 | note:g fmi:4 fmattack:0.05 analyze:1 ]",
|
||||||
"[ 7/4 → 2/1 | note:b fmi:4 fmattack:0.05 ]",
|
"[ 7/4 → 2/1 | note:b fmi:4 fmattack:0.05 analyze:1 ]",
|
||||||
"[ 2/1 → 9/4 | note:c fmi:4 fmattack:0.1 ]",
|
"[ 2/1 → 9/4 | note:c fmi:4 fmattack:0.1 analyze:1 ]",
|
||||||
"[ 9/4 → 5/2 | note:e fmi:4 fmattack:0.1 ]",
|
"[ 9/4 → 5/2 | note:e fmi:4 fmattack:0.1 analyze:1 ]",
|
||||||
"[ 5/2 → 11/4 | note:g fmi:4 fmattack:0.1 ]",
|
"[ 5/2 → 11/4 | note:g fmi:4 fmattack:0.1 analyze:1 ]",
|
||||||
"[ 11/4 → 3/1 | note:b fmi:4 fmattack:0.1 ]",
|
"[ 11/4 → 3/1 | note:b fmi:4 fmattack:0.1 analyze:1 ]",
|
||||||
"[ 3/1 → 13/4 | note:c fmi:4 fmattack:0.2 ]",
|
"[ 3/1 → 13/4 | note:c fmi:4 fmattack:0.2 analyze:1 ]",
|
||||||
"[ 13/4 → 7/2 | note:e fmi:4 fmattack:0.2 ]",
|
"[ 13/4 → 7/2 | note:e fmi:4 fmattack:0.2 analyze:1 ]",
|
||||||
"[ 7/2 → 15/4 | note:g fmi:4 fmattack:0.2 ]",
|
"[ 7/2 → 15/4 | note:g fmi:4 fmattack:0.2 analyze:1 ]",
|
||||||
"[ 15/4 → 4/1 | note:b fmi:4 fmattack:0.2 ]",
|
"[ 15/4 → 4/1 | note:b fmi:4 fmattack:0.2 analyze:1 ]",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "fmdecay" example index 0 1`] = `
|
exports[`runs examples > example "fmdecay" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c fmi:4 fmdecay:0.01 fmsustain:0.4 ]",
|
"[ 0/1 → 1/4 | note:c fmi:4 fmdecay:0.01 fmsustain:0.4 analyze:1 ]",
|
||||||
"[ 1/4 → 1/2 | note:e fmi:4 fmdecay:0.01 fmsustain:0.4 ]",
|
"[ 1/4 → 1/2 | note:e fmi:4 fmdecay:0.01 fmsustain:0.4 analyze:1 ]",
|
||||||
"[ 1/2 → 3/4 | note:g fmi:4 fmdecay:0.01 fmsustain:0.4 ]",
|
"[ 1/2 → 3/4 | note:g fmi:4 fmdecay:0.01 fmsustain:0.4 analyze:1 ]",
|
||||||
"[ 3/4 → 1/1 | note:b fmi:4 fmdecay:0.01 fmsustain:0.4 ]",
|
"[ 3/4 → 1/1 | note:b fmi:4 fmdecay:0.01 fmsustain:0.4 analyze:1 ]",
|
||||||
"[ 1/1 → 5/4 | note:c fmi:4 fmdecay:0.05 fmsustain:0.4 ]",
|
"[ 1/1 → 5/4 | note:c fmi:4 fmdecay:0.05 fmsustain:0.4 analyze:1 ]",
|
||||||
"[ 5/4 → 3/2 | note:e fmi:4 fmdecay:0.05 fmsustain:0.4 ]",
|
"[ 5/4 → 3/2 | note:e fmi:4 fmdecay:0.05 fmsustain:0.4 analyze:1 ]",
|
||||||
"[ 3/2 → 7/4 | note:g fmi:4 fmdecay:0.05 fmsustain:0.4 ]",
|
"[ 3/2 → 7/4 | note:g fmi:4 fmdecay:0.05 fmsustain:0.4 analyze:1 ]",
|
||||||
"[ 7/4 → 2/1 | note:b fmi:4 fmdecay:0.05 fmsustain:0.4 ]",
|
"[ 7/4 → 2/1 | note:b fmi:4 fmdecay:0.05 fmsustain:0.4 analyze:1 ]",
|
||||||
"[ 2/1 → 9/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0.4 ]",
|
"[ 2/1 → 9/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0.4 analyze:1 ]",
|
||||||
"[ 9/4 → 5/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0.4 ]",
|
"[ 9/4 → 5/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0.4 analyze:1 ]",
|
||||||
"[ 5/2 → 11/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0.4 ]",
|
"[ 5/2 → 11/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0.4 analyze:1 ]",
|
||||||
"[ 11/4 → 3/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0.4 ]",
|
"[ 11/4 → 3/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0.4 analyze:1 ]",
|
||||||
"[ 3/1 → 13/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0.4 ]",
|
"[ 3/1 → 13/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0.4 analyze:1 ]",
|
||||||
"[ 13/4 → 7/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0.4 ]",
|
"[ 13/4 → 7/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0.4 analyze:1 ]",
|
||||||
"[ 7/2 → 15/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0.4 ]",
|
"[ 7/2 → 15/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0.4 analyze:1 ]",
|
||||||
"[ 15/4 → 4/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0.4 ]",
|
"[ 15/4 → 4/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0.4 analyze:1 ]",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "fmenv" example index 0 1`] = `
|
exports[`runs examples > example "fmenv" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
"[ 0/1 → 1/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
|
||||||
"[ 1/4 → 1/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
"[ 1/4 → 1/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
|
||||||
"[ 1/2 → 3/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
"[ 1/2 → 3/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
|
||||||
"[ 3/4 → 1/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
"[ 3/4 → 1/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
|
||||||
"[ 1/1 → 5/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
"[ 1/1 → 5/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
|
||||||
"[ 5/4 → 3/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
"[ 5/4 → 3/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
|
||||||
"[ 3/2 → 7/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
"[ 3/2 → 7/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
|
||||||
"[ 7/4 → 2/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
"[ 7/4 → 2/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
|
||||||
"[ 2/1 → 9/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
"[ 2/1 → 9/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
|
||||||
"[ 9/4 → 5/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
"[ 9/4 → 5/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
|
||||||
"[ 5/2 → 11/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
"[ 5/2 → 11/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
|
||||||
"[ 11/4 → 3/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
|
"[ 11/4 → 3/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
|
||||||
"[ 3/1 → 13/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
"[ 3/1 → 13/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
|
||||||
"[ 13/4 → 7/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
"[ 13/4 → 7/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
|
||||||
"[ 7/2 → 15/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
"[ 7/2 → 15/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
|
||||||
"[ 15/4 → 4/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
|
"[ 15/4 → 4/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
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 analyze:1 ]",
|
||||||
"[ 1/4 → 1/2 | note:e fmi:4 fmh:1 ]",
|
"[ 1/4 → 1/2 | note:e fmi:4 fmh:1 analyze:1 ]",
|
||||||
"[ 1/2 → 3/4 | note:g fmi:4 fmh:1 ]",
|
"[ 1/2 → 3/4 | note:g fmi:4 fmh:1 analyze:1 ]",
|
||||||
"[ 3/4 → 1/1 | note:b fmi:4 fmh:1 ]",
|
"[ 3/4 → 1/1 | note:b fmi:4 fmh:1 analyze:1 ]",
|
||||||
"[ 1/1 → 5/4 | note:c fmi:4 fmh:2 ]",
|
"[ 1/1 → 5/4 | note:c fmi:4 fmh:2 analyze:1 ]",
|
||||||
"[ 5/4 → 3/2 | note:e fmi:4 fmh:2 ]",
|
"[ 5/4 → 3/2 | note:e fmi:4 fmh:2 analyze:1 ]",
|
||||||
"[ 3/2 → 7/4 | note:g fmi:4 fmh:2 ]",
|
"[ 3/2 → 7/4 | note:g fmi:4 fmh:2 analyze:1 ]",
|
||||||
"[ 7/4 → 2/1 | note:b fmi:4 fmh:2 ]",
|
"[ 7/4 → 2/1 | note:b fmi:4 fmh:2 analyze:1 ]",
|
||||||
"[ 2/1 → 9/4 | note:c fmi:4 fmh:1.5 ]",
|
"[ 2/1 → 9/4 | note:c fmi:4 fmh:1.5 analyze:1 ]",
|
||||||
"[ 9/4 → 5/2 | note:e fmi:4 fmh:1.5 ]",
|
"[ 9/4 → 5/2 | note:e fmi:4 fmh:1.5 analyze:1 ]",
|
||||||
"[ 5/2 → 11/4 | note:g fmi:4 fmh:1.5 ]",
|
"[ 5/2 → 11/4 | note:g fmi:4 fmh:1.5 analyze:1 ]",
|
||||||
"[ 11/4 → 3/1 | note:b fmi:4 fmh:1.5 ]",
|
"[ 11/4 → 3/1 | note:b fmi:4 fmh:1.5 analyze:1 ]",
|
||||||
"[ 3/1 → 13/4 | note:c fmi:4 fmh:1.61 ]",
|
"[ 3/1 → 13/4 | note:c fmi:4 fmh:1.61 analyze:1 ]",
|
||||||
"[ 13/4 → 7/2 | note:e fmi:4 fmh:1.61 ]",
|
"[ 13/4 → 7/2 | note:e fmi:4 fmh:1.61 analyze:1 ]",
|
||||||
"[ 7/2 → 15/4 | note:g fmi:4 fmh:1.61 ]",
|
"[ 7/2 → 15/4 | note:g fmi:4 fmh:1.61 analyze:1 ]",
|
||||||
"[ 15/4 → 4/1 | note:b fmi:4 fmh:1.61 ]",
|
"[ 15/4 → 4/1 | note:b fmi:4 fmh:1.61 analyze:1 ]",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "fmsustain" 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 ]",
|
"[ 0/1 → 1/4 | note:c fmi:4 fmdecay:0.1 fmsustain:1 analyze:1 ]",
|
||||||
"[ 1/4 → 1/2 | note:e fmi:4 fmdecay:0.1 fmsustain:1 ]",
|
"[ 1/4 → 1/2 | note:e fmi:4 fmdecay:0.1 fmsustain:1 analyze:1 ]",
|
||||||
"[ 1/2 → 3/4 | note:g fmi:4 fmdecay:0.1 fmsustain:1 ]",
|
"[ 1/2 → 3/4 | note:g fmi:4 fmdecay:0.1 fmsustain:1 analyze:1 ]",
|
||||||
"[ 3/4 → 1/1 | note:b fmi:4 fmdecay:0.1 fmsustain:1 ]",
|
"[ 3/4 → 1/1 | note:b fmi:4 fmdecay:0.1 fmsustain:1 analyze:1 ]",
|
||||||
"[ 1/1 → 5/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0.75 ]",
|
"[ 1/1 → 5/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0.75 analyze:1 ]",
|
||||||
"[ 5/4 → 3/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0.75 ]",
|
"[ 5/4 → 3/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0.75 analyze:1 ]",
|
||||||
"[ 3/2 → 7/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0.75 ]",
|
"[ 3/2 → 7/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0.75 analyze:1 ]",
|
||||||
"[ 7/4 → 2/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0.75 ]",
|
"[ 7/4 → 2/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0.75 analyze:1 ]",
|
||||||
"[ 2/1 → 9/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0.5 ]",
|
"[ 2/1 → 9/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0.5 analyze:1 ]",
|
||||||
"[ 9/4 → 5/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0.5 ]",
|
"[ 9/4 → 5/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0.5 analyze:1 ]",
|
||||||
"[ 5/2 → 11/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0.5 ]",
|
"[ 5/2 → 11/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0.5 analyze:1 ]",
|
||||||
"[ 11/4 → 3/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0.5 ]",
|
"[ 11/4 → 3/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0.5 analyze:1 ]",
|
||||||
"[ 3/1 → 13/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0 ]",
|
"[ 3/1 → 13/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0 analyze:1 ]",
|
||||||
"[ 13/4 → 7/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0 ]",
|
"[ 13/4 → 7/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0 analyze:1 ]",
|
||||||
"[ 7/2 → 15/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0 ]",
|
"[ 7/2 → 15/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0 analyze:1 ]",
|
||||||
"[ 15/4 → 4/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0 ]",
|
"[ 15/4 → 4/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0 analyze:1 ]",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@ -8,28 +8,52 @@ import { JsDoc } from '../../docs/JsDoc';
|
|||||||
|
|
||||||
# Synths
|
# Synths
|
||||||
|
|
||||||
For now, [samples](/learn/samples) are the main way to play with Strudel.
|
In addition to the sampling engine, strudel comes with a synthesizer to create sounds on the fly.
|
||||||
In the future, more powerful synthesis capabilities will be added.
|
|
||||||
If in the meantime you want to dive deeper into audio synthesis with Tidal, you will need to [install SuperCollider and SuperDirt](https://tidalcycles.org/docs/).
|
|
||||||
|
|
||||||
## Playing synths with `s`
|
## Basic Waveforms
|
||||||
|
|
||||||
We can change the sound, using the `s` function:
|
The basic waveforms are `sine`, `sawtooth`, `square` and `triangle`, which can be selected via `sound` (or `s`):
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("c2 <eb2 <g2 g1>>").s('sawtooth')`} />
|
<MiniRepl
|
||||||
|
client:idle
|
||||||
|
tune={`note("c2 <eb2 <g2 g1>>")
|
||||||
|
.sound("<sawtooth square triangle sine>")
|
||||||
|
.scope()`}
|
||||||
|
/>
|
||||||
|
|
||||||
Here, we are wrapping our notes inside `note` and set the sound using `s`, connected by a dot.
|
If you don't set a `sound` but a `note` the default value for `sound` is `triangle`!
|
||||||
|
|
||||||
Those functions are only 2 of many ways to alter the properties, or _params_ of a sound.
|
### Additive Synthesis
|
||||||
The power of patterns allows us to sequence any _param_ independently:
|
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("c2 <eb2 <g2 g1>>").s("<sawtooth square triangle>")`} />
|
To tame the harsh sound of the basic waveforms, we can set the `n` control to limit the overtones of the waveform:
|
||||||
|
|
||||||
Now we not only pattern the notes, but the sound as well!
|
<MiniRepl
|
||||||
`sawtooth` `square` and `triangle` are the basic waveforms available in `s`.
|
client:idle
|
||||||
|
tune={`note("c2 <eb2 <g2 g1>>")
|
||||||
|
.sound("sawtooth")
|
||||||
|
.n("<32 16 8 4>")
|
||||||
|
.scope()`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
When the `n` control is used on a basic waveform, it defines the number of harmonic partials the sound is getting.
|
||||||
|
You can also set `n` directly in mini notation with `sound`:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:idle
|
||||||
|
tune={`note("c2 <eb2 <g2 g1>>")
|
||||||
|
.sound("sawtooth:<32 16 8 4>")
|
||||||
|
.scope()`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
Note for tidal users: `n` in tidal is synonymous to `note` for synths only.
|
||||||
|
In strudel, this is not the case, where `n` will always change timbre, be it though different samples or different waveforms.
|
||||||
|
|
||||||
## FM Synthesis
|
## FM Synthesis
|
||||||
|
|
||||||
|
FM Synthesis is a technique that changes the frequency of a basic waveform rapidly to alter the timbre.
|
||||||
|
|
||||||
|
You can use fm with any of the above waveforms, although the below examples all use the default triangle wave.
|
||||||
|
|
||||||
### fm
|
### fm
|
||||||
|
|
||||||
<JsDoc client:idle name="fm" h={0} />
|
<JsDoc client:idle name="fm" h={0} />
|
||||||
@ -54,4 +78,41 @@ Now we not only pattern the notes, but the sound as well!
|
|||||||
|
|
||||||
<JsDoc client:idle name="fmenv" h={0} />
|
<JsDoc client:idle name="fmenv" h={0} />
|
||||||
|
|
||||||
|
## ZZFX
|
||||||
|
|
||||||
|
The "Zuper Zmall Zound Zynth" [ZZFX](https://github.com/KilledByAPixel/ZzFX) is also integrated in strudel.
|
||||||
|
Developed by [Frank Force](https://frankforce.com/), it is a synth and FX engine originally intended to be used for size coding games.
|
||||||
|
|
||||||
|
It has 20 parameters in total, here is a snippet that uses all:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:idle
|
||||||
|
tune={`note("<c2 eb2 f2 g2>") // also supports freq
|
||||||
|
.s("<z_sawtooth z_tan z_noise z_sine z_square>")
|
||||||
|
.zrand(0) // randomization
|
||||||
|
// zzfx envelope
|
||||||
|
.attack(0.001)
|
||||||
|
.decay(0.1)
|
||||||
|
.sustain(.8)
|
||||||
|
.release(.1)
|
||||||
|
// special zzfx params
|
||||||
|
.curve(1) // waveshape 1-3
|
||||||
|
.slide(0) // +/- pitch slide
|
||||||
|
.deltaSlide(0) // +/- pitch slide (?)
|
||||||
|
.noise(0) // make it dirty
|
||||||
|
.zmod(0) // fm speed
|
||||||
|
.zcrush(0) // bit crush 0 - 1
|
||||||
|
.zdelay(0) // simple delay
|
||||||
|
.pitchJump(0) // +/- pitch change after pitchJumpTime
|
||||||
|
.pitchJumpTime(0) // >0 time after pitchJump is applied
|
||||||
|
.lfo(0) // >0 resets slide + pitchJump + sets tremolo speed
|
||||||
|
.tremolo(0) // 0-1 lfo volume modulation amount
|
||||||
|
//.duration(.2) // overwrite strudel event duration
|
||||||
|
//.gain(1) // change volume
|
||||||
|
.scope() // vizualise waveform (not zzfx related)
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
Note that you can also combine zzfx with all the other audio fx (next chapter).
|
||||||
|
|
||||||
Next up: [Audio Effects](/learn/effects)...
|
Next up: [Audio Effects](/learn/effects)...
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Pattern, noteToMidi, valueToMidi } from '@strudel.cycles/core';
|
import { Pattern, noteToMidi, valueToMidi } from '@strudel.cycles/core';
|
||||||
import { registerSynthSounds, samples } from '@strudel.cycles/webaudio';
|
import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio';
|
||||||
import './piano.mjs';
|
import './piano.mjs';
|
||||||
import './files.mjs';
|
import './files.mjs';
|
||||||
|
|
||||||
@ -8,6 +8,7 @@ export async function prebake() {
|
|||||||
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
|
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
registerSynthSounds(),
|
registerSynthSounds(),
|
||||||
|
registerZZFXSounds(),
|
||||||
//registerSoundfonts(),
|
//registerSoundfonts(),
|
||||||
// need dynamic import here, because importing @strudel.cycles/soundfonts fails on server:
|
// need dynamic import here, because importing @strudel.cycles/soundfonts fails on server:
|
||||||
// => getting "window is not defined", as soon as "@strudel.cycles/soundfonts" is imported statically
|
// => getting "window is not defined", as soon as "@strudel.cycles/soundfonts" is imported statically
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user