mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-11 21:58:31 +00:00
commit
e4ea786956
@ -121,6 +121,7 @@ const generic_params = [
|
||||
* note("c e g b")
|
||||
* .fm(4)
|
||||
* .fmh("<1 2 1.5 1.61>")
|
||||
* .scope()
|
||||
*
|
||||
*/
|
||||
[['fmh', 'fmi'], 'fmh'],
|
||||
@ -134,6 +135,7 @@ const generic_params = [
|
||||
* @example
|
||||
* note("c e g b")
|
||||
* .fm("<0 1 2 8 32>")
|
||||
* .scope()
|
||||
*
|
||||
*/
|
||||
[['fmi', 'fmh'], 'fm'],
|
||||
@ -149,6 +151,7 @@ const generic_params = [
|
||||
* .fmdecay(.2)
|
||||
* .fmsustain(0)
|
||||
* .fmenv("<exp lin>")
|
||||
* .scope()
|
||||
*
|
||||
*/
|
||||
['fmenv'],
|
||||
@ -161,6 +164,7 @@ const generic_params = [
|
||||
* note("c e g b")
|
||||
* .fm(4)
|
||||
* .fmattack("<0 .05 .1 .2>")
|
||||
* .scope()
|
||||
*
|
||||
*/
|
||||
['fmattack'],
|
||||
@ -174,6 +178,7 @@ const generic_params = [
|
||||
* .fm(4)
|
||||
* .fmdecay("<.01 .05 .1 .2>")
|
||||
* .fmsustain(.4)
|
||||
* .scope()
|
||||
*
|
||||
*/
|
||||
['fmdecay'],
|
||||
@ -187,6 +192,7 @@ const generic_params = [
|
||||
* .fm(4)
|
||||
* .fmdecay(.1)
|
||||
* .fmsustain("<1 .75 .5 0>")
|
||||
* .scope()
|
||||
*
|
||||
*/
|
||||
['fmsustain'],
|
||||
@ -861,8 +867,22 @@ const generic_params = [
|
||||
*
|
||||
*/
|
||||
['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
|
||||
|
||||
controls.createParam = function (names) {
|
||||
|
||||
@ -29,6 +29,9 @@ export const getDrawContext = (id = 'test-canvas') => {
|
||||
};
|
||||
|
||||
Pattern.prototype.draw = function (callback, { from, to, onQuery } = {}) {
|
||||
if (typeof window === 'undefined') {
|
||||
return this;
|
||||
}
|
||||
if (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
|
||||
|
||||
- [ZZFX](https://github.com/KilledByAPixel/ZzFX) used for synths starting with z
|
||||
- [SuperDirt](https://github.com/musikinformatik/SuperDirt)
|
||||
- [WebDirt](https://github.com/dktr0/WebDirt)
|
||||
|
||||
@ -8,4 +8,5 @@ export * from './superdough.mjs';
|
||||
export * from './sampler.mjs';
|
||||
export * from './helpers.mjs';
|
||||
export * from './synth.mjs';
|
||||
export * from './zzfx.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
|
||||
let t = ac.currentTime + deadline;
|
||||
// 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`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c fmi:0 ]",
|
||||
"[ 1/4 → 1/2 | note:e fmi:0 ]",
|
||||
"[ 1/2 → 3/4 | note:g fmi:0 ]",
|
||||
"[ 3/4 → 1/1 | note:b fmi:0 ]",
|
||||
"[ 1/1 → 5/4 | note:c fmi:1 ]",
|
||||
"[ 5/4 → 3/2 | note:e fmi:1 ]",
|
||||
"[ 3/2 → 7/4 | note:g fmi:1 ]",
|
||||
"[ 7/4 → 2/1 | note:b fmi:1 ]",
|
||||
"[ 2/1 → 9/4 | note:c fmi:2 ]",
|
||||
"[ 9/4 → 5/2 | note:e fmi:2 ]",
|
||||
"[ 5/2 → 11/4 | note:g fmi:2 ]",
|
||||
"[ 11/4 → 3/1 | note:b fmi:2 ]",
|
||||
"[ 3/1 → 13/4 | note:c fmi:8 ]",
|
||||
"[ 13/4 → 7/2 | note:e fmi:8 ]",
|
||||
"[ 7/2 → 15/4 | note:g fmi:8 ]",
|
||||
"[ 15/4 → 4/1 | note:b fmi:8 ]",
|
||||
"[ 0/1 → 1/4 | note:c fmi:0 analyze:1 ]",
|
||||
"[ 1/4 → 1/2 | note:e fmi:0 analyze:1 ]",
|
||||
"[ 1/2 → 3/4 | note:g fmi:0 analyze:1 ]",
|
||||
"[ 3/4 → 1/1 | note:b fmi:0 analyze:1 ]",
|
||||
"[ 1/1 → 5/4 | note:c fmi:1 analyze:1 ]",
|
||||
"[ 5/4 → 3/2 | note:e fmi:1 analyze:1 ]",
|
||||
"[ 3/2 → 7/4 | note:g fmi:1 analyze:1 ]",
|
||||
"[ 7/4 → 2/1 | note:b fmi:1 analyze:1 ]",
|
||||
"[ 2/1 → 9/4 | note:c fmi:2 analyze:1 ]",
|
||||
"[ 9/4 → 5/2 | note:e fmi:2 analyze:1 ]",
|
||||
"[ 5/2 → 11/4 | note:g fmi:2 analyze:1 ]",
|
||||
"[ 11/4 → 3/1 | note:b fmi:2 analyze:1 ]",
|
||||
"[ 3/1 → 13/4 | note:c fmi:8 analyze:1 ]",
|
||||
"[ 13/4 → 7/2 | note:e fmi:8 analyze:1 ]",
|
||||
"[ 7/2 → 15/4 | note:g fmi:8 analyze:1 ]",
|
||||
"[ 15/4 → 4/1 | note:b fmi:8 analyze: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 ]",
|
||||
"[ 0/1 → 1/4 | note:c fmi:4 fmattack:0 analyze:1 ]",
|
||||
"[ 1/4 → 1/2 | note:e fmi:4 fmattack:0 analyze:1 ]",
|
||||
"[ 1/2 → 3/4 | note:g fmi:4 fmattack:0 analyze:1 ]",
|
||||
"[ 3/4 → 1/1 | note:b fmi:4 fmattack:0 analyze:1 ]",
|
||||
"[ 1/1 → 5/4 | note:c fmi:4 fmattack:0.05 analyze:1 ]",
|
||||
"[ 5/4 → 3/2 | note:e fmi:4 fmattack:0.05 analyze:1 ]",
|
||||
"[ 3/2 → 7/4 | note:g fmi:4 fmattack:0.05 analyze:1 ]",
|
||||
"[ 7/4 → 2/1 | note:b fmi:4 fmattack:0.05 analyze: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 analyze: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 analyze:1 ]",
|
||||
"[ 3/1 → 13/4 | note:c fmi:4 fmattack:0.2 analyze:1 ]",
|
||||
"[ 13/4 → 7/2 | note:e fmi:4 fmattack:0.2 analyze:1 ]",
|
||||
"[ 7/2 → 15/4 | note:g fmi:4 fmattack:0.2 analyze:1 ]",
|
||||
"[ 15/4 → 4/1 | note:b fmi:4 fmattack:0.2 analyze:1 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
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 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze: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 ]",
|
||||
"[ 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 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "fmh" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c fmi:4 fmh:1 ]",
|
||||
"[ 1/4 → 1/2 | note:e fmi:4 fmh:1 ]",
|
||||
"[ 1/2 → 3/4 | note:g fmi:4 fmh:1 ]",
|
||||
"[ 3/4 → 1/1 | note:b fmi:4 fmh:1 ]",
|
||||
"[ 1/1 → 5/4 | note:c fmi:4 fmh:2 ]",
|
||||
"[ 5/4 → 3/2 | note:e fmi:4 fmh:2 ]",
|
||||
"[ 3/2 → 7/4 | note:g fmi:4 fmh:2 ]",
|
||||
"[ 7/4 → 2/1 | note:b fmi:4 fmh:2 ]",
|
||||
"[ 2/1 → 9/4 | note:c fmi:4 fmh:1.5 ]",
|
||||
"[ 9/4 → 5/2 | note:e fmi:4 fmh:1.5 ]",
|
||||
"[ 5/2 → 11/4 | note:g fmi:4 fmh:1.5 ]",
|
||||
"[ 11/4 → 3/1 | note:b fmi:4 fmh:1.5 ]",
|
||||
"[ 3/1 → 13/4 | note:c fmi:4 fmh:1.61 ]",
|
||||
"[ 13/4 → 7/2 | note:e fmi:4 fmh:1.61 ]",
|
||||
"[ 7/2 → 15/4 | note:g fmi:4 fmh:1.61 ]",
|
||||
"[ 15/4 → 4/1 | note:b fmi:4 fmh:1.61 ]",
|
||||
"[ 0/1 → 1/4 | note:c fmi:4 fmh:1 analyze:1 ]",
|
||||
"[ 1/4 → 1/2 | note:e fmi:4 fmh:1 analyze:1 ]",
|
||||
"[ 1/2 → 3/4 | note:g fmi:4 fmh:1 analyze:1 ]",
|
||||
"[ 3/4 → 1/1 | note:b fmi:4 fmh:1 analyze:1 ]",
|
||||
"[ 1/1 → 5/4 | note:c fmi:4 fmh:2 analyze:1 ]",
|
||||
"[ 5/4 → 3/2 | note:e fmi:4 fmh:2 analyze:1 ]",
|
||||
"[ 3/2 → 7/4 | note:g fmi:4 fmh:2 analyze:1 ]",
|
||||
"[ 7/4 → 2/1 | note:b fmi:4 fmh:2 analyze:1 ]",
|
||||
"[ 2/1 → 9/4 | note:c fmi:4 fmh:1.5 analyze:1 ]",
|
||||
"[ 9/4 → 5/2 | note:e fmi:4 fmh:1.5 analyze:1 ]",
|
||||
"[ 5/2 → 11/4 | note:g fmi:4 fmh:1.5 analyze:1 ]",
|
||||
"[ 11/4 → 3/1 | note:b fmi:4 fmh:1.5 analyze:1 ]",
|
||||
"[ 3/1 → 13/4 | note:c fmi:4 fmh:1.61 analyze:1 ]",
|
||||
"[ 13/4 → 7/2 | note:e fmi:4 fmh:1.61 analyze:1 ]",
|
||||
"[ 7/2 → 15/4 | note:g fmi:4 fmh:1.61 analyze:1 ]",
|
||||
"[ 15/4 → 4/1 | note:b fmi:4 fmh:1.61 analyze: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 ]",
|
||||
"[ 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 analyze: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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
"[ 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 analyze:1 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
|
||||
@ -8,28 +8,52 @@ import { JsDoc } from '../../docs/JsDoc';
|
||||
|
||||
# Synths
|
||||
|
||||
For now, [samples](/learn/samples) are the main way to play with Strudel.
|
||||
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/).
|
||||
In addition to the sampling engine, strudel comes with a synthesizer to create sounds on the fly.
|
||||
|
||||
## 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.
|
||||
The power of patterns allows us to sequence any _param_ independently:
|
||||
### Additive Synthesis
|
||||
|
||||
<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!
|
||||
`sawtooth` `square` and `triangle` are the basic waveforms available in `s`.
|
||||
<MiniRepl
|
||||
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 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
|
||||
|
||||
<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} />
|
||||
|
||||
## 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)...
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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 './files.mjs';
|
||||
|
||||
@ -8,6 +8,7 @@ export async function prebake() {
|
||||
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
|
||||
await Promise.all([
|
||||
registerSynthSounds(),
|
||||
registerZZFXSounds(),
|
||||
//registerSoundfonts(),
|
||||
// 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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user