Merge branch 'main' into deprecate-old-package-names

This commit is contained in:
Felix Roos 2024-01-18 09:33:16 +01:00
commit 171f4206a8
65 changed files with 2829 additions and 1113 deletions

View File

@ -10,3 +10,4 @@ paper
pnpm-lock.yaml
pnpm-workspace.yaml
**/dev-dist
website/.astro

View File

@ -234,7 +234,7 @@ const generic_params = [
* note("c3 e3").decay("<.1 .2 .3 .4>").sustain(0)
*
*/
['decay'],
['decay', 'dec'],
/**
* Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset.
*
@ -270,7 +270,7 @@ const generic_params = [
* s("bd sd,hh*3").bpf("<1000 2000 4000 8000>")
*
*/
[['bandf', 'bandq'], 'bpf', 'bp'],
[['bandf', 'bandq', 'bpenv'], 'bpf', 'bp'],
// TODO: in tidal, it seems to be normalized
/**
* Sets the **b**and-**p**ass **q**-factor (resonance).
@ -481,7 +481,7 @@ const generic_params = [
* s("bd*8").lpf("1000:0 1000:10 1000:20 1000:30")
*
*/
[['cutoff', 'resonance'], 'ctf', 'lpf', 'lp'],
[['cutoff', 'resonance', 'lpenv'], 'ctf', 'lpf', 'lp'],
/**
* Sets the lowpass filter envelope modulation depth.
@ -758,7 +758,7 @@ const generic_params = [
* .vibmod("<.25 .5 1 2 12>:8")
*/
[['vibmod', 'vib'], 'vmod'],
[['hcutoff', 'hresonance'], 'hpf', 'hp'],
[['hcutoff', 'hresonance', 'hpenv'], 'hpf', 'hp'],
/**
* Controls the **h**igh-**p**ass **q**-value.
*
@ -891,6 +891,82 @@ const generic_params = [
*
*/
['freq'],
// pitch envelope
/**
* Attack time of pitch envelope.
*
* @name pattack
* @synonyms patt
* @param {number | Pattern} time time in seconds
* @example
* note("<c eb g bb>").pattack("<0 .1 .25 .5>")
*
*/
['pattack', 'patt'],
/**
* Decay time of pitch envelope.
*
* @name pdecay
* @synonyms pdec
* @param {number | Pattern} time time in seconds
* @example
* note("<c eb g bb>").pdecay("<0 .1 .25 .5>")
*
*/
['pdecay', 'pdec'],
// TODO: how to use psustain?!
['psustain', 'psus'],
/**
* Release time of pitch envelope
*
* @name prelease
* @synonyms prel
* @param {number | Pattern} time time in seconds
* @example
* note("<c eb g bb> ~")
* .release(.5) // to hear the pitch release
* .prelease("<0 .1 .25 .5>")
*
*/
['prelease', 'prel'],
/**
* Amount of pitch envelope. Negative values will flip the envelope.
* If you don't set other pitch envelope controls, `pattack:.2` will be the default.
*
* @name penv
* @param {number | Pattern} semitones change in semitones
* @example
* note("c")
* .penv("<12 7 1 .5 0 -1 -7 -12>")
*
*/
['penv'],
/**
* Curve of envelope. Defaults to linear. exponential is good for kicks
*
* @name pcurve
* @param {number | Pattern} type 0 = linear, 1 = exponential
* @example
* note("g1*2")
* .s("sine").pdec(.5)
* .penv(32)
* .pcurve("<0 1>")
*
*/
['pcurve'],
/**
* Sets the range anchor of the envelope:
* - anchor 0: range = [note, note + penv]
* - anchor 1: range = [note - penv, note]
* If you don't set an anchor, the value will default to the psustain value.
*
* @name panchor
* @param {number | Pattern} anchor anchor offset
* @example
* note("c").penv(12).panchor("<0 .5 1 .5>")
*
*/
['panchor'],
// TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate
['gate', 'gat'],
// ['hatgrain'],
@ -1209,7 +1285,7 @@ const generic_params = [
* Formant filter to make things sound like vowels.
*
* @name vowel
* @param {string | Pattern} vowel You can use a e i o u.
* @param {string | Pattern} vowel You can use a e i o u ae aa oe ue y uh un en an on, corresponding to [a] [e] [i] [o] [u] [æ] [ɑ] [ø] [y] [ɯ] [ʌ] [œ̃] [ɛ̃] [ɑ̃] [ɔ̃]. Aliases: aa = å = ɑ, oe = ø = ö, y = ı, ae = æ.
* @example
* note("c2 <eb2 <g2 g1>>").s('sawtooth')
* .vowel("<a e i <o u>>")
@ -1394,10 +1470,20 @@ controls.adsr = register('adsr', (adsr, pat) => {
const [attack, decay, sustain, release] = adsr;
return pat.set({ attack, decay, sustain, release });
});
controls.ds = register('ds', (ds, pat) => {
ds = !Array.isArray(ds) ? [ds] : ds;
const [decay, sustain] = ds;
controls.ad = register('ad', (t, pat) => {
t = !Array.isArray(t) ? [t] : t;
const [attack, decay = attack] = t;
return pat.attack(attack).decay(decay);
});
controls.ds = register('ds', (t, pat) => {
t = !Array.isArray(t) ? [t] : t;
const [decay, sustain = 0] = t;
return pat.set({ decay, sustain });
});
controls.ds = register('ar', (t, pat) => {
t = !Array.isArray(t) ? [t] : t;
const [attack, release = attack] = t;
return pat.set({ attack, release });
});
export default controls;

View File

@ -86,7 +86,7 @@ export const midi2note = (n) => {
// modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does)
export const _mod = (n, m) => ((n % m) + m) % m;
export function nanFallback(value, fallback) {
export function nanFallback(value, fallback = 0) {
if (isNaN(Number(value))) {
logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning');
return fallback;

View File

@ -292,7 +292,7 @@ function peg$parse(input, options) {
var peg$f3 = function(s) { return s };
var peg$f4 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; };
var peg$f5 = function(a) { return a };
var peg$f6 = function(s) { s.arguments_.alignment = 'slowcat'; return s; };
var peg$f6 = function(s) { s.arguments_.alignment = 'polymeter_slowcat'; return s; };
var peg$f7 = function(a) { return x => x.options_['weight'] = a };
var peg$f8 = function(a) { return x => x.options_['reps'] = a };
var peg$f9 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) };
@ -1073,7 +1073,7 @@ function peg$parse(input, options) {
}
if (s2 !== peg$FAILED) {
s3 = peg$parsews();
s4 = peg$parsesequence();
s4 = peg$parsepolymeter_stack();
if (s4 !== peg$FAILED) {
s5 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 62) {

View File

@ -119,8 +119,8 @@ polymeter_steps = "%"a:slice
// define a step-per-cycle timeline e.g <1 3 [3 5]>. We simply defer to a sequence and
// change the alignment to slowcat
slow_sequence = ws "<" ws s:sequence ws ">" ws
{ s.arguments_.alignment = 'slowcat'; return s; }
slow_sequence = ws "<" ws s:polymeter_stack ws ">" ws
{ s.arguments_.alignment = 'polymeter_slowcat'; return s; }
// a slice is either a single step or a sub cycle
slice = step / sub_cycle / polymeter / slow_sequence

View File

@ -91,6 +91,10 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
if (alignment === 'stack') {
return strudel.stack(...children);
}
if (alignment === 'polymeter_slowcat') {
const aligned = children.map((child) => child._slow(strudel.Fraction(child.__weight ?? 1)));
return strudel.stack(...aligned);
}
if (alignment === 'polymeter') {
// polymeter
const stepsPerCycle = ast.arguments_.stepsPerCycle
@ -104,15 +108,9 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
return strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
}
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
if (!weightedChildren && alignment === 'slowcat') {
return strudel.slowcat(...children);
}
if (weightedChildren) {
const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0);
const pat = strudel.timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
if (alignment === 'slowcat') {
return pat._slow(weightSum); // timecat + slow
}
pat.__weight = weightSum;
return pat;
}

View File

@ -1,5 +1,12 @@
import { noteToMidi, freqToMidi, getSoundIndex } from '@strudel.cycles/core';
import { getAudioContext, registerSound, getEnvelope } from '@strudel.cycles/webaudio';
import {
getAudioContext,
registerSound,
getParamADSR,
getADSRValues,
getPitchEnvelope,
getVibratoOscillator,
} from '@strudel.cycles/webaudio';
import gm from './gm.mjs';
let loadCache = {};
@ -130,24 +137,39 @@ export function registerSoundfonts() {
registerSound(
name,
async (time, value, onended) => {
const [attack, decay, sustain, release] = getADSRValues([
value.attack,
value.decay,
value.sustain,
value.release,
]);
const { duration } = value;
const n = getSoundIndex(value.n, fonts.length);
const { attack = 0.001, decay = 0.001, sustain = 1, release = 0.001 } = value;
const font = fonts[n];
const ctx = getAudioContext();
const bufferSource = await getFontBufferSource(font, value, ctx);
bufferSource.start(time);
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 0.3, time);
bufferSource.connect(envelope);
const stop = (releaseTime) => {
const silentAt = releaseEnvelope(releaseTime);
bufferSource.stop(silentAt);
};
const envGain = ctx.createGain();
const node = bufferSource.connect(envGain);
const holdEnd = time + duration;
getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, time, holdEnd, 'linear');
let envEnd = holdEnd + release + 0.01;
// vibrato
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, time);
// pitch envelope
getPitchEnvelope(bufferSource.detune, value, time, holdEnd);
bufferSource.stop(envEnd);
const stop = (releaseTime) => {};
bufferSource.onended = () => {
bufferSource.disconnect();
envelope.disconnect();
vibratoOscillator?.stop();
node.disconnect();
onended();
};
return { node: envelope, stop };
return { node, stop };
},
{ type: 'soundfont', prebake: true, fonts },
);

View File

@ -1,5 +1,5 @@
import { getAudioContext } from './superdough.mjs';
import { clamp } from './util.mjs';
import { clamp, nanFallback } from './util.mjs';
export function gainNode(value) {
const node = getAudioContext().createGain();
@ -7,78 +7,73 @@ export function gainNode(value) {
return node;
}
// alternative to getADSR returning the gain node and a stop handle to trigger the release anytime in the future
export const getEnvelope = (attack, decay, sustain, release, velocity, begin) => {
const gainNode = getAudioContext().createGain();
let phase = begin;
gainNode.gain.setValueAtTime(0, begin);
phase += attack;
gainNode.gain.linearRampToValueAtTime(velocity, phase); // attack
phase += decay;
let sustainLevel = sustain * velocity;
gainNode.gain.linearRampToValueAtTime(sustainLevel, phase); // decay / sustain
// sustain end
return {
node: gainNode,
stop: (t) => {
// to make sure the release won't begin before sustain is reached
phase = Math.max(t, phase);
// see https://github.com/tidalcycles/strudel/issues/522
gainNode.gain.setValueAtTime(sustainLevel, phase);
phase += release;
gainNode.gain.linearRampToValueAtTime(0, phase); // release
return phase;
},
};
};
export const getExpEnvelope = (attack, decay, sustain, release, velocity, begin) => {
sustain = Math.max(0.001, sustain);
velocity = Math.max(0.001, velocity);
const gainNode = getAudioContext().createGain();
gainNode.gain.setValueAtTime(0.0001, begin);
gainNode.gain.exponentialRampToValueAtTime(velocity, begin + attack);
gainNode.gain.exponentialRampToValueAtTime(sustain * velocity, begin + attack + decay);
return {
node: gainNode,
stop: (t) => {
// similar to getEnvelope, this will glitch if sustain level has not been reached
gainNode.gain.exponentialRampToValueAtTime(0.0001, t + release);
},
};
};
export const getADSR = (attack, decay, sustain, release, velocity, begin, end) => {
const gainNode = getAudioContext().createGain();
gainNode.gain.setValueAtTime(0, begin);
gainNode.gain.linearRampToValueAtTime(velocity, begin + attack); // attack
gainNode.gain.linearRampToValueAtTime(sustain * velocity, begin + attack + decay); // sustain start
gainNode.gain.setValueAtTime(sustain * velocity, end); // sustain end
gainNode.gain.linearRampToValueAtTime(0, end + release); // release
// for some reason, using exponential ramping creates little cracklings
/* let t = begin;
gainNode.gain.setValueAtTime(0, t);
gainNode.gain.exponentialRampToValueAtTime(velocity, (t += attack));
const sustainGain = Math.max(sustain * velocity, 0.001);
gainNode.gain.exponentialRampToValueAtTime(sustainGain, (t += decay));
if (end - begin < attack + decay) {
gainNode.gain.cancelAndHoldAtTime(end);
} else {
gainNode.gain.setValueAtTime(sustainGain, end);
const getSlope = (y1, y2, x1, x2) => {
const denom = x2 - x1;
if (denom === 0) {
return 0;
}
gainNode.gain.exponentialRampToValueAtTime(0.001, end + release); // release */
return gainNode;
return (y2 - y1) / (x2 - x1);
};
export const getParamADSR = (param, attack, decay, sustain, release, min, max, begin, end) => {
export const getParamADSR = (
param,
attack,
decay,
sustain,
release,
min,
max,
begin,
end,
//exponential works better for frequency modulations (such as filter cutoff) due to human ear perception
curve = 'exponential',
) => {
attack = nanFallback(attack);
decay = nanFallback(decay);
sustain = nanFallback(sustain);
release = nanFallback(release);
const ramp = curve === 'exponential' ? 'exponentialRampToValueAtTime' : 'linearRampToValueAtTime';
if (curve === 'exponential') {
min = min === 0 ? 0.001 : min;
max = max === 0 ? 0.001 : max;
}
const range = max - min;
const peak = min + range;
const sustainLevel = min + sustain * range;
const peak = max;
const sustainVal = min + sustain * range;
const duration = end - begin;
const envValAtTime = (time) => {
let val;
if (attack > time) {
let slope = getSlope(min, peak, 0, attack);
val = time * slope + (min > peak ? min : 0);
} else {
val = (time - attack) * getSlope(peak, sustainVal, 0, decay) + peak;
}
if (curve === 'exponential') {
val = val || 0.001;
}
return val;
};
param.setValueAtTime(min, begin);
param.linearRampToValueAtTime(peak, begin + attack);
param.linearRampToValueAtTime(sustainLevel, begin + attack + decay);
param.setValueAtTime(sustainLevel, end);
param.linearRampToValueAtTime(min, end + Math.max(release, 0.1));
if (attack > duration) {
//attack
param[ramp](envValAtTime(duration), end);
} else if (attack + decay > duration) {
//attack
param[ramp](envValAtTime(attack), begin + attack);
//decay
param[ramp](envValAtTime(duration), end);
} else {
//attack
param[ramp](envValAtTime(attack), begin + attack);
//decay
param[ramp](envValAtTime(attack + decay), begin + attack + decay);
//sustain
param.setValueAtTime(sustainVal, end);
}
//release
param[ramp](min, end + release);
};
export function getCompressor(ac, threshold, ratio, knee, attack, release) {
@ -92,38 +87,44 @@ export function getCompressor(ac, threshold, ratio, knee, attack, release) {
return new DynamicsCompressorNode(ac, options);
}
export function createFilter(
context,
type,
frequency,
Q,
attack,
decay,
sustain,
release,
fenv,
start,
end,
fanchor = 0.5,
) {
// changes the default values of the envelope based on what parameters the user has defined
// so it behaves more like you would expect/familiar as other synthesis tools
// ex: sound(val).decay(val) will behave as a decay only envelope. sound(val).attack(val).decay(val) will behave like an "ad" env, etc.
export const getADSRValues = (params, curve = 'linear', defaultValues) => {
const envmin = curve === 'exponential' ? 0.001 : 0.001;
const releaseMin = 0.01;
const envmax = 1;
const [a, d, s, r] = params;
if (a == null && d == null && s == null && r == null) {
return defaultValues ?? [envmin, envmin, envmax, releaseMin];
}
const sustain = s != null ? s : (a != null && d == null) || (a == null && d == null) ? envmax : envmin;
return [Math.max(a ?? 0, envmin), Math.max(d ?? 0, envmin), Math.min(sustain, envmax), Math.max(r ?? 0, releaseMin)];
};
export function createFilter(context, type, frequency, Q, att, dec, sus, rel, fenv, start, end, fanchor) {
const curve = 'exponential';
const [attack, decay, sustain, release] = getADSRValues([att, dec, sus, rel], curve, [0.005, 0.14, 0, 0.1]);
const filter = context.createBiquadFilter();
filter.type = type;
filter.Q.value = Q;
filter.frequency.value = frequency;
// envelope is active when any of these values is set
const hasEnvelope = att ?? dec ?? sus ?? rel ?? fenv;
// Apply ADSR to filter frequency
if (!isNaN(fenv) && fenv !== 0) {
const offset = fenv * fanchor;
const min = clamp(2 ** -offset * frequency, 0, 20000);
const max = clamp(2 ** (fenv - offset) * frequency, 0, 20000);
// console.log('min', min, 'max', max);
getParamADSR(filter.frequency, attack, decay, sustain, release, min, max, start, end);
if (hasEnvelope !== undefined) {
fenv = nanFallback(fenv, 1, true);
fanchor = nanFallback(fanchor, 0, true);
const fenvAbs = Math.abs(fenv);
const offset = fenvAbs * fanchor;
let min = clamp(2 ** -offset * frequency, 0, 20000);
let max = clamp(2 ** (fenvAbs - offset) * frequency, 0, 20000);
if (fenv < 0) [min, max] = [max, min];
getParamADSR(filter.frequency, attack, decay, sustain, release, min, max, start, end, curve);
return filter;
}
return filter;
}
@ -148,3 +149,40 @@ export function drywet(dry, wet, wetAmount = 0) {
wet_gain.connect(mix);
return mix;
}
let curves = ['linear', 'exponential'];
export function getPitchEnvelope(param, value, t, holdEnd) {
// envelope is active when any of these values is set
const hasEnvelope = value.pattack ?? value.pdecay ?? value.psustain ?? value.prelease ?? value.penv;
if (!hasEnvelope) {
return;
}
const penv = nanFallback(value.penv, 1, true);
const curve = curves[value.pcurve ?? 0];
let [pattack, pdecay, psustain, prelease] = getADSRValues(
[value.pattack, value.pdecay, value.psustain, value.prelease],
curve,
[0.2, 0.001, 1, 0.001],
);
let panchor = value.panchor ?? psustain;
const cents = penv * 100; // penv is in semitones
const min = 0 - cents * panchor;
const max = cents - cents * panchor;
getParamADSR(param, pattack, pdecay, psustain, prelease, min, max, t, holdEnd, curve);
}
export function getVibratoOscillator(param, value, t) {
const { vibmod = 0.5, vib } = value;
let vibratoOscillator;
if (vib > 0) {
vibratoOscillator = getAudioContext().createOscillator();
vibratoOscillator.frequency.value = vib;
const gain = getAudioContext().createGain();
// Vibmod is the amount of vibrato, in semitones
gain.gain.value = vibmod * 100;
vibratoOscillator.connect(gain);
gain.connect(param);
vibratoOscillator.start(t);
return vibratoOscillator;
}
}

View File

@ -1,6 +1,6 @@
import { noteToMidi, valueToMidi, getSoundIndex } from './util.mjs';
import { getAudioContext, registerSound } from './index.mjs';
import { getEnvelope } from './helpers.mjs';
import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
import { logger } from './logger.mjs';
const bufferCache = {}; // string: Promise<ArrayBuffer>
@ -243,8 +243,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
begin = 0,
loopEnd = 1,
end = 1,
vib,
vibmod = 0.5,
duration,
} = value;
// load sample
if (speed === 0) {
@ -254,24 +253,15 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
loop = s.startsWith('wt_') ? 1 : value.loop;
const ac = getAudioContext();
// destructure adsr here, because the default should be different for synths and samples
const { attack = 0.001, decay = 0.001, sustain = 1, release = 0.001 } = value;
let [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]);
//const soundfont = getSoundfontKey(s);
const time = t + nudge;
const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank, resolveUrl);
// vibrato
let vibratoOscillator;
if (vib > 0) {
vibratoOscillator = getAudioContext().createOscillator();
vibratoOscillator.frequency.value = vib;
const gain = getAudioContext().createGain();
// Vibmod is the amount of vibrato, in semitones
gain.gain.value = vibmod * 100;
vibratoOscillator.connect(gain);
gain.connect(bufferSource.detune);
vibratoOscillator.start(0);
}
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t);
// asny stuff above took too long?
if (ac.currentTime > t) {
@ -298,26 +288,31 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
bufferSource.loopEnd = loopEnd * bufferSource.buffer.duration - offset;
}
bufferSource.start(time, offset);
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t);
bufferSource.connect(envelope);
const envGain = ac.createGain();
const node = bufferSource.connect(envGain);
if (clip == null && loop == null && value.release == null) {
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
duration = (end - begin) * bufferDuration;
}
let holdEnd = t + duration;
getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear');
// pitch envelope
getPitchEnvelope(bufferSource.detune, value, t, holdEnd);
const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox...
envelope.connect(out);
node.connect(out);
bufferSource.onended = function () {
bufferSource.disconnect();
vibratoOscillator?.stop();
envelope.disconnect();
node.disconnect();
out.disconnect();
onended();
};
const stop = (endTime, playWholeBuffer = clip === undefined && loop === undefined) => {
let releaseTime = endTime;
if (playWholeBuffer) {
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
releaseTime = t + (end - begin) * bufferDuration;
}
const silentAt = releaseEnvelope(releaseTime);
bufferSource.stop(silentAt);
};
let envEnd = holdEnd + release + 0.01;
bufferSource.stop(envEnd);
const stop = (endTime, playWholeBuffer) => {};
const handle = { node: out, bufferSource, stop };
// cut groups

View File

@ -280,26 +280,26 @@ export const superdough = async (value, deadline, hapDuration) => {
// low pass
cutoff,
lpenv,
lpattack = 0.01,
lpdecay = 0.01,
lpsustain = 1,
lprelease = 0.01,
lpattack,
lpdecay,
lpsustain,
lprelease,
resonance = 1,
// high pass
hpenv,
hcutoff,
hpattack = 0.01,
hpdecay = 0.01,
hpsustain = 1,
hprelease = 0.01,
hpattack,
hpdecay,
hpsustain,
hprelease,
hresonance = 1,
// band pass
bpenv,
bandf,
bpattack = 0.01,
bpdecay = 0.01,
bpsustain = 1,
bprelease = 0.01,
bpattack,
bpdecay,
bpsustain,
bprelease,
bandq = 1,
channels = [1, 2],
//phaser
@ -333,6 +333,7 @@ export const superdough = async (value, deadline, hapDuration) => {
compressorAttack,
compressorRelease,
} = value;
gain = nanFallback(gain, 1);
//music programs/audio gear usually increments inputs/outputs from 1, so imitate that behavior

View File

@ -1,6 +1,6 @@
import { midiToFreq, noteToMidi } from './util.mjs';
import { registerSound, getAudioContext } from './superdough.mjs';
import { gainNode, getEnvelope, getExpEnvelope } from './helpers.mjs';
import { gainNode, getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
const mod = (freq, range = 1, type = 'sine') => {
@ -29,8 +29,11 @@ export function registerSynthSounds() {
registerSound(
s,
(t, value, onended) => {
// destructure adsr here, because the default should be different for synths and samples
let { attack = 0.001, decay = 0.05, sustain = 0.6, release = 0.01 } = value;
const [attack, decay, sustain, release] = getADSRValues(
[value.attack, value.decay, value.sustain, value.release],
'linear',
[0.001, 0.05, 0.6, 0.01],
);
let sound;
if (waveforms.includes(s)) {
@ -45,21 +48,24 @@ export function registerSynthSounds() {
// turn down
const g = gainNode(0.3);
// gain envelope
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t);
const { duration } = value;
o.onended = () => {
o.disconnect();
g.disconnect();
onended();
};
const envGain = gainNode(1);
let node = o.connect(g).connect(envGain);
const holdEnd = t + duration;
getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear');
const envEnd = holdEnd + release + 0.01;
triggerRelease?.(envEnd);
stop(envEnd);
return {
node: o.connect(g).connect(envelope),
stop: (releaseTime) => {
const silentAt = releaseEnvelope(releaseTime);
triggerRelease?.(releaseTime);
stop(silentAt);
},
node,
stop: (releaseTime) => {},
};
},
{ type: 'synth', prebake: true },
@ -99,28 +105,24 @@ export function waveformN(partials, type) {
}
// expects one of waveforms as s
export function getOscillator(
s,
t,
{
export function getOscillator(s, t, value) {
let {
n: partials,
note,
freq,
vib = 0,
vibmod = 0.5,
noise = 0,
// fm
fmh: fmHarmonicity = 1,
fmi: fmModulationIndex,
fmenv: fmEnvelopeType = 'lin',
fmenv: fmEnvelopeType = 'exp',
fmattack: fmAttack,
fmdecay: fmDecay,
fmsustain: fmSustain,
fmrelease: fmRelease,
fmvelocity: fmVelocity,
fmwave: fmWaveform = 'sine',
},
) {
duration,
} = value;
let ac = getAudioContext();
let o;
// If no partials are given, use stock waveforms
@ -148,42 +150,39 @@ export function getOscillator(
o.start(t);
// FM
let stopFm, fmEnvelope;
let stopFm;
let envGain = ac.createGain();
if (fmModulationIndex) {
const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex, fmWaveform);
if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) {
// no envelope by default
modulator.connect(o.frequency);
} else {
fmAttack = fmAttack ?? 0.001;
fmDecay = fmDecay ?? 0.001;
fmSustain = fmSustain ?? 1;
fmRelease = fmRelease ?? 0.001;
fmVelocity = fmVelocity ?? 1;
fmEnvelope = getEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t);
if (fmEnvelopeType === 'exp') {
fmEnvelope = getExpEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t);
fmEnvelope.node.maxValue = fmModulationIndex * 2;
fmEnvelope.node.minValue = 0.00001;
}
modulator.connect(fmEnvelope.node);
fmEnvelope.node.connect(o.frequency);
const [attack, decay, sustain, release] = getADSRValues([fmAttack, fmDecay, fmSustain, fmRelease]);
const holdEnd = t + duration;
getParamADSR(
envGain.gain,
attack,
decay,
sustain,
release,
0,
1,
t,
holdEnd,
fmEnvelopeType === 'exp' ? 'exponential' : 'linear',
);
modulator.connect(envGain);
envGain.connect(o.frequency);
}
stopFm = stop;
}
// Additional oscillator for vibrato effect
let vibratoOscillator;
if (vib > 0) {
vibratoOscillator = getAudioContext().createOscillator();
vibratoOscillator.frequency.value = vib;
const gain = getAudioContext().createGain();
// Vibmod is the amount of vibrato, in semitones
gain.gain.value = vibmod * 100;
vibratoOscillator.connect(gain);
gain.connect(o.detune);
vibratoOscillator.start(t);
}
let vibratoOscillator = getVibratoOscillator(o.detune, value, t);
// pitch envelope
getPitchEnvelope(o.detune, value, t, t + duration);
let noiseMix;
if (noise) {
@ -199,7 +198,7 @@ export function getOscillator(
o.stop(time);
},
triggerRelease: (time) => {
fmEnvelope?.stop(time);
// envGain?.stop(time);
},
};
}

View File

@ -54,9 +54,9 @@ export const valueToMidi = (value, fallbackValue) => {
return fallbackValue;
};
export function nanFallback(value, fallback) {
export function nanFallback(value, fallback = 0, silent) {
if (isNaN(Number(value))) {
logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning');
!silent && logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning');
return fallback;
}
return value;

View File

@ -5,6 +5,37 @@ export var vowelFormant = {
i: { freqs: [270, 1850, 2900, 3350, 3590], gains: [1, 0.0631, 0.0631, 0.0158, 0.0158], qs: [40, 90, 100, 120, 120] },
o: { freqs: [430, 820, 2700, 3000, 3300], gains: [1, 0.3162, 0.0501, 0.0794, 0.01995], qs: [40, 80, 100, 120, 120] },
u: { freqs: [370, 630, 2750, 3000, 3400], gains: [1, 0.1, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] },
ae: { freqs: [650, 1515, 2400, 3000, 3350], gains: [1, 0.5, 0.1008, 0.0631, 0.0126], qs: [80, 90, 120, 130, 140] },
aa: { freqs: [560, 900, 2570, 3000, 3300], gains: [1, 0.5, 0.0708, 0.0631, 0.0126], qs: [80, 90, 120, 130, 140] },
oe: { freqs: [500, 1430, 2300, 3000, 3300], gains: [1, 0.2, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] },
ue: { freqs: [250, 1750, 2150, 3200, 3300], gains: [1, 0.1, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] },
y: { freqs: [400, 1460, 2400, 3000, 3300], gains: [1, 0.2, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
uh: { freqs: [600, 1250, 2100, 3100, 3500], gains: [1, 0.3, 0.0608, 0.0316, 0.01995], qs: [40, 70, 100, 120, 130] },
un: { freqs: [500, 1240, 2280, 3000, 3500], gains: [1, 0.1, 0.1708, 0.0216, 0.02995], qs: [40, 60, 100, 120, 120] },
en: { freqs: [600, 1480, 2450, 3200, 3300], gains: [1, 0.15, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
an: { freqs: [700, 1050, 2500, 3000, 3300], gains: [1, 0.1, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
on: { freqs: [500, 1080, 2350, 3000, 3300], gains: [1, 0.1, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] },
get æ() {
return this.ae;
},
get ø() {
return this.oe;
},
get ɑ() {
return this.aa;
},
get å() {
return this.aa;
},
get ö() {
return this.oe;
},
get ü() {
return this.ue;
},
get ı() {
return this.y;
},
};
if (typeof GainNode !== 'undefined') {
class VowelNode extends GainNode {

1066
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -3332,6 +3332,55 @@ exports[`runs examples > example "pan" example index 0 1`] = `
]
`;
exports[`runs examples > example "panchor" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:c penv:12 panchor:0 ]",
"[ 1/1 → 2/1 | note:c penv:12 panchor:0.5 ]",
"[ 2/1 → 3/1 | note:c penv:12 panchor:1 ]",
"[ 3/1 → 4/1 | note:c penv:12 panchor:0.5 ]",
]
`;
exports[`runs examples > example "pattack" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:c pattack:0 ]",
"[ 1/1 → 2/1 | note:eb pattack:0.1 ]",
"[ 2/1 → 3/1 | note:g pattack:0.25 ]",
"[ 3/1 → 4/1 | note:bb pattack:0.5 ]",
]
`;
exports[`runs examples > example "pcurve" example index 0 1`] = `
[
"[ 0/1 → 1/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
"[ 1/2 → 1/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
"[ 1/1 → 3/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
"[ 3/2 → 2/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
"[ 2/1 → 5/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
"[ 5/2 → 3/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]",
"[ 3/1 → 7/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
"[ 7/2 → 4/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]",
]
`;
exports[`runs examples > example "pdecay" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:c pdecay:0 ]",
"[ 1/1 → 2/1 | note:eb pdecay:0.1 ]",
"[ 2/1 → 3/1 | note:g pdecay:0.25 ]",
"[ 3/1 → 4/1 | note:bb pdecay:0.5 ]",
]
`;
exports[`runs examples > example "penv" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:c penv:12 ]",
"[ 1/1 → 2/1 | note:c penv:7 ]",
"[ 2/1 → 3/1 | note:c penv:1 ]",
"[ 3/1 → 4/1 | note:c penv:0.5 ]",
]
`;
exports[`runs examples > example "perlin" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:hh cutoff:512.5097280354112 ]",
@ -3660,6 +3709,15 @@ exports[`runs examples > example "postgain" example index 0 1`] = `
]
`;
exports[`runs examples > example "prelease" example index 0 1`] = `
[
"[ 0/1 → 1/2 | note:c release:0.5 prelease:0 ]",
"[ 1/1 → 3/2 | note:eb release:0.5 prelease:0.1 ]",
"[ 2/1 → 5/2 | note:g release:0.5 prelease:0.25 ]",
"[ 3/1 → 7/2 | note:bb release:0.5 prelease:0.5 ]",
]
`;
exports[`runs examples > example "press" example index 0 1`] = `
[
"[ 0/1 → 1/2 | s:hh ]",

View File

@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { getMetadata } from '../website/src/pages/metadata_parser';
import { getMetadata } from '../website/src/metadata_parser';
describe.concurrent('Metadata parser', () => {
it('loads a tag from inline comment', async () => {

276
website/.astro/types.d.ts vendored Normal file
View File

@ -0,0 +1,276 @@
declare module 'astro:content' {
interface Render {
'.mdx': Promise<{
Content: import('astro').MarkdownInstance<{}>['Content'];
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}>;
}
}
declare module 'astro:content' {
interface Render {
'.md': Promise<{
Content: import('astro').MarkdownInstance<{}>['Content'];
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}>;
}
}
declare module 'astro:content' {
export { z } from 'astro/zod';
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
export type CollectionKey = keyof AnyEntryMap;
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
export type ContentCollectionKey = keyof ContentEntryMap;
export type DataCollectionKey = keyof DataEntryMap;
// This needs to be in sync with ImageMetadata
export type ImageFunction = () => import('astro/zod').ZodObject<{
src: import('astro/zod').ZodString;
width: import('astro/zod').ZodNumber;
height: import('astro/zod').ZodNumber;
format: import('astro/zod').ZodUnion<
[
import('astro/zod').ZodLiteral<'png'>,
import('astro/zod').ZodLiteral<'jpg'>,
import('astro/zod').ZodLiteral<'jpeg'>,
import('astro/zod').ZodLiteral<'tiff'>,
import('astro/zod').ZodLiteral<'webp'>,
import('astro/zod').ZodLiteral<'gif'>,
import('astro/zod').ZodLiteral<'svg'>,
import('astro/zod').ZodLiteral<'avif'>,
]
>;
}>;
type BaseSchemaWithoutEffects =
| import('astro/zod').AnyZodObject
| import('astro/zod').ZodUnion<[BaseSchemaWithoutEffects, ...BaseSchemaWithoutEffects[]]>
| import('astro/zod').ZodDiscriminatedUnion<string, import('astro/zod').AnyZodObject[]>
| import('astro/zod').ZodIntersection<BaseSchemaWithoutEffects, BaseSchemaWithoutEffects>;
type BaseSchema =
| BaseSchemaWithoutEffects
| import('astro/zod').ZodEffects<BaseSchemaWithoutEffects>;
export type SchemaContext = { image: ImageFunction };
type DataCollectionConfig<S extends BaseSchema> = {
type: 'data';
schema?: S | ((context: SchemaContext) => S);
};
type ContentCollectionConfig<S extends BaseSchema> = {
type?: 'content';
schema?: S | ((context: SchemaContext) => S);
};
type CollectionConfig<S> = ContentCollectionConfig<S> | DataCollectionConfig<S>;
export function defineCollection<S extends BaseSchema>(
input: CollectionConfig<S>
): CollectionConfig<S>;
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
ContentEntryMap[C]
>['slug'];
export function getEntryBySlug<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
// Note that this has to accept a regular string too, for SSR
entrySlug: E
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
collection: C,
entryId: E
): Promise<CollectionEntry<C>>;
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
collection: C,
filter?: (entry: CollectionEntry<C>) => entry is E
): Promise<E[]>;
export function getCollection<C extends keyof AnyEntryMap>(
collection: C,
filter?: (entry: CollectionEntry<C>) => unknown
): Promise<CollectionEntry<C>[]>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(entry: {
collection: C;
slug: E;
}): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(entry: {
collection: C;
id: E;
}): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
slug: E
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
collection: C,
id: E
): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
/** Resolve an array of entry references from the same collection */
export function getEntries<C extends keyof ContentEntryMap>(
entries: {
collection: C;
slug: ValidContentEntrySlug<C>;
}[]
): Promise<CollectionEntry<C>[]>;
export function getEntries<C extends keyof DataEntryMap>(
entries: {
collection: C;
id: keyof DataEntryMap[C];
}[]
): Promise<CollectionEntry<C>[]>;
export function reference<C extends keyof AnyEntryMap>(
collection: C
): import('astro/zod').ZodEffects<
import('astro/zod').ZodString,
C extends keyof ContentEntryMap
? {
collection: C;
slug: ValidContentEntrySlug<C>;
}
: {
collection: C;
id: keyof DataEntryMap[C];
}
>;
// Allow generic `string` to avoid excessive type errors in the config
// if `dev` is not running to update as you edit.
// Invalid collection names will be caught at build time.
export function reference<C extends string>(
collection: C
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
>;
type ContentEntryMap = {
"blog": {
"release-0.0.2-schwindlig.mdx": {
id: "release-0.0.2-schwindlig.mdx";
slug: "release-002-schwindlig";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.0.2.1-stuermisch.mdx": {
id: "release-0.0.2.1-stuermisch.mdx";
slug: "release-0021-stuermisch";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.0.3-maelstrom.mdx": {
id: "release-0.0.3-maelstrom.mdx";
slug: "release-003-maelstrom";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.0.4-gischt.mdx": {
id: "release-0.0.4-gischt.mdx";
slug: "release-004-gischt";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.3.0-donauwelle.mdx": {
id: "release-0.3.0-donauwelle.mdx";
slug: "release-030-donauwelle";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.4.0-brandung.mdx": {
id: "release-0.4.0-brandung.mdx";
slug: "release-040-brandung";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.5.0-wirbel.mdx": {
id: "release-0.5.0-wirbel.mdx";
slug: "release-050-wirbel";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.6.0-zimtschnecke.mdx": {
id: "release-0.6.0-zimtschnecke.mdx";
slug: "release-060-zimtschnecke";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.7.0-zuckerguss.mdx": {
id: "release-0.7.0-zuckerguss.mdx";
slug: "release-070-zuckerguss";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.8.0-himbeermuffin.mdx": {
id: "release-0.8.0-himbeermuffin.mdx";
slug: "release-080-himbeermuffin";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
"release-0.9.0-bananenbrot.mdx": {
id: "release-0.9.0-bananenbrot.mdx";
slug: "release-090-bananenbrot";
body: string;
collection: "blog";
data: InferEntrySchema<"blog">
} & { render(): Render[".mdx"] };
};
};
type DataEntryMap = {
};
type AnyEntryMap = ContentEntryMap & DataEntryMap;
type ContentConfig = typeof import("../src/content/config");
}

120
website/database.types.ts Normal file
View File

@ -0,0 +1,120 @@
// generated with https://supabase.com/docs/reference/javascript/typescript-support#generating-typescript-types
export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[];
export interface Database {
public: {
Tables: {
code: {
Row: {
code: string | null;
created_at: string | null;
featured: boolean | null;
hash: string | null;
id: number;
public: boolean | null;
};
Insert: {
code?: string | null;
created_at?: string | null;
featured?: boolean | null;
hash?: string | null;
id?: number;
public?: boolean | null;
};
Update: {
code?: string | null;
created_at?: string | null;
featured?: boolean | null;
hash?: string | null;
id?: number;
public?: boolean | null;
};
Relationships: [];
};
};
Views: {
[_ in never]: never;
};
Functions: {
[_ in never]: never;
};
Enums: {
[_ in never]: never;
};
CompositeTypes: {
[_ in never]: never;
};
};
}
export type Tables<
PublicTableNameOrOptions extends
| keyof (Database['public']['Tables'] & Database['public']['Views'])
| { schema: keyof Database },
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
? keyof (Database[PublicTableNameOrOptions['schema']]['Tables'] &
Database[PublicTableNameOrOptions['schema']]['Views'])
: never = never,
> = PublicTableNameOrOptions extends { schema: keyof Database }
? (Database[PublicTableNameOrOptions['schema']]['Tables'] &
Database[PublicTableNameOrOptions['schema']]['Views'])[TableName] extends {
Row: infer R;
}
? R
: never
: PublicTableNameOrOptions extends keyof (Database['public']['Tables'] & Database['public']['Views'])
? (Database['public']['Tables'] & Database['public']['Views'])[PublicTableNameOrOptions] extends {
Row: infer R;
}
? R
: never
: never;
export type TablesInsert<
PublicTableNameOrOptions extends keyof Database['public']['Tables'] | { schema: keyof Database },
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
? keyof Database[PublicTableNameOrOptions['schema']]['Tables']
: never = never,
> = PublicTableNameOrOptions extends { schema: keyof Database }
? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends {
Insert: infer I;
}
? I
: never
: PublicTableNameOrOptions extends keyof Database['public']['Tables']
? Database['public']['Tables'][PublicTableNameOrOptions] extends {
Insert: infer I;
}
? I
: never
: never;
export type TablesUpdate<
PublicTableNameOrOptions extends keyof Database['public']['Tables'] | { schema: keyof Database },
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
? keyof Database[PublicTableNameOrOptions['schema']]['Tables']
: never = never,
> = PublicTableNameOrOptions extends { schema: keyof Database }
? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends {
Update: infer U;
}
? U
: never
: PublicTableNameOrOptions extends keyof Database['public']['Tables']
? Database['public']['Tables'][PublicTableNameOrOptions] extends {
Update: infer U;
}
? U
: never
: never;
export type Enums<
PublicEnumNameOrOptions extends keyof Database['public']['Enums'] | { schema: keyof Database },
EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database }
? keyof Database[PublicEnumNameOrOptions['schema']]['Enums']
: never = never,
> = PublicEnumNameOrOptions extends { schema: keyof Database }
? Database[PublicEnumNameOrOptions['schema']]['Enums'][EnumName]
: PublicEnumNameOrOptions extends keyof Database['public']['Enums']
? Database['public']['Enums'][PublicEnumNameOrOptions]
: never;

View File

@ -48,6 +48,7 @@
"astro": "^4.0.8",
"canvas": "^2.11.2",
"claviature": "^0.1.0",
"date-fns": "^3.2.0",
"nanoid": "^5.0.4",
"nanostores": "^0.9.5",
"react": "^18.2.0",

View File

@ -0,0 +1,27 @@
---
import type { CollectionEntry } from 'astro:content';
type Props = CollectionEntry<'blog'>['data'];
const { post } = Astro.props;
const { Content } = await post.render();
import { format } from 'date-fns';
---
<article
class="prose max-w-none prose-headings:font-sans prose-headings:font-black prose-headings:text-slate-900 dark:prose-headings:text-gray-200 dark:text-gray-400 dark:prose-strong:text-gray-400 dark:prose-code:text-slate-400 dark:prose-a:text-gray-300 prose-a:text-slate-900 prose-blockquote:text-slate-800 dark:prose-blockquote:text-slate-400"
>
<div class="pb-2">
<div class="md:flex justify-between">
<h1 class="mb-4" id={post.slug}>{post.data.title}</h1>
</div>
<p class="italic p-0 m-0">
<time datetime={post.data.date.toISOString()}>
{format(post.data.date, 'MMMM yyyy')} by {post.data.author}
</time>
</p>
</div>
<div>
<Content />
</div>
</article>

View File

@ -0,0 +1,5 @@
---
const { src } = Astro.props;
---
<video controls width="600" src={src}></video>

View File

@ -9,7 +9,7 @@ import MobileNav from '../../docs/MobileNav';
import { SIDEBAR } from '../../config';
type Props = {
currentPage: string;
currentPage?: string;
};
const { currentPage } = Astro.props as Props;

View File

@ -1,18 +1,11 @@
---
import type { Frontmatter } from '../../config';
import MoreMenu from '../RightSidebar/MoreMenu.astro';
import TableOfContents from '../RightSidebar/TableOfContents';
import type { MarkdownHeading } from 'astro';
type Props = {
frontmatter: Frontmatter;
headings: MarkdownHeading[];
githubEditUrl: string;
githubEditUrl?: string;
};
const { frontmatter, headings, githubEditUrl } = Astro.props as Props;
const title = frontmatter.title;
const currentPage = Astro.url.pathname;
const { githubEditUrl } = Astro.props as Props;
---
<article id="article" class="content">

View File

@ -2,7 +2,7 @@
import * as CONFIG from '../../config';
type Props = {
editHref: string;
editHref?: string;
};
const { editHref } = Astro.props as Props;

View File

@ -6,7 +6,7 @@ import AvatarList from '../Footer/AvatarList.astro';
type Props = {
headings: MarkdownHeading[];
githubEditUrl: string;
githubEditUrl?: string;
};
const { headings, githubEditUrl } = Astro.props as Props;

View File

@ -36,6 +36,13 @@ export function Showcase() {
}
let _videos = [
{ title: 'Coding Music With Strudel Workhop by Dan Gorelick and Viola He', id: 'oqyAJ4WeKoU' },
{ title: 'Hexe - playing w strudel live coding music', id: '03m3F5xVOMg' },
{ title: 'DJ_Dave - Array [Lil Data Edit]', id: 'KUujFuTcuKc' },
{ title: 'DJ_Dave - Bitrot [v10101a Edit]', id: 'z_cJMdBp67Q' },
{ title: 'you will not steve reich your way out of it', id: 'xpILnXcWyuo' },
{ title: 'dough dream #1 - strudel jam 12/03/23', id: 'p0J7XrT9JEs' },
{ title: 'eddyflux & superdirtspatz at the dough cathedral', id: 'GrkwKMQ7_Ys' },
// solstice 2023
{ title: 'Jade Rose @ solstice stream 2023', id: 'wg0vW5Ac7L0' },
{

View File

@ -57,6 +57,7 @@ export const SIDEBAR: Sidebar = {
Presentation: [
{ text: 'What is Strudel?', link: 'workshop/getting-started' },
{ text: 'Showcase', link: 'intro/showcase' },
{ text: 'Blog', link: 'blog' },
],
Workshop: [
// { text: 'Getting Started', link: 'workshop/getting-started' },

View File

@ -0,0 +1,40 @@
---
title: 'Release Notes v0.0.2 Schwindlig'
description: ''
date: '2022-03-28'
tags: ['meta']
author: froos
---
## What's Changed
- Most work done as [commits to main](https://github.com/tidalcycles/strudel/commits/2a0d8c3f77ff7b34e82602e2d02400707f367316)
- repl + reify functions by @felixroos in https://github.com/tidalcycles/strudel/pull/2
- Fix path by @yaxu in https://github.com/tidalcycles/strudel/pull/3
- update readme for local dev by @kindohm in https://github.com/tidalcycles/strudel/pull/4
- Patternify all the things by @yaxu in https://github.com/tidalcycles/strudel/pull/5
- krill parser + improved repl by @felixroos in https://github.com/tidalcycles/strudel/pull/6
- fixed editor crash by @felixroos in https://github.com/tidalcycles/strudel/pull/7
- timeCat by @yaxu in https://github.com/tidalcycles/strudel/pull/8
- Bugfix every, and create more top level functions by @yaxu in https://github.com/tidalcycles/strudel/pull/9
- Failing test for `when` WIP by @yaxu in https://github.com/tidalcycles/strudel/pull/10
- Added mask() and struct() by @yaxu in https://github.com/tidalcycles/strudel/pull/11
- Add continuous signals (sine, cosine, saw, etc) by @yaxu in https://github.com/tidalcycles/strudel/pull/13
- add apply and layer, and missing div/mul methods by @yaxu in https://github.com/tidalcycles/strudel/pull/15
- higher latencyHint by @felixroos in https://github.com/tidalcycles/strudel/pull/16
- test: 📦 Add missing dependency and a CI check, to prevent oversights ;p by @puria in https://github.com/tidalcycles/strudel/pull/17
- fix: 💄 Enhance visualisation of the Tutorial on mobile by @puria in https://github.com/tidalcycles/strudel/pull/19
- Stateful queries and events (WIP) by @yaxu in https://github.com/tidalcycles/strudel/pull/14
- Fix resolveState by @yaxu in https://github.com/tidalcycles/strudel/pull/22
- added \_asNumber + interprete numbers as midi by @felixroos in https://github.com/tidalcycles/strudel/pull/21
- Update package.json by @ChiakiUehira in https://github.com/tidalcycles/strudel/pull/23
- packaging by @felixroos in https://github.com/tidalcycles/strudel/pull/24
## New Contributors
- @felixroos made their first contribution in https://github.com/tidalcycles/strudel/pull/2
- @kindohm made their first contribution in https://github.com/tidalcycles/strudel/pull/4
- @puria made their first contribution in https://github.com/tidalcycles/strudel/pull/17
- @ChiakiUehira made their first contribution in https://github.com/tidalcycles/strudel/pull/23
**Full Changelog**: https://github.com/tidalcycles/strudel/commits/2a0d8c3f77ff7b34e82602e2d02400707f367316

View File

@ -0,0 +1,53 @@
---
title: 'Release Notes v0.0.3 Stürmisch'
date: '2022-05-20'
tags: ['meta']
author: froos
---
## What's Changed
- Add chunk, chunkBack and iterBack by @yaxu in https://github.com/tidalcycles/strudel/pull/25
- Update tutorial.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/37
- Update tutorial.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/38
- Compose by @felixroos in https://github.com/tidalcycles/strudel/pull/40
- Fix polymeter by @yaxu in https://github.com/tidalcycles/strudel/pull/44
- First run at squeezeBind, ref #32 by @yaxu in https://github.com/tidalcycles/strudel/pull/48
- Implement `chop()` by @yaxu in https://github.com/tidalcycles/strudel/pull/50
- OSC and SuperDirt support by @yaxu in https://github.com/tidalcycles/strudel/pull/27
- More functions by @yaxu in https://github.com/tidalcycles/strudel/pull/56
- More functions by @yaxu in https://github.com/tidalcycles/strudel/pull/61
- Separate out strudel.mjs, make index.mjs aggregate module by @yaxu in https://github.com/tidalcycles/strudel/pull/62
- Speech output by @felixroos in https://github.com/tidalcycles/strudel/pull/67
- use new fixed version of osc-js package by @felixroos in https://github.com/tidalcycles/strudel/pull/68
- First effort at rand() by @yaxu in https://github.com/tidalcycles/strudel/pull/69
- More randomness, fix `rand`, and add `brand`, `irand` and `choose` by @yaxu in https://github.com/tidalcycles/strudel/pull/70
- webaudio package by @felixroos in https://github.com/tidalcycles/strudel/pull/26
- Port `perlin` noise, `rangex`, and `palindrome` by @yaxu in https://github.com/tidalcycles/strudel/pull/73
- More random functions by @yaxu in https://github.com/tidalcycles/strudel/pull/74
- Try to fix appLeft / appRight by @yaxu in https://github.com/tidalcycles/strudel/pull/75
- Basic webserial support by @yaxu in https://github.com/tidalcycles/strudel/pull/80
- Webaudio in REPL by @felixroos in https://github.com/tidalcycles/strudel/pull/77
- add `striate()` by @yaxu in https://github.com/tidalcycles/strudel/pull/76
- Tidy up a couple of old files by @mindofmatthew in https://github.com/tidalcycles/strudel/pull/84
- Add pattern composers, implements #82 by @yaxu in https://github.com/tidalcycles/strudel/pull/83
- Fiddles with cat/stack by @yaxu in https://github.com/tidalcycles/strudel/pull/90
- Paper by @felixroos in https://github.com/tidalcycles/strudel/pull/98
- Change to Affero GPL by @yaxu in https://github.com/tidalcycles/strudel/pull/101
- Work on Codemirror 6 highlighting by @mindofmatthew in https://github.com/tidalcycles/strudel/pull/102
- Codemirror 6 by @felixroos in https://github.com/tidalcycles/strudel/pull/97
- Tune tests by @felixroos in https://github.com/tidalcycles/strudel/pull/104
- /embed package: web component for repl by @felixroos in https://github.com/tidalcycles/strudel/pull/106
- Reset, Restart and other composers by @felixroos in https://github.com/tidalcycles/strudel/pull/88
- Embed style by @felixroos in https://github.com/tidalcycles/strudel/pull/109
- In source doc by @yaxu in https://github.com/tidalcycles/strudel/pull/105
- `.brak()`, `.inside()` and `.outside()` by @yaxu in https://github.com/tidalcycles/strudel/pull/112
- loopAt by @yaxu in https://github.com/tidalcycles/strudel/pull/114
- Osc timing improvements by @yaxu in https://github.com/tidalcycles/strudel/pull/113
## New Contributors
- @bwagner made their first contribution in https://github.com/tidalcycles/strudel/pull/37
- @mindofmatthew made their first contribution in https://github.com/tidalcycles/strudel/pull/84
**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.0.2...@strudel.cycles/core@0.1.0

View File

@ -0,0 +1,66 @@
---
title: 'Release Notes v0.0.3 Maelstrom'
description: ''
date: '2022-06-18'
tags: ['meta']
author: froos
---
## What's Changed
- Add chunk, chunkBack and iterBack by @yaxu in https://github.com/tidalcycles/strudel/pull/25
- Update tutorial.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/37
- Update tutorial.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/38
- Compose by @felixroos in https://github.com/tidalcycles/strudel/pull/40
- Fix polymeter by @yaxu in https://github.com/tidalcycles/strudel/pull/44
- First run at squeezeBind, ref #32 by @yaxu in https://github.com/tidalcycles/strudel/pull/48
- Implement `chop()` by @yaxu in https://github.com/tidalcycles/strudel/pull/50
- OSC and SuperDirt support by @yaxu in https://github.com/tidalcycles/strudel/pull/27
- More functions by @yaxu in https://github.com/tidalcycles/strudel/pull/56
- More functions by @yaxu in https://github.com/tidalcycles/strudel/pull/61
- Separate out strudel.mjs, make index.mjs aggregate module by @yaxu in https://github.com/tidalcycles/strudel/pull/62
- Speech output by @felixroos in https://github.com/tidalcycles/strudel/pull/67
- use new fixed version of osc-js package by @felixroos in https://github.com/tidalcycles/strudel/pull/68
- First effort at rand() by @yaxu in https://github.com/tidalcycles/strudel/pull/69
- More randomness, fix `rand`, and add `brand`, `irand` and `choose` by @yaxu in https://github.com/tidalcycles/strudel/pull/70
- webaudio package by @felixroos in https://github.com/tidalcycles/strudel/pull/26
- Port `perlin` noise, `rangex`, and `palindrome` by @yaxu in https://github.com/tidalcycles/strudel/pull/73
- More random functions by @yaxu in https://github.com/tidalcycles/strudel/pull/74
- Try to fix appLeft / appRight by @yaxu in https://github.com/tidalcycles/strudel/pull/75
- Basic webserial support by @yaxu in https://github.com/tidalcycles/strudel/pull/80
- Webaudio in REPL by @felixroos in https://github.com/tidalcycles/strudel/pull/77
- add `striate()` by @yaxu in https://github.com/tidalcycles/strudel/pull/76
- Tidy up a couple of old files by @mindofmatthew in https://github.com/tidalcycles/strudel/pull/84
- Add pattern composers, implements #82 by @yaxu in https://github.com/tidalcycles/strudel/pull/83
- Fiddles with cat/stack by @yaxu in https://github.com/tidalcycles/strudel/pull/90
- Paper by @felixroos in https://github.com/tidalcycles/strudel/pull/98
- Change to Affero GPL by @yaxu in https://github.com/tidalcycles/strudel/pull/101
- Work on Codemirror 6 highlighting by @mindofmatthew in https://github.com/tidalcycles/strudel/pull/102
- Codemirror 6 by @felixroos in https://github.com/tidalcycles/strudel/pull/97
- Tune tests by @felixroos in https://github.com/tidalcycles/strudel/pull/104
- /embed package: web component for repl by @felixroos in https://github.com/tidalcycles/strudel/pull/106
- Reset, Restart and other composers by @felixroos in https://github.com/tidalcycles/strudel/pull/88
- Embed style by @felixroos in https://github.com/tidalcycles/strudel/pull/109
- In source doc by @yaxu in https://github.com/tidalcycles/strudel/pull/105
- `.brak()`, `.inside()` and `.outside()` by @yaxu in https://github.com/tidalcycles/strudel/pull/112
- loopAt by @yaxu in https://github.com/tidalcycles/strudel/pull/114
- Osc timing improvements by @yaxu in https://github.com/tidalcycles/strudel/pull/113
- react package + vite build by @felixroos in https://github.com/tidalcycles/strudel/pull/116
- In source doc by @felixroos in https://github.com/tidalcycles/strudel/pull/117
- fix: #108 by @felixroos in https://github.com/tidalcycles/strudel/pull/123
- fix: #122 ctrl enter would add newline by @felixroos in https://github.com/tidalcycles/strudel/pull/124
- Webdirt by @felixroos in https://github.com/tidalcycles/strudel/pull/121
- Fix link to contributing to tutorial docs by @stephendwolff in https://github.com/tidalcycles/strudel/pull/129
- Pianoroll enhancements by @felixroos in https://github.com/tidalcycles/strudel/pull/131
- add createParam + createParams by @felixroos in https://github.com/tidalcycles/strudel/pull/110
- remove cycle + delta from onTrigger by @felixroos in https://github.com/tidalcycles/strudel/pull/135
- Scheduler improvements by @felixroos in https://github.com/tidalcycles/strudel/pull/134
- add onTrigger helper by @felixroos in https://github.com/tidalcycles/strudel/pull/136
## New Contributors
- @bwagner made their first contribution in https://github.com/tidalcycles/strudel/pull/37
- @mindofmatthew made their first contribution in https://github.com/tidalcycles/strudel/pull/84
- @stephendwolff made their first contribution in https://github.com/tidalcycles/strudel/pull/129
**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.0.2...v0.0.3

View File

@ -0,0 +1,45 @@
---
title: 'Release Notes v0.0.4 Gischt'
description: ''
date: '2022-08-14'
tags: ['meta']
author: froos
---
## What's Changed
- Webaudio rewrite by @felixroos in https://github.com/tidalcycles/strudel/pull/138
- Fix createParam() by @yaxu in https://github.com/tidalcycles/strudel/pull/140
- Soundfont Support by @felixroos in https://github.com/tidalcycles/strudel/pull/139
- Serial twiddles by @yaxu in https://github.com/tidalcycles/strudel/pull/141
- Pianoroll Object Support by @felixroos in https://github.com/tidalcycles/strudel/pull/142
- flash effect on ctrl enter by @felixroos in https://github.com/tidalcycles/strudel/pull/144
- can now generate short link for sharing by @felixroos in https://github.com/tidalcycles/strudel/pull/146
- Sampler optimizations and more by @felixroos in https://github.com/tidalcycles/strudel/pull/148
- Final update to demo.pdf by @yaxu in https://github.com/tidalcycles/strudel/pull/151
- add webdirt drum samples to prebake for general availability by @larkob in https://github.com/tidalcycles/strudel/pull/150
- update to tutorial documentation by @larkob in https://github.com/tidalcycles/strudel/pull/162
- add chooseInWith/chooseCycles by @yaxu in https://github.com/tidalcycles/strudel/pull/166
- fix: jsdoc comments by @felixroos in https://github.com/tidalcycles/strudel/pull/169
- Pianoroll fixes by @felixroos in https://github.com/tidalcycles/strudel/pull/163
- Talk fixes by @felixroos in https://github.com/tidalcycles/strudel/pull/164
- Amend shapeshifter to allow use of dynamic import by @debrisapron in https://github.com/tidalcycles/strudel/pull/171
- add more shapeshifter flags by @felixroos in https://github.com/tidalcycles/strudel/pull/99
- Replace react-codemirror6 with @uiw/react-codemirror by @felixroos in https://github.com/tidalcycles/strudel/pull/173
- fix some annoying bugs by @felixroos in https://github.com/tidalcycles/strudel/pull/177
- incorporate elements of randomness to the mini notation by @bpow in https://github.com/tidalcycles/strudel/pull/165
- replace mocha with vitest by @felixroos in https://github.com/tidalcycles/strudel/pull/175
- scheduler improvements by @felixroos in https://github.com/tidalcycles/strudel/pull/181
- Fix codemirror bug by @felixroos in https://github.com/tidalcycles/strudel/pull/186
- wait for prebake to finish before evaluating by @felixroos in https://github.com/tidalcycles/strudel/pull/189
- fix regression: old way of setting frequencies was broken by @felixroos in https://github.com/tidalcycles/strudel/pull/190
- Soundfont file support by @felixroos in https://github.com/tidalcycles/strudel/pull/183
- change "stride"/"offset" of successive degradeBy/chooseIn by @bpow in https://github.com/tidalcycles/strudel/pull/185
## New Contributors
- @larkob made their first contribution in https://github.com/tidalcycles/strudel/pull/150
- @debrisapron made their first contribution in https://github.com/tidalcycles/strudel/pull/171
- @bpow made their first contribution in https://github.com/tidalcycles/strudel/pull/165
**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.0.3...v0.0.4

View File

@ -0,0 +1,65 @@
---
title: 'Release Notes v0.3.0 Donauwelle'
description: ''
date: '2022-11-06'
tags: ['meta']
author: froos
---
## Package Versions
- @strudel.cycles/core@0.3.1
- @strudel.cycles/eval@0.3.1
- @strudel.cycles/midi@0.3.1
- @strudel.cycles/mini@0.3.1
- @strudel.cycles/react@0.3.1
- @strudel.cycles/soundfonts@0.3.1
- @strudel.cycles/tonal@0.3.1
- @strudel.cycles/tone@0.3.1
- @strudel.cycles/webaudio@0.3.1
- @strudel.cycles/webdirt@0.3.1
- @strudel.cycles/xen@0.3.1
## What's Changed
- Fix numbers in sampler by @felixroos in https://github.com/tidalcycles/strudel/pull/196
- document random functions by @felixroos in https://github.com/tidalcycles/strudel/pull/199
- add rollup-plugin-visualizer to build by @felixroos in https://github.com/tidalcycles/strudel/pull/200
- add vowel to .out by @felixroos in https://github.com/tidalcycles/strudel/pull/201
- Coarse crush shape by @felixroos in https://github.com/tidalcycles/strudel/pull/205
- Webaudio guide by @felixroos in https://github.com/tidalcycles/strudel/pull/207
- Even more docs by @felixroos in https://github.com/tidalcycles/strudel/pull/212
- Just another docs PR by @felixroos in https://github.com/tidalcycles/strudel/pull/215
- sampler features + fixes by @felixroos in https://github.com/tidalcycles/strudel/pull/217
- samples now have envelopes by @felixroos in https://github.com/tidalcycles/strudel/pull/218
- encapsulate webaudio output by @felixroos in https://github.com/tidalcycles/strudel/pull/219
- Fix squeeze join by @yaxu in https://github.com/tidalcycles/strudel/pull/220
- Feedback Delay by @felixroos in https://github.com/tidalcycles/strudel/pull/213
- support negative speeds by @felixroos in https://github.com/tidalcycles/strudel/pull/222
- focus tweak for squeezeJoin - another go at fixing #216 by @yaxu in https://github.com/tidalcycles/strudel/pull/221
- Reverb by @felixroos in https://github.com/tidalcycles/strudel/pull/224
- fix fastgap for events that go across cycle boundaries by @yaxu in https://github.com/tidalcycles/strudel/pull/225
- Core util tests by @mystery-house in https://github.com/tidalcycles/strudel/pull/226
- Refactor tunes away from tone by @felixroos in https://github.com/tidalcycles/strudel/pull/230
- Just another docs branch by @felixroos in https://github.com/tidalcycles/strudel/pull/228
- Patternify range by @yaxu in https://github.com/tidalcycles/strudel/pull/231
- Out by default by @felixroos in https://github.com/tidalcycles/strudel/pull/232
- Fix zero length queries WIP by @yaxu in https://github.com/tidalcycles/strudel/pull/234
- add vcsl sample library by @felixroos in https://github.com/tidalcycles/strudel/pull/235
- fx on stereo speakers by @felixroos in https://github.com/tidalcycles/strudel/pull/236
- Tidal drum machines by @felixroos in https://github.com/tidalcycles/strudel/pull/237
- Object arithmetic by @felixroos in https://github.com/tidalcycles/strudel/pull/238
- Load samples from url by @felixroos in https://github.com/tidalcycles/strudel/pull/239
- feat: support github: links by @felixroos in https://github.com/tidalcycles/strudel/pull/240
- in source example tests by @felixroos in https://github.com/tidalcycles/strudel/pull/242
- Readme + TLC by @felixroos in https://github.com/tidalcycles/strudel/pull/244
- patchday by @felixroos in https://github.com/tidalcycles/strudel/pull/246
- Some tunes by @felixroos in https://github.com/tidalcycles/strudel/pull/247
- snapshot tests on shared snippets by @felixroos in https://github.com/tidalcycles/strudel/pull/243
- General purpose scheduler by @felixroos in https://github.com/tidalcycles/strudel/pull/248
## New Contributors
- @mystery-house made their first contribution in https://github.com/tidalcycles/strudel/pull/226
**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.0.4...v0.3.0

View File

@ -0,0 +1,15 @@
---
title: 'Release Notes v0.4.0 Brandung'
description: ''
date: '2022-11-13'
tags: ['meta']
author: froos
---
## What's Changed
- new transpiler based on acorn by @felixroos in https://github.com/tidalcycles/strudel/pull/249
- Webaudio build by @felixroos in https://github.com/tidalcycles/strudel/pull/250
- Repl refactoring by @felixroos in https://github.com/tidalcycles/strudel/pull/255
**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.3.0...v0.4.0

View File

@ -0,0 +1,59 @@
---
title: 'Release Notes v0.5.0 Wirbel'
description: ''
date: '2022-12-13'
tags: ['meta']
author: froos
---
## Package Versions
- @strudel.cycles/core@0.5.0
- @strudel.cycles/osc@0.4.0
- @strudel.cycles/serial@0.3.0
- @strudel.cycles/csound@0.5.1
- @strudel.cycles/eval@0.5.0
- @strudel.cycles/midi@0.5.0
- @strudel.cycles/mini@0.5.0
- @strudel.cycles/react@0.5.0
- @strudel.cycles/soundfonts@0.5.0
- @strudel.cycles/tonal@0.5.0
- @strudel.cycles/tone@0.5.0
- @strudel.cycles/transpiler@0.5.0
- @strudel.cycles/webaudio@0.5.0
- @strudel.cycles/webdirt@0.5.0
- @strudel.cycles/xen@0.5.0
## What's Changed
- Binaries by @felixroos in https://github.com/tidalcycles/strudel/pull/254
- fix tutorial bugs by @felixroos in https://github.com/tidalcycles/strudel/pull/263
- fix performance bottleneck by @felixroos in https://github.com/tidalcycles/strudel/pull/266
- Tidying up core by @yaxu in https://github.com/tidalcycles/strudel/pull/256
- tonal update with fixed memory leak by @felixroos in https://github.com/tidalcycles/strudel/pull/272
- add eslint by @felixroos in https://github.com/tidalcycles/strudel/pull/271
- release version bumps by @felixroos in https://github.com/tidalcycles/strudel/pull/273
- Support sending CRC16 bytes with serial messages by @yaxu in https://github.com/tidalcycles/strudel/pull/276
- add licenses / credits to all tunes + remove some by @felixroos in https://github.com/tidalcycles/strudel/pull/277
- add basic csound output by @felixroos in https://github.com/tidalcycles/strudel/pull/275
- do not recompile orc by @felixroos in https://github.com/tidalcycles/strudel/pull/278
- implement collect + arp function by @felixroos in https://github.com/tidalcycles/strudel/pull/281
- Switch 'operators' from .whatHow to .what.how by @yaxu in https://github.com/tidalcycles/strudel/pull/285
- Fancy hap show, include part in snapshots by @yaxu in https://github.com/tidalcycles/strudel/pull/291
- Reorganise pattern.mjs with a 'toplevel first' regime by @yaxu in https://github.com/tidalcycles/strudel/pull/286
- add prettier task by @felixroos in https://github.com/tidalcycles/strudel/pull/296
- Move stuff to new register function by @felixroos in https://github.com/tidalcycles/strudel/pull/295
- can now add bare numbers to numeral object props by @felixroos in https://github.com/tidalcycles/strudel/pull/287
- update vitest by @felixroos in https://github.com/tidalcycles/strudel/pull/297
- remove whitespace from highlighted region by @felixroos in https://github.com/tidalcycles/strudel/pull/298
- .defragmentHaps() for merging touching haps that share a whole and value by @yaxu in https://github.com/tidalcycles/strudel/pull/299
- fix whitespace trimming by @felixroos in https://github.com/tidalcycles/strudel/pull/300
- add freq support to sampler by @felixroos in https://github.com/tidalcycles/strudel/pull/301
- add lint + prettier check before test by @felixroos in https://github.com/tidalcycles/strudel/pull/305
- Updated csoundm to use the register facility . by @gogins in https://github.com/tidalcycles/strudel/pull/303
## New Contributors
- @gogins made their first contribution in https://github.com/tidalcycles/strudel/pull/303
**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.4.0...v0.5.0

View File

@ -0,0 +1,84 @@
---
title: 'Release Notes v0.6.0 Zimtschnecke'
description: ''
date: '2023-02-01'
tags: ['meta']
author: froos
---
## Package Versions
- @strudel.cycles/core@0.6.8
- @strudel.cycles/eval@0.6.2
- @strudel.cycles/transpiler@0.6.0
- @strudel.cycles/mini@0.6.0
- @strudel.cycles/tonal@0.6.0
- @strudel.cycles/tone@0.6.0
- @strudel.cycles/xen@0.6.0
- @strudel.cycles/webaudio@0.6.1
- @strudel.cycles/react@0.6.0
- @strudel.cycles/osc@0.6.0
- @strudel.cycles/midi@0.6.0
- @strudel.cycles/webdirt@0.6.0
- @strudel.cycles/serial@0.6.0
- @strudel.cycles/soundfonts@0.6.0
- @strudel.cycles/csound@0.6.0
## What's Changed
- support freq in pianoroll by @felixroos in https://github.com/tidalcycles/strudel/pull/308
- ICLC2023 paper WIP by @yaxu in https://github.com/tidalcycles/strudel/pull/306
- fix: copy share link to clipboard was broken for some browers by @felixroos in https://github.com/tidalcycles/strudel/pull/311
- Jsdoc component by @felixroos in https://github.com/tidalcycles/strudel/pull/312
- object support for .scale by @felixroos in https://github.com/tidalcycles/strudel/pull/307
- Astro build by @felixroos in https://github.com/tidalcycles/strudel/pull/315
- Reference tab sort by @felixroos in https://github.com/tidalcycles/strudel/pull/318
- tutorial updates by @jarmitage in https://github.com/tidalcycles/strudel/pull/320
- support notes without octave by @felixroos in https://github.com/tidalcycles/strudel/pull/323
- mini repl improvements by @felixroos in https://github.com/tidalcycles/strudel/pull/324
- fix: workaround Object.assign globalThis by @felixroos in https://github.com/tidalcycles/strudel/pull/326
- add examples route by @felixroos in https://github.com/tidalcycles/strudel/pull/327
- add my-patterns by @felixroos in https://github.com/tidalcycles/strudel/pull/328
- my-patterns build + deploy by @felixroos in https://github.com/tidalcycles/strudel/pull/329
- my-patterns: fix paths + update readme by @felixroos in https://github.com/tidalcycles/strudel/pull/330
- improve displaying 's' in pianoroll by @felixroos in https://github.com/tidalcycles/strudel/pull/331
- fix: can now multiply floats in mini notation by @felixroos in https://github.com/tidalcycles/strudel/pull/332
- Embed mode improvements by @felixroos in https://github.com/tidalcycles/strudel/pull/333
- testing + docs docs by @felixroos in https://github.com/tidalcycles/strudel/pull/334
- animate mvp by @felixroos in https://github.com/tidalcycles/strudel/pull/335
- Tidy parser, implement polymeters by @yaxu in https://github.com/tidalcycles/strudel/pull/336
- animation options by @felixroos in https://github.com/tidalcycles/strudel/pull/337
- move /my-patterns to /swatch by @yaxu in https://github.com/tidalcycles/strudel/pull/338
- more animate functions + mini repl fix by @felixroos in https://github.com/tidalcycles/strudel/pull/340
- Patternify euclid, fast, slow and polymeter step parameters in mininotation by @yaxu in https://github.com/tidalcycles/strudel/pull/341
- fixes #346 by @felixroos in https://github.com/tidalcycles/strudel/pull/347
- Fix prebake base path by @felixroos in https://github.com/tidalcycles/strudel/pull/345
- Fix Bjorklund by @yaxu in https://github.com/tidalcycles/strudel/pull/343
- docs: tidal comparison + add global fx + add missing sampler fx by @felixroos in https://github.com/tidalcycles/strudel/pull/356
- Fix .out(), renaming webaudio's out() to webaudio() by @yaxu in https://github.com/tidalcycles/strudel/pull/361
- Support for multiple mininotation operators by @yaxu in https://github.com/tidalcycles/strudel/pull/350
- doc structuring by @felixroos in https://github.com/tidalcycles/strudel/pull/360
- add https to url by @urswilke in https://github.com/tidalcycles/strudel/pull/364
- document more functions + change arp join by @felixroos in https://github.com/tidalcycles/strudel/pull/369
- improve new draw logic by @felixroos in https://github.com/tidalcycles/strudel/pull/372
- Draw fixes by @felixroos in https://github.com/tidalcycles/strudel/pull/377
- update my-patterns instructions by @felixroos in https://github.com/tidalcycles/strudel/pull/384
- docs: use note instead of n to mitigate confusion by @felixroos in https://github.com/tidalcycles/strudel/pull/385
- add run + test + docs by @felixroos in https://github.com/tidalcycles/strudel/pull/386
- Rename a to angle by @felixroos in https://github.com/tidalcycles/strudel/pull/387
- document csound by @felixroos in https://github.com/tidalcycles/strudel/pull/391
- Notes are not essential :) by @yaxu in https://github.com/tidalcycles/strudel/pull/393
- add ribbon + test + docs by @felixroos in https://github.com/tidalcycles/strudel/pull/388
- Add tidal-drum-patterns to examples by @urswilke in https://github.com/tidalcycles/strudel/pull/379
- add pattern methods hurry, press and pressBy by @yaxu in https://github.com/tidalcycles/strudel/pull/397
- proper builds + use pnpm workspaces by @felixroos in https://github.com/tidalcycles/strudel/pull/396
- fix: minirepl styles by @felixroos in https://github.com/tidalcycles/strudel/pull/398
- can now await initAudio + initAudioOnFirstClick by @felixroos in https://github.com/tidalcycles/strudel/pull/399
- release webaudio by @felixroos in https://github.com/tidalcycles/strudel/pull/400
## New Contributors
- @jarmitage made their first contribution in https://github.com/tidalcycles/strudel/pull/320
- @urswilke made their first contribution in https://github.com/tidalcycles/strudel/pull/364
**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.5.0...v0.6.0

View File

@ -0,0 +1,88 @@
---
title: 'Release Notes v0.7.0 Zimtschnecke'
description: ''
date: '2023-03-23'
tags: ['meta']
author: froos
---
## Package Versions
- @strudel.cycles/core@0.7.2
- @strudel.cycles/transpiler@0.7.1
- @strudel.cycles/mini@0.7.2
- @strudel.cycles/tonal@0.7.1
- @strudel.cycles/xen@0.7.1
- @strudel.cycles/tone@0.7.1
- @strudel.cycles/webaudio@0.7.1
- @strudel.cycles/react@0.7.1
- @strudel.cycles/osc@0.7.1
- @strudel.cycles/serial@0.7.1
- @strudel.cycles/midi@0.7.1
- @strudel.cycles/csound@0.7.1
## What's Changed
- pin @csound/browser to 6.18.3 + bump by @felixroos in https://github.com/tidalcycles/strudel/pull/403
- update csound + fix sound output by @felixroos in https://github.com/tidalcycles/strudel/pull/404
- fix: share url on subpath by @felixroos in https://github.com/tidalcycles/strudel/pull/405
- add shabda doc by @felixroos in https://github.com/tidalcycles/strudel/pull/407
- Update effects.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/410
- improve effects doc by @felixroos in https://github.com/tidalcycles/strudel/pull/409
- google gtfo by @felixroos in https://github.com/tidalcycles/strudel/pull/413
- improve samples doc by @felixroos in https://github.com/tidalcycles/strudel/pull/411
- PWA with offline support by @felixroos in https://github.com/tidalcycles/strudel/pull/417
- add caching strategy for missing file types + cache all samples loaded from github by @felixroos in https://github.com/tidalcycles/strudel/pull/419
- add more offline caching by @felixroos in https://github.com/tidalcycles/strudel/pull/421
- add cdn.freesound to cache list by @felixroos in https://github.com/tidalcycles/strudel/pull/425
- minirepl: add keyboard shortcuts by @felixroos in https://github.com/tidalcycles/strudel/pull/429
- Themes by @felixroos in https://github.com/tidalcycles/strudel/pull/431
- autocomplete preparations by @felixroos in https://github.com/tidalcycles/strudel/pull/427
- Fix anchors by @felixroos in https://github.com/tidalcycles/strudel/pull/433
- Update code.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/436
- Update mini-notation.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/437
- Update synths.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/438
- FIXES: Warning about jsxBracketSameLine deprecation by @bwagner in https://github.com/tidalcycles/strudel/pull/461
- Composable functions by @yaxu in https://github.com/tidalcycles/strudel/pull/390
- weave and weaveWith by @yaxu in https://github.com/tidalcycles/strudel/pull/465
- slice and splice by @yaxu in https://github.com/tidalcycles/strudel/pull/466
- fix: osc should not return a promise by @felixroos in https://github.com/tidalcycles/strudel/pull/472
- FIXES: freqs instead of pitches by @bwagner in https://github.com/tidalcycles/strudel/pull/464
- Update input-output.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/471
- settings tab with vim / emacs modes + additional themes and fonts by @felixroos in https://github.com/tidalcycles/strudel/pull/467
- fix: hash links by @felixroos in https://github.com/tidalcycles/strudel/pull/473
- midi cc support by @felixroos in https://github.com/tidalcycles/strudel/pull/478
- Fix array args by @felixroos in https://github.com/tidalcycles/strudel/pull/480
- docs: packages + offline by @felixroos in https://github.com/tidalcycles/strudel/pull/482
- Update mini-notation.mdx by @yaxu in https://github.com/tidalcycles/strudel/pull/365
- Revert "Another attempt at composable functions - WIP (#390)" by @felixroos in https://github.com/tidalcycles/strudel/pull/484
- fix app height by @felixroos in https://github.com/tidalcycles/strudel/pull/485
- add algolia creds + optimize sidebar for crawling by @felixroos in https://github.com/tidalcycles/strudel/pull/488
- refactor react package by @felixroos in https://github.com/tidalcycles/strudel/pull/490
- react style fixes by @felixroos in https://github.com/tidalcycles/strudel/pull/491
- implement cps in scheduler by @felixroos in https://github.com/tidalcycles/strudel/pull/493
- Add control aliases by @yaxu in https://github.com/tidalcycles/strudel/pull/497
- fix: nano-repl highlighting by @felixroos in https://github.com/tidalcycles/strudel/pull/501
- Reinstate slice and splice by @yaxu in https://github.com/tidalcycles/strudel/pull/500
- can now use : as a replacement for space in scales by @felixroos in https://github.com/tidalcycles/strudel/pull/502
- Support list syntax in mininotation by @yaxu in https://github.com/tidalcycles/strudel/pull/512
- update react to 18 by @felixroos in https://github.com/tidalcycles/strudel/pull/514
- add arrange function by @felixroos in https://github.com/tidalcycles/strudel/pull/508
- Update README.md by @bwagner in https://github.com/tidalcycles/strudel/pull/474
- add 2 illegible fonts by @felixroos in https://github.com/tidalcycles/strudel/pull/518
- registerSound API + improved sounds tab + regroup soundfonts by @felixroos in https://github.com/tidalcycles/strudel/pull/516
- fix: envelopes in chrome by @felixroos in https://github.com/tidalcycles/strudel/pull/521
- Update samples.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/524
- Update intro.mdx by @bwagner in https://github.com/tidalcycles/strudel/pull/525
- fix(footer): fix link to tidalcycles by @revolunet in https://github.com/tidalcycles/strudel/pull/529
- FIXES: alias pm for polymeter by @bwagner in https://github.com/tidalcycles/strudel/pull/527
- Maintain random seed state in parser, not globally by @ijc8 in https://github.com/tidalcycles/strudel/pull/531
- feat: add freq support to gm soundfonts by @felixroos in https://github.com/tidalcycles/strudel/pull/534
- Update lerna by @felixroos in https://github.com/tidalcycles/strudel/pull/535
## New Contributors
- @revolunet made their first contribution in https://github.com/tidalcycles/strudel/pull/529
- @ijc8 made their first contribution in https://github.com/tidalcycles/strudel/pull/531
**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.6.0...v0.7.0

View File

@ -0,0 +1,130 @@
---
title: 'Release Notes v0.8.0 Himbeermuffin'
description: ''
date: '2023-06-30'
tags: ['meta']
author: froos
---
import BlogVideo from '../../components/BlogVideo.astro';
These are the release notes for Strudel 0.8.0 aka "Himbeermuffin"!
[Go to Tidal Club Forum for this Release](https://club.tidalcycles.org/t/strudel-0-8-0-released/4769)
Let me write up some of the highlights:
## Desktop App
Besides the REPL (https://strudel.tidalcycles.org/), Strudel is now also distributed as a Desktop App via https://tauri.app/! Thanks to [vasilymilovidov](https://github.com/vasilymilovidov)!
- [Linux: Debian based](https://github.com/tidalcycles/strudel/releases/download/v0.8.0/strudel_0.1.0_amd64.deb)
- [Linux: AppImage](https://github.com/tidalcycles/strudel/releases/download/v0.8.0/strudel_0.1.0_amd64.AppImage)
- [MacOS](https://github.com/tidalcycles/strudel/releases/download/v0.8.0/Strudel_0.1.0_x64.dmg)
- [Windows .exe](https://github.com/tidalcycles/strudel/releases/download/v0.8.0/Strudel_0.1.0_x64-setup.exe)
- [Windows .msi](https://github.com/tidalcycles/strudel/releases/download/v0.8.0/Strudel_0.1.0_x64_en-US.msi)
edit: the desktop app performance on linux is currently not that great.. the web REPL runs much smoother (using firefox or chromium)
The desktop App has the same features as the webapp, with the additional ability to load samples from disk. It is currently not documented yet, but you can do something like
```js
await samples('~/music/xxx');
s('my_sound');
```
You have to start with `~/music/`, followed by an arbitrary folder path that is expected to be present in the systems [audio directory](https://tauri.app/v1/api/js/path/#audiodir).
When you first run it, the app will create a strudel.json file in that directory to map out the available samples.
I would be very happy to collect some feedback on how it works across different platforms & systems!
## Spiral Visualization
Also still undocumented, but you can now visualize patterns as a spiral via `.spiral()`:
<BlogVideo src="https://github.com/tidalcycles/strudel/assets/12023032/05bc2dba-b304-4298-9465-a1a6fafe5ded" />
This is especially nice because strudel is not only the name of a dessert but also the german word for vortex! The spiral is very fitting to visualize cycles because you can align cycles vertically, while surfing along an infinite twisted timeline.
## More settings
In the settings tab, you can now toggle:
- line numbers
- auto-complete
- line wrapping
Thanks to [roipoussiere](https://github.com/roipoussiere)!
## More
Scroll down to see the full list of Changes!
A big thanks to all the contributors!
## Package Versions
- @strudel.cycles/core: 0.8.2
- @strudel.cycles/mini: 0.8.2
- @strudel.cycles/transpiler: 0.8.2
- @strudel.cycles/webaudio: 0.8.2
- @strudel.cycles/soundfonts: 0.8.2
- @strudel.cycles/react: 0.8.0
- @strudel.cycles/midi: 0.8.0
- @strudel.cycles/osc: 0.8.0
- @strudel.cycles/csound: 0.8.0
- @strudel.cycles/serial: 0.8.0
- @strudel.cycles/tonal: 0.8.2
- @strudel.cycles/xen: 0.8.0
- @strudel/codemirror: 0.8.4
- @strudel/web: 0.8.3
## What's Changed
- fix period key for dvorak + remove duplicated code by @felixroos in https://github.com/tidalcycles/strudel/pull/537
- improve initial loading + wait before eval by @felixroos in https://github.com/tidalcycles/strudel/pull/538
- do not reset cps before eval #517 by @felixroos in https://github.com/tidalcycles/strudel/pull/539
- feat: add loader bar to animate loading state by @felixroos in https://github.com/tidalcycles/strudel/pull/542
- add firacode font by @felixroos in https://github.com/tidalcycles/strudel/pull/544
- fix: allow whitespace at the end of a mini pattern by @felixroos in https://github.com/tidalcycles/strudel/pull/547
- fix: reset time on stop by @felixroos in https://github.com/tidalcycles/strudel/pull/548
- fix: load soundfonts in prebake by @felixroos in https://github.com/tidalcycles/strudel/pull/550
- fix: colorable highlighting by @felixroos in https://github.com/tidalcycles/strudel/pull/553
- fix: make soundfonts import dynamic by @felixroos in https://github.com/tidalcycles/strudel/pull/556
- add basic triads and guidetone voicings by @felixroos in https://github.com/tidalcycles/strudel/pull/557
- Patchday by @felixroos in https://github.com/tidalcycles/strudel/pull/559
- Vanilla JS Refactoring by @felixroos in https://github.com/tidalcycles/strudel/pull/563
- repl: add option to display line numbers by @roipoussiere in https://github.com/tidalcycles/strudel/pull/582
- learn/tonal: fix typo in "scaleTran[s]pose" by @srenatus in https://github.com/tidalcycles/strudel/pull/585
- Music metadata by @roipoussiere in https://github.com/tidalcycles/strudel/pull/580
- New Workshop by @felixroos in https://github.com/tidalcycles/strudel/pull/587
- Fix option dot by @felixroos in https://github.com/tidalcycles/strudel/pull/596
- fix: allow f for flat notes like tidal by @felixroos in https://github.com/tidalcycles/strudel/pull/593
- fix: division by zero by @felixroos in https://github.com/tidalcycles/strudel/pull/591
- Solmization added by @dariacotocu in https://github.com/tidalcycles/strudel/pull/570
- improve cursor by @felixroos in https://github.com/tidalcycles/strudel/pull/597
- enable auto-completion by @roipoussiere in https://github.com/tidalcycles/strudel/pull/588
- add ratio function by @felixroos in https://github.com/tidalcycles/strudel/pull/602
- editor: enable line wrapping by @roipoussiere in https://github.com/tidalcycles/strudel/pull/581
- tonal fixes by @felixroos in https://github.com/tidalcycles/strudel/pull/607
- fix: flatten scale lists by @felixroos in https://github.com/tidalcycles/strudel/pull/605
- clip now works like legato in tidal by @felixroos in https://github.com/tidalcycles/strudel/pull/598
- fix: doc links by @felixroos in https://github.com/tidalcycles/strudel/pull/612
- tauri desktop app by @vasilymilovidov in https://github.com/tidalcycles/strudel/pull/613
- add spiral viz by @felixroos in https://github.com/tidalcycles/strudel/pull/614
- patterning ui settings by @felixroos in https://github.com/tidalcycles/strudel/pull/606
- Fix typo on packages.mdx by @paikwiki in https://github.com/tidalcycles/strudel/pull/520
- cps dependent functions by @felixroos in https://github.com/tidalcycles/strudel/pull/620
- desktop: play samples from disk by @felixroos in https://github.com/tidalcycles/strudel/pull/621
- fix: midi clock drift by @felixroos in https://github.com/tidalcycles/strudel/pull/627
## New Contributors
- @roipoussiere made their first contribution in https://github.com/tidalcycles/strudel/pull/582
- @srenatus made their first contribution in https://github.com/tidalcycles/strudel/pull/585
- @dariacotocu made their first contribution in https://github.com/tidalcycles/strudel/pull/570
- @vasilymilovidov made their first contribution in https://github.com/tidalcycles/strudel/pull/613
- @paikwiki made their first contribution in https://github.com/tidalcycles/strudel/pull/520
**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.7.0...v0.8.0

View File

@ -0,0 +1,114 @@
---
title: 'Release Notes v0.9.0 Bananenbrot'
description: ''
date: '2023-09-17'
tags: ['meta']
author: froos
---
import BlogVideo from '../../components/BlogVideo.astro';
These are the release notes for Strudel 0.9.0 aka "Bananenbrot"!
The last release was over 11 weeks ago, so a lot of things have happened!
Let me write up some of the highlights:
## Improved Synth Engine
The synth engine has gotten a lot of love + a name: [superdough](https://www.npmjs.com/package/superdough) encapsulates the web audio based synth / sampler / fx engine into a reusable package, which is already used by [Topos](https://topos.raphaelforment.fr/).
Main new features include:
- [filter envelopes](https://strudel.tidalcycles.org/learn/effects#filter-envelope)
- [FM Synthesis](https://strudel.tidalcycles.org/learn/synths#fm-synthesis)
- [looping samples](https://strudel.tidalcycles.org/learn/samples#loop), allowing [wavetable synthesis](https://strudel.tidalcycles.org/?I9myTNQoKKaP)
- [vibrato](https://strudel.tidalcycles.org/learn/synths#vibrato)
- an integration of [ZZFX](https://strudel.tidalcycles.org/learn/synths#zzfx)
<BlogVideo src="https://github.com/tidalcycles/strudel/assets/12023032/652e7042-f296-496b-95cd-b2a4987fe238" />
Related PRs:
- superdough: encapsulates web audio output by @felixroos in https://github.com/tidalcycles/strudel/pull/664
- basic fm by @felixroos in https://github.com/tidalcycles/strudel/pull/669
- Wave Selection and Global Envelope on the FM Synth Modulator by @Bubobubobubobubo in https://github.com/tidalcycles/strudel/pull/683
- control osc partial count with n by @felixroos in https://github.com/tidalcycles/strudel/pull/674
- ZZFX Synth support by @Bubobubobubobubo in https://github.com/tidalcycles/strudel/pull/684
- Adding filter envelopes and filter order selection by @Bubobubobubobubo in https://github.com/tidalcycles/strudel/pull/692
- Adding loop points and thus wavetable synthesis by @Bubobubobubobubo in https://github.com/tidalcycles/strudel/pull/698
- Adding vibrato to base oscillators by @Bubobubobubobubo in https://github.com/tidalcycles/strudel/pull/693
## Desktop App Improvements
Thanks to @daslyfe and @vasilymilovidov , the desktop app now has its own rust based MIDI and OSC integrations,
which do not depend on browser APIs!
You can see superdough, superdirt via OSC + hardware synths via MIDI all together playing in harmony in this [awesome video](https://www.youtube.com/watch?v=lxQgBeLQBgk). These are the related PRs:
- Create Midi Integration for Tauri Desktop app by @daslyfe in https://github.com/tidalcycles/strudel/pull/685
- add sleep timer + improve message iterating by @daslyfe in https://github.com/tidalcycles/strudel/pull/688
- fix MIDI CC messages by @vasilymilovidov in https://github.com/tidalcycles/strudel/pull/690
- Direct OSC Support in Tauri by @daslyfe in https://github.com/tidalcycles/strudel/pull/694
- Add logging from tauri by @daslyfe in https://github.com/tidalcycles/strudel/pull/697
- fix osc bundle timestamp glitches caused by drifting clock by @daslyfe in https://github.com/tidalcycles/strudel/pull/666
- Midi time fixes by @daslyfe in https://github.com/tidalcycles/strudel/pull/668
- [Bug Fix] Account for numeral notation when converting to midi by @daslyfe in https://github.com/tidalcycles/strudel/pull/656
- [Bug Fix] Midi: Don't treat note 0 as false by @daslyfe in https://github.com/tidalcycles/strudel/pull/657
## Visuals
- 2 new FFT based vizualisations have now landed: [scope and fscope](https://github.com/tidalcycles/strudel/pull/677) (featured in the video at the top).
- pianoroll has new options, see [PR](https://github.com/tidalcycles/strudel/pull/679)
Related PRs:
- Scope by @felixroos in https://github.com/tidalcycles/strudel/pull/677 ([demo](https://strudel.tidalcycles.org/?hXVQF-KxMI8p))
- Pianoroll improvements by @felixroos in https://github.com/tidalcycles/strudel/pull/679 ([demo](https://strudel.tidalcycles.org/?aPMKqXGVMgSM))
## Voicings
There is now a new way to play chord voicings + a huge selection of chord voicings available. Find out more in these PRs:
- stateless voicings + tonleiter lib by @felixroos in https://github.com/tidalcycles/strudel/pull/647 ([demo](https://strudel.tidalcycles.org/?FoILM0Hs9y9f))
- ireal voicings by @felixroos in https://github.com/tidalcycles/strudel/pull/653 ([demo](https://strudel.tidalcycles.org/?bv_TjY9hOC28))
## Adaptive Highlighting
Thanks to @mindofmatthew , the highlighting will adapt to edits instantly! Related PRs:
- More work on highlight IDs by @mindofmatthew in https://github.com/tidalcycles/strudel/pull/636
- Adaptive Highlighting by @felixroos in https://github.com/tidalcycles/strudel/pull/634
## UI Changes
- teletext theme + fonts by @felixroos in https://github.com/tidalcycles/strudel/pull/681 (featured in video at the top)
- togglable panel position by @felixroos in https://github.com/tidalcycles/strudel/pull/667
## Other New Features
- slice: list mode by @felixroos in https://github.com/tidalcycles/strudel/pull/645 ([demo](https://strudel.tidalcycles.org/?bAYIqz5NLjRr))
- add emoji support by @felixroos in https://github.com/tidalcycles/strudel/pull/680 ([demo](https://strudel.tidalcycles.org/?a6FgLz475gN9))
## Articles
- Understand pitch by @felixroos in https://github.com/tidalcycles/strudel/pull/652
## Other Fixes & Enhancements
- fix: out of range error by @felixroos in https://github.com/tidalcycles/strudel/pull/630
- fix: update canvas size on window resize by @felixroos in https://github.com/tidalcycles/strudel/pull/631
- FIXES: TODO in rotateChroma by @bwagner in https://github.com/tidalcycles/strudel/pull/650
- snapshot tests: sort haps by part by @felixroos in https://github.com/tidalcycles/strudel/pull/637
- Delete old packages by @felixroos in https://github.com/tidalcycles/strudel/pull/639
- update vitest by @felixroos in https://github.com/tidalcycles/strudel/pull/651
- fix: welcome message for latestCode by @felixroos in https://github.com/tidalcycles/strudel/pull/659
- fix: always run previous trigger by @felixroos in https://github.com/tidalcycles/strudel/pull/660
## New Contributors
- @daslyfe made their first contribution in https://github.com/tidalcycles/strudel/pull/656
- @Bubobubobubobubo made their first contribution in https://github.com/tidalcycles/strudel/pull/683
**Full Changelog**: https://github.com/tidalcycles/strudel/compare/v0.8.0...v0.9.0
A big thanks to all the contributors!

View File

@ -0,0 +1,24 @@
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
author: z.string(),
description: z.string().optional(),
// Transform string to Date object
date: z
.string()
.or(z.date())
.transform((val) => new Date(val)),
updatedDate: z
.string()
.optional()
.transform((str) => (str ? new Date(str) : undefined)),
image: z.string().optional(),
tags: z.array(z.string()).optional(),
draft: z.boolean().optional(),
}),
});
export const collections = { blog };

View File

@ -2,3 +2,5 @@
/// <reference types="astro/client" />
/// <reference types="vite-plugin-pwa/info" />
/// <reference types="vite-plugin-pwa/client" />
declare module 'date-fns';

View File

@ -0,0 +1,10 @@
import { getMetadata } from './metadata_parser';
export function getMyPatterns() {
const my = import.meta.glob('../../my-patterns/**', { as: 'raw', eager: true });
return Object.fromEntries(
Object.entries(my)
.filter(([name]) => name.endsWith('.txt'))
.map(([name, raw]) => [getMetadata(raw)['title'] || name.split('/').slice(-1), raw]),
);
}

View File

@ -0,0 +1,50 @@
---
import BlogPost from '../components/BlogPost.astro';
import HeadCommon from '../components/HeadCommon.astro';
import HeadSEO from '../components/HeadSEO.astro';
import Header from '../components/Header/Header.astro';
import LeftSidebar from '../components/LeftSidebar/LeftSidebar.astro';
import PageContent from '../components/PageContent/PageContent.astro';
import RightSidebar from '../components/RightSidebar/RightSidebar.astro';
import { getCollection } from 'astro:content';
import { compareDesc } from 'date-fns';
const currentPage = Astro.url.pathname;
const posts = (await getCollection('blog')).sort((a, b) => compareDesc(a.data.date, b.data.date));
---
<html dir={'ltr'} lang={'en'} class="initial dark">
<head>
<HeadCommon />
<!-- <HeadSEO frontmatter={frontmatter} canonicalUrl={canonicalURL} /> -->
<title>🌀 Strudel Blog</title>
</head>
<body class="h-app-height text-gray-50 bg-background">
<div class="w-full h-full space-y-4 flex flex-col">
<header class="max-w-full fixed top-0 w-full z-[100]">
<Header currentPage={currentPage} />
</header>
<main class="relative pt-16">
<div class="h-full top-0 overflow-auto min-w-[300px] flex xl:justify-center pr-4 pl-4 md:pl-[300px] xl:pl-0">
<aside title="Site Navigation" class="w-[300px] px-6 left-0 hidden md:block fixed h-full">
<LeftSidebar currentPage={currentPage} />
</aside>
<PageContent>
{posts.map((post) => <BlogPost post={post} />)}
</PageContent>
<aside class="fixed right-0 h-full overflow-auto pr-4 pl-0 pb-16 hidden xl:block" title="Table of Contents">
<RightSidebar
headings={posts.map((post) => ({
depth: 1,
slug: post.slug,
text: post.data.title,
}))}
/>
</aside>
</div>
</main>
</div>
</body>
</html>

View File

@ -15,7 +15,6 @@ import Box from '@components/Box.astro';
**low-pass filter**
<MiniRepl
hideHeader
client:visible
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
.sound("sawtooth").lpf(800)`}
@ -33,7 +32,6 @@ lpf = **l**ow **p**ass **f**ilter
**filter automatisieren**
<MiniRepl
hideHeader
client:visible
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
.sound("sawtooth").lpf("200 1000")`}
@ -51,7 +49,6 @@ Später sehen wir, wie man mit Wellenformen Dinge automatisieren kann.
**vowel = Vokal**
<MiniRepl
hideHeader
client:visible
tune={`note("<[c3,g3,e4] [bb2,f3,d4] [a2,f3,c4] [bb2,g3,eb4]>/2")
.sound("sawtooth").vowel("<a e i o>/2")`}
@ -60,7 +57,6 @@ Später sehen wir, wie man mit Wellenformen Dinge automatisieren kann.
**gain = Verstärkung**
<MiniRepl
hideHeader
client:visible
tune={`stack(
sound("hh*8").gain("[.25 1]*2"),
@ -83,7 +79,6 @@ Bei Rhythmen ist die Dynamik (= Veränderungen der Lautstärke) sehr wichtig.
Lass uns die obigen Beispiele kombinieren:
<MiniRepl
hideHeader
client:visible
tune={`stack(
stack(
@ -108,7 +103,6 @@ Die 3 Teile (Drums, Bass, Akkorde) sind genau wie vorher, nur in einem `stack`,
**Den Sound formen mit ADSR-Hüllkurve**
<MiniRepl
hideHeader
client:visible
tune={`note("<c3 bb2 f3 eb3>")
.sound("sawtooth").lpf(600)
@ -145,7 +139,6 @@ Kannst du erraten, was die einzelnen Werte machen?
**adsr-Kurznotation**
<MiniRepl
hideHeader
client:visible
tune={`note("<c3 bb2 f3 eb3>")
.sound("sawtooth").lpf(600)
@ -156,7 +149,6 @@ Kannst du erraten, was die einzelnen Werte machen?
**delay = Verzögerung**
<MiniRepl
hideHeader
client:visible
tune={`stack(
note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
@ -188,7 +180,6 @@ Was passiert, wenn du `.delay(".8:.06:.8")` schreibst? Kannst du erraten, was di
**room aka reverb = Hall**
<MiniRepl
hideHeader
client:visible
tune={`n("<4 [3@3 4] [<2 0> ~@16] ~>/2")
.scale("D4:minor").sound("gm_accordion:2")
@ -206,7 +197,6 @@ Füg auch ein Delay hinzu!
**kleiner Dub-Tune**
<MiniRepl
hideHeader
client:visible
tune={`stack(
note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
@ -221,7 +211,6 @@ Füg auch ein Delay hinzu!
Für echten Dub fehlt noch der Bass:
<MiniRepl
hideHeader
client:visible
tune={`stack(
note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
@ -245,7 +234,6 @@ Füg `.hush()` ans Ende eines Patterns im stack...
**pan = Panorama**
<MiniRepl
hideHeader
client:visible
tune={`sound("numbers:1 numbers:2 numbers:3 numbers:4")
.pan("0 0.3 .6 1")
@ -254,13 +242,13 @@ Füg `.hush()` ans Ende eines Patterns im stack...
**speed = Geschwindigkeit**
<MiniRepl hideHeader client:visible tune={`sound("bd rim").speed("<1 2 -1 -2>").room(.2)`} />
<MiniRepl client:visible tune={`sound("bd rim").speed("<1 2 -1 -2>").room(.2)`} />
**fast and slow = schnell und langsam**
Mit `fast` und `slow` kann man das Tempo eines Patterns außerhalb der Mini-Notation ändern:
<MiniRepl hideHeader client:visible tune={`sound("bd*2,~ rim").slow(2)`} />
<MiniRepl client:visible tune={`sound("bd*2,~ rim").slow(2)`} />
<Box>
@ -272,13 +260,13 @@ Was passiert, wenn du den Wert automatisierst? z.b. `.fast("<1 [2 4]>")` ?
Übrigens, innerhalb der Mini-Notation: `fast` ist `*` und `slow` ist `/`.
<MiniRepl hideHeader client:visible tune={`sound("[bd*2,~ rim]*<1 [2 4]>")`} />
<MiniRepl client:visible tune={`sound("[bd*2,~ rim]*<1 [2 4]>")`} />
## Automation mit Signalen
Anstatt Werte schrittweise zu automatisieren, können wir auch sogenannte Signale benutzen:
<MiniRepl hideHeader client:visible tune={`sound("hh*16").gain(sine)`} punchcard punchcardLabels={false} />
<MiniRepl client:visible tune={`sound("hh*16").gain(sine)`} punchcard punchcardLabels={false} />
<Box>
@ -294,7 +282,7 @@ Der `gain`-Wert (Verstärkung) wird in der Visualisierung als Transparenz darges
Signale bewegen sich standardmäßig zwischen 0 und 1. Wir können das mit `range` ändern:
<MiniRepl hideHeader client:visible tune={`sound("hh*8").lpf(saw.range(500, 2000))`} />
<MiniRepl client:visible tune={`sound("hh*8").lpf(saw.range(500, 2000))`} />
`range` ist nützlich wenn wir Funktionen mit einem anderen Wertebereich als 0 und 1 automatisieren wollen (z.b. `lpf`)
@ -307,7 +295,6 @@ Was passiert wenn du die beiden Werte vertauschst?
Wir können die Geschwindigkeit der Automation mit slow / fast ändern:
<MiniRepl
hideHeader
client:visible
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
.sound("sawtooth")
@ -322,15 +309,15 @@ Die ganze Automation braucht nun 8 cycle bis sie sich wiederholt.
## Rückblick
| Name | Beispiel |
| ----- | -------------------------------------------------------------------------------------------------- |
| lpf | <MiniRepl hideHeader client:visible tune={`note("c2 c3").s("sawtooth").lpf("<400 2000>")`} /> |
| vowel | <MiniRepl hideHeader client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
| gain | <MiniRepl hideHeader client:visible tune={`s("hh*8").gain("[.25 1]*2")`} /> |
| delay | <MiniRepl hideHeader client:visible tune={`s("bd rim").delay(.5)`} /> |
| room | <MiniRepl hideHeader client:visible tune={`s("bd rim").room(.5)`} /> |
| pan | <MiniRepl hideHeader client:visible tune={`s("bd rim").pan("0 1")`} /> |
| speed | <MiniRepl hideHeader client:visible tune={`s("bd rim").speed("<1 2 -1 -2>")`} /> |
| range | <MiniRepl hideHeader client:visible tune={`s("hh*16").lpf(saw.range(200,4000))`} /> |
| Name | Beispiel |
| ----- | --------------------------------------------------------------------------------------- |
| lpf | <MiniRepl client:visible tune={`note("c2 c3").s("sawtooth").lpf("<400 2000>")`} /> |
| vowel | <MiniRepl client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
| gain | <MiniRepl client:visible tune={`s("hh*8").gain("[.25 1]*2")`} /> |
| delay | <MiniRepl client:visible tune={`s("bd rim").delay(.5)`} /> |
| room | <MiniRepl client:visible tune={`s("bd rim").room(.5)`} /> |
| pan | <MiniRepl client:visible tune={`s("bd rim").pan("0 1")`} /> |
| speed | <MiniRepl client:visible tune={`s("bd rim").speed("<1 2 -1 -2>")`} /> |
| range | <MiniRepl client:visible tune={`s("hh*16").lpf(saw.range(200,4000))`} /> |
Lass uns nun die für Tidal typischen [Pattern-Effekte anschauen](/de/workshop/pattern-effects).

View File

@ -17,7 +17,6 @@ Jetzt schauen wir uns an wie man mit Tönen mit der `note` Funktion spielt.
**Töne mit Zahlen**
<MiniRepl
hideHeader
client:visible
tune={`note("48 52 55 59").sound("piano")`}
claviature
@ -39,7 +38,6 @@ Versuch auch mal Kommazahlen, z.B. 55.5 (beachte die englische Schreibweise von
**Töne mit Buchstaben**
<MiniRepl
hideHeader
client:visible
tune={`note("c e g b").sound("piano")`}
claviature
@ -57,7 +55,6 @@ Findest du Melodien die auch gleichzeitig ein Wort sind? Tipp: ☕ 🙈 🧚
**Vorzeichen**
<MiniRepl
hideHeader
client:visible
tune={`note("db eb gb ab bb").sound("piano")`}
claviature
@ -67,7 +64,6 @@ Findest du Melodien die auch gleichzeitig ein Wort sind? Tipp: ☕ 🙈 🧚
/>
<MiniRepl
hideHeader
client:visible
tune={`note("c# d# f# g# a#").sound("piano")`}
claviature
@ -79,7 +75,6 @@ Findest du Melodien die auch gleichzeitig ein Wort sind? Tipp: ☕ 🙈 🧚
**Andere Oktaven**
<MiniRepl
hideHeader
client:visible
tune={`note("c2 e3 g4 b5").sound("piano")`}
claviature
@ -105,7 +100,7 @@ Später sehen wir auch noch ein paar Tricks die es uns erleichtern Töne zu spie
Genau wie bei geräuschhaften Sounds können wir den Klang unserer Töne mit `sound` verändern:
<MiniRepl hideHeader client:visible tune={`note("36 43, 52 59 62 64").sound("piano")`} />
<MiniRepl client:visible tune={`note("36 43, 52 59 62 64").sound("piano")`} />
<Box>
@ -126,7 +121,6 @@ Probier ein paar sounds aus:
**Zwischen Sounds hin und her wechseln**
<MiniRepl
hideHeader
client:visible
tune={`note("48 67 63 [62, 58]")
.sound("piano gm_electric_guitar_muted")`}
@ -135,7 +129,6 @@ Probier ein paar sounds aus:
**Gleichzeitige Sounds**
<MiniRepl
hideHeader
client:visible
tune={`note("48 67 63 [62, 58]")
.sound("piano, gm_electric_guitar_muted")`}
@ -155,7 +148,7 @@ Wir schauen uns später noch mehr Möglichkeiten an wie man patterns kombiniert.
{/* [c2 bb1 f2 eb2] */}
<MiniRepl hideHeader client:visible tune={`note("[36 34 41 39]/4").sound("gm_acoustic_bass")`} punchcard />
<MiniRepl client:visible tune={`note("[36 34 41 39]/4").sound("gm_acoustic_bass")`} punchcard />
<Box>
@ -171,7 +164,7 @@ Wenn eine Sequenz unabhängig von ihrem Inhalt immer gleich schnell bleiben soll
**Eins pro Cycle per \< \>**
<MiniRepl hideHeader client:visible tune={`note("<36 34 41 39>").sound("gm_acoustic_bass")`} punchcard />
<MiniRepl client:visible tune={`note("<36 34 41 39>").sound("gm_acoustic_bass")`} punchcard />
<Box>
@ -190,7 +183,6 @@ usw..
**Eine Sequenz pro Cycle**
<MiniRepl
hideHeader
client:visible
tune={`note("<[36 48] [34 46] [41 53] [39 51]>")
.sound("gm_acoustic_bass")`}
@ -200,7 +192,6 @@ usw..
oder auch...
<MiniRepl
hideHeader
client:visible
tune={`note("<[36 48]*4 [34 46]*4 [41 53]*4 [39 51]*4>/2")
.sound("gm_acoustic_bass")`}
@ -212,7 +203,6 @@ oder auch...
Ähnlich wie Unter-Sequenzen, kann auch `<...>` innerhalb einer Sequenz verwendet werden:
<MiniRepl
hideHeader
client:visible
tune={`note("60 <63 62 65 63>")
.sound("gm_xylophone")`}
@ -222,7 +212,6 @@ oder auch...
Das ist auch praktisch für atonale Sounds:
<MiniRepl
hideHeader
client:visible
tune={`sound("bd*2, ~ <sd cp>, [~ hh]*2")
.bank("RolandTR909")`}
@ -235,7 +224,6 @@ Es kann mühsam sein die richtigen Noten zu finden wenn man alle zur Verfügung
Mit Skalen ist das einfacher:
<MiniRepl
hideHeader
client:visible
tune={`n("0 2 4 <[6,8] [7,9]>")
.scale("C:minor").sound("piano")`}
@ -262,7 +250,6 @@ Probier verschiedene Skalen:
Wie alle Funktionen können auch Skalen mit einem Pattern automatisiert werden:
<MiniRepl
hideHeader
client:visible
tune={`n("<0 -3>, 2 4 <[6,8] [7,9]>")
.scale("<C:major D:mixolydian>/4")
@ -283,7 +270,7 @@ Nimm dir Zeit um herauszufinden welche Skalen du magst.
**Verlängern mit @**
<MiniRepl hideHeader client:visible tune={`note("c@3 eb").sound("gm_acoustic_bass")`} punchcard />
<MiniRepl client:visible tune={`note("c@3 eb").sound("gm_acoustic_bass")`} punchcard />
<Box>
@ -296,7 +283,6 @@ Spiel mit der Länge!
**Unter-Sequenzen verlängern**
<MiniRepl
hideHeader
client:visible
tune={`n("<[4@2 4] [5@2 5] [6@2 6] [5@2 5]>*2")
.scale("<C2:mixolydian F2:mixolydian>/4")
@ -314,7 +300,7 @@ Das nennt man auch manchmal `triolen swing`. Es ist ein typischer Rhythmus im Bl
**Wiederholen**
<MiniRepl hideHeader client:visible tune={`note("c!2 [eb,<g a bb a>]").sound("piano")`} punchcard />
<MiniRepl client:visible tune={`note("c!2 [eb,<g a bb a>]").sound("piano")`} punchcard />
<Box>
@ -328,27 +314,26 @@ Was ist der Unterschied?
Das haben wir in diesem Kapitel gelernt:
| Concept | Syntax | Example |
| ------------ | ------ | ------------------------------------------------------------------- |
| Verlangsamen | \/ | <MiniRepl hideHeader client:visible tune={`note("[c a f e]/2")`} /> |
| Alternativen | \<\> | <MiniRepl hideHeader client:visible tune={`note("c <e g>")`} /> |
| Verlängern | @ | <MiniRepl hideHeader client:visible tune={`note("c@3 e")`} /> |
| Wiederholen | ! | <MiniRepl hideHeader client:visible tune={`note("c!3 e")`} /> |
| Concept | Syntax | Example |
| ------------ | ------ | -------------------------------------------------------- |
| Verlangsamen | \/ | <MiniRepl client:visible tune={`note("[c a f e]/2")`} /> |
| Alternativen | \<\> | <MiniRepl client:visible tune={`note("c <e g>")`} /> |
| Verlängern | @ | <MiniRepl client:visible tune={`note("c@3 e")`} /> |
| Wiederholen | ! | <MiniRepl client:visible tune={`note("c!3 e")`} /> |
Neue Funktionen:
| Name | Description | Example |
| ----- | --------------------------------------- | -------------------------------------------------------------------------------------------- |
| note | Tonhöhe als Buchstabe oder Zahl | <MiniRepl hideHeader client:visible tune={`note("b g e c").sound("piano")`} /> |
| scale | Interpretiert `n` als Skalenstufe | <MiniRepl hideHeader client:visible tune={`n("6 4 2 0").scale("C:minor").sound("piano")`} /> |
| stack | Spiele mehrere Patterns parallel (s.u.) | <MiniRepl hideHeader client:visible tune={`stack(s("bd sd"),note("c eb g"))`} /> |
| Name | Description | Example |
| ----- | --------------------------------------- | --------------------------------------------------------------------------------- |
| note | Tonhöhe als Buchstabe oder Zahl | <MiniRepl client:visible tune={`note("b g e c").sound("piano")`} /> |
| scale | Interpretiert `n` als Skalenstufe | <MiniRepl client:visible tune={`n("6 4 2 0").scale("C:minor").sound("piano")`} /> |
| stack | Spiele mehrere Patterns parallel (s.u.) | <MiniRepl client:visible tune={`stack(s("bd sd"),note("c eb g"))`} /> |
## Beispiele
**Bassline**
<MiniRepl
hideHeader
client:visible
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
.sound("gm_synth_bass_1")
@ -358,7 +343,6 @@ Neue Funktionen:
**Melodie**
<MiniRepl
hideHeader
client:visible
tune={`n(\`<
[~ 0] 2 [0 2] [~ 2]
@ -372,7 +356,6 @@ Neue Funktionen:
**Drums**
<MiniRepl
hideHeader
client:visible
tune={`sound("bd*2, ~ <sd cp>, [~ hh]*2")
.bank("RolandTR909")`}
@ -387,7 +370,6 @@ Das geht mit `stack` 😙
</Box>
<MiniRepl
hideHeader
client:visible
tune={`stack(
note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")

View File

@ -15,7 +15,7 @@ Dies ist das erste Kapitel im Strudel Workshop, schön dich an Bord zu haben!
Der Workshop ist voller interaktiver Textfelder. Lass uns lernen wie sie funktionieren. Hier ist eins:
<MiniRepl hideHeader client:visible tune={`sound("casio")`} />
<MiniRepl client:visible tune={`sound("casio")`} />
<Box>
@ -35,7 +35,7 @@ Glückwunsch, du kannst jetzt live coden!
Gerade haben wir schon den ersten sound mit `sound` abgespielt:
<MiniRepl hideHeader client:visible tune={`sound("casio")`} />
<MiniRepl client:visible tune={`sound("casio")`} />
<Box>
@ -57,7 +57,7 @@ Ein Sound kann mehrere Samples (Audio Dateien) enthalten.
Du kannst ein anderes Sample wählen, indem du `:` und eine Zahl an den Sound-Namen anhängst:
<MiniRepl hideHeader client:visible tune={`sound("casio:1")`} hideHeader />
<MiniRepl client:visible tune={`sound("casio:1")`} />
<Box>
@ -74,7 +74,7 @@ Vorerst bleiben wir bei den voreingestellten Sounds, später erfahren wir noch w
Strudel kommt von Haus aus mit einer breiten Auswahl an Drum Sounds:
<MiniRepl hideHeader client:visible tune={`sound("bd hh sd oh")`} hideHeader />
<MiniRepl client:visible tune={`sound("bd hh sd oh")`} />
<Box>
@ -92,7 +92,7 @@ Probier verschiedene Sounds aus!
Wir können den Charakter des Drum Sounds verändern, indem wir mit `bank` die Drum Machine auswählen:
<MiniRepl hideHeader client:visible tune={`sound("bd hh sd oh").bank("RolandTR909")`} hideHeader />
<MiniRepl client:visible tune={`sound("bd hh sd oh").bank("RolandTR909")`} />
In diesem Beispiel ist `RolandTR909` der Name der Drum Machine, die eine prägende Rolle für House und Techno Musik spielte.
@ -117,7 +117,7 @@ Dann kannst du ihn mit `Strg`+`C` kopieren und mit `Strg`+`V` einfügen.
Im letzten Beispiel haben wir schon gesehen dass man mehrere Sounds hintereinander abspielen kann wenn man sie durch Leerzeichen trennt:
<MiniRepl hideHeader client:visible tune={`sound("bd hh sd hh")`} punchcard />
<MiniRepl client:visible tune={`sound("bd hh sd hh")`} punchcard />
Beachte wie der aktuell gespielte Sound im Code markiert und auch darunter visualisiert wird.
@ -129,13 +129,13 @@ Versuch noch mehr Sounds hinzuzfügen!
**Je länger die Sequence, desto schneller**
<MiniRepl hideHeader client:visible tune={`sound("bd bd hh bd rim bd hh bd")`} punchcard />
<MiniRepl client:visible tune={`sound("bd bd hh bd rim bd hh bd")`} punchcard />
Der Inhalt einer Sequence wird in einen sogenannten Cycle (=Zyklus) zusammengequetscht.
**Tempo ändern mit `cpm`**
<MiniRepl hideHeader client:visible tune={`sound("bd bd hh bd rim bd hh bd").cpm(40)`} punchcard />
<MiniRepl client:visible tune={`sound("bd bd hh bd rim bd hh bd").cpm(40)`} punchcard />
<Box>
@ -151,7 +151,7 @@ Wir werden später noch mehr Möglichkeiten kennen lernen das Tempo zu veränder
**Pausen mit '~'**
<MiniRepl hideHeader client:visible tune={`sound("bd hh ~ rim")`} punchcard />
<MiniRepl client:visible tune={`sound("bd hh ~ rim")`} punchcard />
<Box>
@ -164,7 +164,7 @@ Tilde tippen:
**Unter-Sequenzen mit [Klammern]**
<MiniRepl hideHeader client:visible tune={`sound("bd [hh hh] rim [hh hh]")`} punchcard />
<MiniRepl client:visible tune={`sound("bd [hh hh] rim [hh hh]")`} punchcard />
<Box>
@ -178,11 +178,11 @@ Genau wie bei der ganzen Sequence wird eine Unter-Sequence schneller je mehr dri
**Multiplikation: Dinge schneller machen**
<MiniRepl hideHeader client:visible tune={`sound("bd hh*2 sd hh*3")`} punchcard />
<MiniRepl client:visible tune={`sound("bd hh*2 sd hh*3")`} punchcard />
**Multiplikation: Vieeeeeeel schneller**
<MiniRepl hideHeader client:visible tune={`sound("bd hh*16 sd hh*8")`} punchcard />
<MiniRepl client:visible tune={`sound("bd hh*16 sd hh*8")`} punchcard />
<Box>
@ -192,12 +192,11 @@ Tonhöhe = sehr schneller Rhythmus
**Multiplikation: Ganze Unter-Sequences schneller machen**
<MiniRepl hideHeader client:visible tune={`sound("bd [sd hh]*2")`} punchcard />
<MiniRepl client:visible tune={`sound("bd [sd hh]*2")`} punchcard />
Bolero:
<MiniRepl
hideHeader
client:visible
tune={`sound("sd sd*3 sd sd*3 sd sd sd sd*3 sd sd*3 sd*3 sd*3")
.cpm(10)`}
@ -206,7 +205,7 @@ Bolero:
**Unter-Unter-Sequenzen mit [[Klammern]]**
<MiniRepl hideHeader client:visible tune={`sound("bd [[rim rim] hh]")`} punchcard />
<MiniRepl client:visible tune={`sound("bd [[rim rim] hh]")`} punchcard />
<Box>
@ -216,15 +215,15 @@ Du kannst so tief verschachteln wie du willst!
**Parallele Sequenzen mit Komma**
<MiniRepl hideHeader client:visible tune={`sound("hh hh hh, bd casio")`} punchcard />
<MiniRepl client:visible tune={`sound("hh hh hh, bd casio")`} punchcard />
Du kannst so viele Kommas benutzen wie du möchtest:
<MiniRepl hideHeader client:visible tune={`sound("hh hh hh, bd bd, ~ casio")`} punchcard />
<MiniRepl client:visible tune={`sound("hh hh hh, bd bd, ~ casio")`} punchcard />
Kommas können auch in Unter-Sequenzen verwendet werden:
<MiniRepl hideHeader client:visible tune={`sound("hh hh hh, bd [bd,casio]")`} punchcard />
<MiniRepl client:visible tune={`sound("hh hh hh, bd [bd,casio]")`} punchcard />
<Box>
@ -237,7 +236,6 @@ Es kommt öfter vor, dass man die gleiche Idee auf verschiedene Arten ausdrücke
**Mehrere Zeilen schreiben mit \` (backtick)**
<MiniRepl
hideHeader
client:visible
tune={`sound(\`bd*2, ~ cp,
~ ~ ~ oh, hh*4,
@ -255,11 +253,11 @@ Ob man " oder \` benutzt ist nur eine Frage der Übersichtlichkeit.
Benutzt man nur einen Sound mit unterschiedlichen Sample Nummer sieht das so aus:
<MiniRepl hideHeader client:visible tune={`sound("jazz:0 jazz:1 [jazz:4 jazz:2] jazz:3*2")`} punchcard />
<MiniRepl client:visible tune={`sound("jazz:0 jazz:1 [jazz:4 jazz:2] jazz:3*2")`} punchcard />
Das gleiche kann man auch so schreiben:
<MiniRepl hideHeader client:visible tune={`n("0 1 [4 2] 3*2").sound("jazz")`} punchcard />
<MiniRepl client:visible tune={`n("0 1 [4 2] 3*2").sound("jazz")`} punchcard />
## Rückblick
@ -267,35 +265,35 @@ Wir haben jetzt die Grundlagen der sogenannten Mini-Notation gelernt, der Rhythm
Das haben wir bisher gelernt:
| Concept | Syntax | Example |
| --------------------- | ----------- | -------------------------------------------------------------------------------- |
| Sequenz | Leerzeichen | <MiniRepl hideHeader client:visible tune={`sound("bd bd sd hh")`} /> |
| Sound Nummer | :x | <MiniRepl hideHeader client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
| Pausen | ~ | <MiniRepl hideHeader client:visible tune={`sound("metal ~ jazz jazz:1")`} /> |
| Unter-Sequenzen | \[\] | <MiniRepl hideHeader client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
| Unter-Unter-Sequenzen | \[\[\]\] | <MiniRepl hideHeader client:visible tune={`sound("bd [metal [jazz sd]]")`} /> |
| Schneller | \* | <MiniRepl hideHeader client:visible tune={`sound("bd sd*2 cp*3")`} /> |
| Parallel | , | <MiniRepl hideHeader client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
| Concept | Syntax | Example |
| --------------------- | ----------- | --------------------------------------------------------------------- |
| Sequenz | Leerzeichen | <MiniRepl client:visible tune={`sound("bd bd sd hh")`} /> |
| Sound Nummer | :x | <MiniRepl client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
| Pausen | ~ | <MiniRepl client:visible tune={`sound("metal ~ jazz jazz:1")`} /> |
| Unter-Sequenzen | \[\] | <MiniRepl client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
| Unter-Unter-Sequenzen | \[\[\]\] | <MiniRepl client:visible tune={`sound("bd [metal [jazz sd]]")`} /> |
| Schneller | \* | <MiniRepl client:visible tune={`sound("bd sd*2 cp*3")`} /> |
| Parallel | , | <MiniRepl client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
Die mit Apostrophen umgebene Mini-Notation benutzt man normalerweise in einer sogenannten Funktion.
Die folgenden Funktionen haben wir bereits gesehen:
| Name | Description | Example |
| ----- | -------------------------------------- | ---------------------------------------------------------------------------------- |
| sound | Spielt den Sound mit dem Namen | <MiniRepl hideHeader client:visible tune={`sound("bd sd")`} /> |
| bank | Wählt die Soundbank / Drum Machine | <MiniRepl hideHeader client:visible tune={`sound("bd sd").bank("RolandTR909")`} /> |
| cpm | Tempo in **C**ycles **p**ro **M**inute | <MiniRepl hideHeader client:visible tune={`sound("bd sd").cpm(90)`} /> |
| n | Sample Nummer | <MiniRepl hideHeader client:visible tune={`n("0 1 4 2").sound("jazz")`} /> |
| Name | Description | Example |
| ----- | -------------------------------------- | ----------------------------------------------------------------------- |
| sound | Spielt den Sound mit dem Namen | <MiniRepl client:visible tune={`sound("bd sd")`} /> |
| bank | Wählt die Soundbank / Drum Machine | <MiniRepl client:visible tune={`sound("bd sd").bank("RolandTR909")`} /> |
| cpm | Tempo in **C**ycles **p**ro **M**inute | <MiniRepl client:visible tune={`sound("bd sd").cpm(90)`} /> |
| n | Sample Nummer | <MiniRepl client:visible tune={`n("0 1 4 2").sound("jazz")`} /> |
## Beispiele
**Einfacher Rock Beat**
<MiniRepl hideHeader client:visible tune={`sound("bd sd, hh*4").bank("RolandTR505").cpm(100/2)`} punchcard />
<MiniRepl client:visible tune={`sound("bd sd, hh*4").bank("RolandTR505").cpm(100/2)`} punchcard />
**Klassischer House**
<MiniRepl hideHeader client:visible tune={`sound("bd*2, ~ cp, [~ hh]*2").bank("RolandTR909")`} punchcard />
<MiniRepl client:visible tune={`sound("bd*2, ~ cp, [~ hh]*2").bank("RolandTR909")`} punchcard />
<Box>
@ -306,12 +304,11 @@ Bestimmte Drum Patterns werden oft genreübergreifend wiederverwendet.
We Will Rock you
<MiniRepl hideHeader client:visible tune={`sound("bd*2 cp").bank("RolandTR707").cpm(81/2)`} punchcard />
<MiniRepl client:visible tune={`sound("bd*2 cp").bank("RolandTR707").cpm(81/2)`} punchcard />
**Yellow Magic Orchestra - Firecracker**
<MiniRepl
hideHeader
client:visible
tune={`sound("bd sd, ~ ~ ~ hh ~ hh ~ ~, ~ perc ~ perc:1*2")
.bank("RolandCompurhythm1000")`}
@ -321,7 +318,6 @@ We Will Rock you
**Nachahmung eines 16 step sequencers**
<MiniRepl
hideHeader
client:visible
tune={`sound(\`
[~ ~ oh ~ ] [~ ~ ~ ~ ] [~ ~ ~ ~ ] [~ ~ ~ ~ ],
@ -335,7 +331,6 @@ We Will Rock you
**Noch eins**
<MiniRepl
hideHeader
client:visible
tune={`sound(\`
[~ ~ ~ ~ ] [~ ~ ~ ~ ] [~ ~ ~ ~ ] [~ ~ oh:1 ~ ],
@ -349,7 +344,6 @@ We Will Rock you
**Nicht so typische Drums**
<MiniRepl
hideHeader
client:visible
tune={`s(\`jazz*2,
insect [crow metal] ~ ~,

View File

@ -15,16 +15,15 @@ In diesem Kapitel beschäftigen wir uns mit Funktionen die weniger herkömmlich
**rev = rückwärts abspielen**
<MiniRepl hideHeader client:visible tune={`n("0 1 [4 3] 2").sound("jazz").rev()`} />
<MiniRepl client:visible tune={`n("0 1 [4 3] 2").sound("jazz").rev()`} />
**jux = einen stereo kanal modifizieren**
<MiniRepl hideHeader client:visible tune={`n("0 1 [4 3] 2").sound("jazz").jux(rev)`} />
<MiniRepl client:visible tune={`n("0 1 [4 3] 2").sound("jazz").jux(rev)`} />
So würde man das ohne `jux` schreiben:
<MiniRepl
hideHeader
client:visible
tune={`stack(
n("0 1 [4 3] 2").sound("jazz").pan(0),
@ -35,7 +34,6 @@ So würde man das ohne `jux` schreiben:
Lass uns visualisieren, was hier passiert:
<MiniRepl
hideHeader
client:visible
tune={`stack(
n("0 1 [4 3] 2").sound("jazz").pan(0).color("cyan"),
@ -52,12 +50,11 @@ Schreibe `//` vor eine der beiden Zeilen im `stack`!
**mehrere tempos**
<MiniRepl hideHeader client:visible tune={`note("c2, eb3 g3 [bb3 c4]").sound("piano").slow("1,2,3")`} />
<MiniRepl client:visible tune={`note("c2, eb3 g3 [bb3 c4]").sound("piano").slow("1,2,3")`} />
Das hat den gleichen Effekt, wie:
<MiniRepl
hideHeader
client:visible
tune={`stack(
note("c2, eb3 g3 [bb3 c4]").s("piano").slow(1).color('cyan'),
@ -76,7 +73,6 @@ Schreibe wieder `//` vor eine oder mehrere Zeilen im `stack`.
**add = addieren**
<MiniRepl
hideHeader
client:visible
tune={`note("c2 [eb3,g3]".add("<0 <1 -1>>"))
.color("<cyan <magenta yellow>>").adsr("[.1 0]:.2:[1 0]")
@ -95,7 +91,6 @@ z.B. c4 = 60, also ist c4 + 2 = 62
Man kann auch mehrmals addieren:
<MiniRepl
hideHeader
client:visible
tune={`note("c2 [eb3,g3]".add("<0 <1 -1>>").add("0,7"))
.color("<cyan <magenta yellow>>").adsr("[.1 0]:.2:[1 0]")
@ -106,7 +101,6 @@ Man kann auch mehrmals addieren:
**add + scale**
<MiniRepl
hideHeader
client:visible
tune={`n("<0 [2 4] <3 5> [~ <4 1>]>*2".add("<0 [0,2,4]>/4"))
.scale("C5:minor").release(.5)
@ -117,7 +111,6 @@ Man kann auch mehrmals addieren:
**Alles zusammen**
<MiniRepl
hideHeader
client:visible
tune={`stack(
n("<0 [2 4] <3 5> [~ <4 1>]>*2".add("<0 [0,2,4]>/4"))
@ -134,11 +127,11 @@ Man kann auch mehrmals addieren:
**ply**
<MiniRepl hideHeader client:visible tune={`sound("hh, bd rim").bank("RolandTR707").ply(2)`} punchcard />
<MiniRepl client:visible tune={`sound("hh, bd rim").bank("RolandTR707").ply(2)`} punchcard />
das ist wie:
<MiniRepl hideHeader client:visible tune={`sound("hh*2, bd*2 rim*2").bank("RolandTR707")`} punchcard />
<MiniRepl client:visible tune={`sound("hh*2, bd*2 rim*2").bank("RolandTR707")`} punchcard />
<Box>
@ -149,7 +142,6 @@ Probier `ply` mit einem pattern zu automatisieren, z.b. `"<1 2 1 3>"`
**off**
<MiniRepl
hideHeader
client:visible
tune={`n("<0 [4 <3 2>] <2 3> [~ 1]>"
.off(1/8, x=>x.add(4))
@ -168,16 +160,15 @@ In der Notation `x=>x.`, ist `x` das Pattern, das wir bearbeiten.
`off` ist auch nützlich für Sounds:
<MiniRepl
hideHeader
client:visible
tune={`s("bd sd,[~ hh]*2").bank("CasioRZ1")
.off(1/8, x=>x.speed(1.5).gain(.25))`}
/>
| Name | Beschreibung | Beispiel |
| ---- | --------------------------------- | ---------------------------------------------------------------------------------------------- |
| rev | rückwärts | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6").scale("C:minor").rev()`} /> |
| jux | einen Stereo-Kanal modifizieren | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
| add | addiert Zahlen oder Noten | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
| ply | multipliziert jedes Element x mal | <MiniRepl hideHeader client:visible tune={`s("bd sd").ply("<1 2 3>")`} /> |
| off | verzögert eine modifizierte Kopie | <MiniRepl hideHeader client:visible tune={`s("bd sd, hh*4").off(1/8, x=>x.speed(2))`} /> |
| Name | Beschreibung | Beispiel |
| ---- | --------------------------------- | ----------------------------------------------------------------------------------- |
| rev | rückwärts | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").rev()`} /> |
| jux | einen Stereo-Kanal modifizieren | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
| add | addiert Zahlen oder Noten | <MiniRepl client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
| ply | multipliziert jedes Element x mal | <MiniRepl client:visible tune={`s("bd sd").ply("<1 2 3>")`} /> |
| off | verzögert eine modifizierte Kopie | <MiniRepl client:visible tune={`s("bd sd, hh*4").off(1/8, x=>x.speed(2))`} /> |

View File

@ -11,58 +11,58 @@ Diese Seite ist eine Auflistung aller im Workshop vorgestellten Funktionen.
## Mini Notation
| Konzept | Syntax | Beispiel |
| --------------------- | -------- | -------------------------------------------------------------------------------- |
| Sequenz | space | <MiniRepl hideHeader client:visible tune={`sound("bd bd sn hh")`} /> |
| Sample-Nummer | :x | <MiniRepl hideHeader client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
| Pausen | ~ | <MiniRepl hideHeader client:visible tune={`sound("metal ~ jazz jazz:1")`} /> |
| Unter-Sequenzen | \[\] | <MiniRepl hideHeader client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
| Unter-Unter-Sequenzen | \[\[\]\] | <MiniRepl hideHeader client:visible tune={`sound("bd [metal [jazz sn]]")`} /> |
| Schneller | \* | <MiniRepl hideHeader client:visible tune={`sound("bd sn*2 cp*3")`} /> |
| Verlangsamen | \/ | <MiniRepl hideHeader client:visible tune={`note("[c a f e]/2")`} /> |
| Parallel | , | <MiniRepl hideHeader client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
| Alternieren | \<\> | <MiniRepl hideHeader client:visible tune={`note("c <e g>")`} /> |
| Verlängern | @ | <MiniRepl hideHeader client:visible tune={`note("c@3 e")`} /> |
| Wiederholen | ! | <MiniRepl hideHeader client:visible tune={`note("c!3 e")`} /> |
| Konzept | Syntax | Beispiel |
| --------------------- | -------- | --------------------------------------------------------------------- |
| Sequenz | space | <MiniRepl client:visible tune={`sound("bd bd sn hh")`} /> |
| Sample-Nummer | :x | <MiniRepl client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
| Pausen | ~ | <MiniRepl client:visible tune={`sound("metal ~ jazz jazz:1")`} /> |
| Unter-Sequenzen | \[\] | <MiniRepl client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
| Unter-Unter-Sequenzen | \[\[\]\] | <MiniRepl client:visible tune={`sound("bd [metal [jazz sn]]")`} /> |
| Schneller | \* | <MiniRepl client:visible tune={`sound("bd sn*2 cp*3")`} /> |
| Verlangsamen | \/ | <MiniRepl client:visible tune={`note("[c a f e]/2")`} /> |
| Parallel | , | <MiniRepl client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
| Alternieren | \<\> | <MiniRepl client:visible tune={`note("c <e g>")`} /> |
| Verlängern | @ | <MiniRepl client:visible tune={`note("c@3 e")`} /> |
| Wiederholen | ! | <MiniRepl client:visible tune={`note("c!3 e")`} /> |
## Sounds
| Name | Beschreibung | Beispiel |
| ----- | -------------------------- | ---------------------------------------------------------------------------------- |
| sound | spielt den Sound mit Namen | <MiniRepl hideHeader client:visible tune={`sound("bd sd")`} /> |
| bank | wählt die Soundbank | <MiniRepl hideHeader client:visible tune={`sound("bd sd").bank("RolandTR909")`} /> |
| n | wählt Sample mit Nummer | <MiniRepl hideHeader client:visible tune={`n("0 1 4 2").sound("jazz")`} /> |
| Name | Beschreibung | Beispiel |
| ----- | -------------------------- | ----------------------------------------------------------------------- |
| sound | spielt den Sound mit Namen | <MiniRepl client:visible tune={`sound("bd sd")`} /> |
| bank | wählt die Soundbank | <MiniRepl client:visible tune={`sound("bd sd").bank("RolandTR909")`} /> |
| n | wählt Sample mit Nummer | <MiniRepl client:visible tune={`n("0 1 4 2").sound("jazz")`} /> |
## Noten
| Name | Beschreibung | Beispiel |
| --------- | ---------------------------------- | -------------------------------------------------------------------------------------------- |
| note | wählt Note per Zahl oder Buchstabe | <MiniRepl hideHeader client:visible tune={`note("b g e c").sound("piano")`} /> |
| n + scale | wählt Note n in Skala | <MiniRepl hideHeader client:visible tune={`n("6 4 2 0").scale("C:minor").sound("piano")`} /> |
| stack | spielt mehrere Patterns parallel | <MiniRepl hideHeader client:visible tune={`stack(s("bd sd"),note("c eb g"))`} /> |
| Name | Beschreibung | Beispiel |
| --------- | ---------------------------------- | --------------------------------------------------------------------------------- |
| note | wählt Note per Zahl oder Buchstabe | <MiniRepl client:visible tune={`note("b g e c").sound("piano")`} /> |
| n + scale | wählt Note n in Skala | <MiniRepl client:visible tune={`n("6 4 2 0").scale("C:minor").sound("piano")`} /> |
| stack | spielt mehrere Patterns parallel | <MiniRepl client:visible tune={`stack(s("bd sd"),note("c eb g"))`} /> |
## Audio-Effekte
| Name | Beispiele |
| ----- | -------------------------------------------------------------------------------------------------- |
| lpf | <MiniRepl hideHeader client:visible tune={`note("c2 c3").s("sawtooth").lpf("<400 2000>")`} /> |
| vowel | <MiniRepl hideHeader client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
| gain | <MiniRepl hideHeader client:visible tune={`s("hh*8").gain("[.25 1]*2")`} /> |
| delay | <MiniRepl hideHeader client:visible tune={`s("bd rim").delay(.5)`} /> |
| room | <MiniRepl hideHeader client:visible tune={`s("bd rim").room(.5)`} /> |
| pan | <MiniRepl hideHeader client:visible tune={`s("bd rim").pan("0 1")`} /> |
| speed | <MiniRepl hideHeader client:visible tune={`s("bd rim").speed("<1 2 -1 -2>")`} /> |
| range | <MiniRepl hideHeader client:visible tune={`s("hh*16").lpf(saw.range(200,4000))`} /> |
| Name | Beispiele |
| ----- | --------------------------------------------------------------------------------------- |
| lpf | <MiniRepl client:visible tune={`note("c2 c3").s("sawtooth").lpf("<400 2000>")`} /> |
| vowel | <MiniRepl client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
| gain | <MiniRepl client:visible tune={`s("hh*8").gain("[.25 1]*2")`} /> |
| delay | <MiniRepl client:visible tune={`s("bd rim").delay(.5)`} /> |
| room | <MiniRepl client:visible tune={`s("bd rim").room(.5)`} /> |
| pan | <MiniRepl client:visible tune={`s("bd rim").pan("0 1")`} /> |
| speed | <MiniRepl client:visible tune={`s("bd rim").speed("<1 2 -1 -2>")`} /> |
| range | <MiniRepl client:visible tune={`s("hh*16").lpf(saw.range(200,4000))`} /> |
## Pattern-Effekte
| Name | Beschreibung | Beispiel |
| ---- | --------------------------------- | ---------------------------------------------------------------------------------------------- |
| cpm | Tempo in Cycles pro Minute | <MiniRepl hideHeader client:visible tune={`sound("bd sd").cpm(90)`} /> |
| fast | schneller | <MiniRepl hideHeader client:visible tune={`sound("bd sd").fast(2)`} /> |
| slow | langsamer | <MiniRepl hideHeader client:visible tune={`sound("bd sd").slow(2)`} /> |
| rev | rückwärts | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6").scale("C:minor").rev()`} /> |
| jux | einen Stereo-Kanal modifizieren | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
| add | addiert Zahlen oder Noten | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
| ply | jedes Element schneller machen | <MiniRepl hideHeader client:visible tune={`s("bd sd").ply("<1 2 3>")`} /> |
| off | verzögert eine modifizierte Kopie | <MiniRepl hideHeader client:visible tune={`s("bd sd, hh*4").off(1/8, x=>x.speed(2))`} /> |
| Name | Beschreibung | Beispiel |
| ---- | --------------------------------- | ----------------------------------------------------------------------------------- |
| cpm | Tempo in Cycles pro Minute | <MiniRepl client:visible tune={`sound("bd sd").cpm(90)`} /> |
| fast | schneller | <MiniRepl client:visible tune={`sound("bd sd").fast(2)`} /> |
| slow | langsamer | <MiniRepl client:visible tune={`sound("bd sd").slow(2)`} /> |
| rev | rückwärts | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").rev()`} /> |
| jux | einen Stereo-Kanal modifizieren | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
| add | addiert Zahlen oder Noten | <MiniRepl client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
| ply | jedes Element schneller machen | <MiniRepl client:visible tune={`s("bd sd").ply("<1 2 3>")`} /> |
| off | verzögert eine modifizierte Kopie | <MiniRepl client:visible tune={`s("bd sd, hh*4").off(1/8, x=>x.speed(2))`} /> |

View File

@ -2,7 +2,7 @@
import * as tunes from '../../../src/repl/tunes.mjs';
import HeadCommon from '../../components/HeadCommon.astro';
import { getMetadata } from '../metadata_parser';
import { getMetadata } from '../../metadata_parser';
const { BASE_URL } = import.meta.env;
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
@ -25,3 +25,4 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
}
</div>
</body>
../../metadata_parser

View File

@ -138,6 +138,60 @@ There is one filter envelope for each filter type and thus one set of envelope f
<JsDoc client:idle name="lpenv" h={0} />
# Pitch Envelope
You can also control the pitch with envelopes!
Pitch envelopes can breathe life into static sounds:
<MiniRepl
client:idle
tune={`n("<-4,0 5 2 1>*<2!3 4>")
.scale("<C F>/8:pentatonic")
.s("gm_electric_guitar_jazz")
.penv("<.5 0 7 -2>*2").vib("4:.1")
.phaser(2).delay(.25).room(.3)
.size(4).fast(.75)`}
/>
You also create some lovely chiptune-style sounds:
<MiniRepl
client:idle
tune={`n(run("<4 8>/16")).jux(rev)
.chord("<C^7 <Db^7 Fm7>>")
.dict('ireal')
.voicing().add(note("<0 1>/8"))
.dec(.1).room(.2)
.segment("<4 [2 8]>")
.penv("<0 <2 -2>>").patt(.02)`}
/>
Let's break down all pitch envelope controls:
## pattack
<JsDoc client:idle name="pattack" h={0} />
## pdecay
<JsDoc client:idle name="pdecay" h={0} />
## prelease
<JsDoc client:idle name="prelease" h={0} />
## penv
<JsDoc client:idle name="penv" h={0} />
## pcurve
<JsDoc client:idle name="pcurve" h={0} />
## panchor
<JsDoc client:idle name="panchor" h={0} />
# Dynamics
## gain

View File

@ -50,7 +50,7 @@ Available tags are:
- `@title`: music title
- `@by`: music author(s), separated by comma, eventually followed with a link in `<>` (ex: `@by John Doe <https://example.com>`)
- `@license`: music license(s)
- `@license`: music license(s), e.g. CC BY-NC-SA. Unsure? [Choose a creative commons license here](https://creativecommons.org/choose/)
- `@details`: some additional information about the music
- `@url`: web page(s) related to the music (git repo, soundcloud link, etc.)
- `@genre`: music genre(s) (pop, jazz, etc)

View File

@ -2,9 +2,9 @@ import { createCanvas } from 'canvas';
import { pianoroll } from '@strudel.cycles/core';
import { evaluate } from '@strudel.cycles/transpiler';
import '../../../../test/runtime.mjs';
import { getMyPatterns } from './list.json';
import { getMyPatterns } from '../../my_patterns';
export async function get({ params, request }) {
export async function GET({ params, request }) {
const patterns = await getMyPatterns();
const { name } = params;
const tune = patterns[name];
@ -14,10 +14,7 @@ export async function get({ params, request }) {
const ctx = canvas.getContext('2d');
pianoroll({ time: 4, haps, ctx, playhead: 1, fold: 1, background: 'transparent', playheadColor: 'transparent' });
const buffer = canvas.toBuffer('image/png');
return {
body: buffer,
encoding: 'binary',
};
return new Response(buffer);
}
export async function getStaticPaths() {
const patterns = await getMyPatterns();

View File

@ -1,5 +1,5 @@
---
import { getMyPatterns } from './list.json';
import { getMyPatterns } from '../../my_patterns.js';
import { Content } from '../../../../my-patterns/README.md';
import HeadCommon from '../../components/HeadCommon.astro';
@ -37,3 +37,4 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
}
</div>
</body>
../../list.json

View File

@ -1,17 +0,0 @@
import { getMetadata } from '../metadata_parser';
export function getMyPatterns() {
const my = import.meta.glob('../../../../my-patterns/**', { as: 'raw', eager: true });
return Object.fromEntries(
Object.entries(my)
.filter(([name]) => name.endsWith('.txt'))
.map(([name, raw]) => [getMetadata(raw)['title'] || name.split('/').slice(-1), raw]),
);
}
export async function get() {
const all = await getMyPatterns();
return {
body: JSON.stringify(all),
};
}

View File

@ -17,7 +17,6 @@ We have sounds, we have notes, now let's look at effects!
**low-pass filter**
<MiniRepl
hideHeader
client:visible
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
.sound("sawtooth").lpf(800)`}
@ -35,7 +34,6 @@ lpf = **l**ow **p**ass **f**ilter
**pattern the filter**
<MiniRepl
hideHeader
client:visible
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
.sound("sawtooth").lpf("200 1000")`}
@ -53,7 +51,6 @@ We will learn how to automate with waves later...
**vowel**
<MiniRepl
hideHeader
client:visible
tune={`note("<[c3,g3,e4] [bb2,f3,d4] [a2,f3,c4] [bb2,g3,eb4]>/2")
.sound("sawtooth").vowel("<a e i o>/2")`}
@ -62,7 +59,6 @@ We will learn how to automate with waves later...
**gain**
<MiniRepl
hideHeader
client:visible
tune={`stack(
sound("hh*8").gain("[.25 1]*2"),
@ -85,7 +81,6 @@ Rhythm is all about dynamics!
Let's combine all of the above into a little tune:
<MiniRepl
hideHeader
client:visible
tune={`stack(
stack(
@ -109,7 +104,6 @@ The 3 parts (drums, bassline, chords) are exactly as earlier, just stacked toget
**shape the sound with an adsr envelope**
<MiniRepl
hideHeader
client:visible
tune={`note("<c3 bb2 f3 eb3>")
.sound("sawtooth").lpf(600)
@ -146,7 +140,6 @@ Can you guess what they do?
**adsr short notation**
<MiniRepl
hideHeader
client:visible
tune={`note("<c3 bb2 f3 eb3>")
.sound("sawtooth").lpf(600)
@ -157,7 +150,6 @@ Can you guess what they do?
**delay**
<MiniRepl
hideHeader
client:visible
tune={`stack(
note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
@ -189,7 +181,6 @@ What happens if you use `.delay(".8:.06:.8")` ? Can you guess what the third num
**room aka reverb**
<MiniRepl
hideHeader
client:visible
tune={`n("<4 [3@3 4] [<2 0> ~@16] ~>/2")
.scale("D4:minor").sound("gm_accordion:2")
@ -207,7 +198,6 @@ Add a delay too!
**little dub tune**
<MiniRepl
hideHeader
client:visible
tune={`stack(
note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
@ -222,7 +212,6 @@ Add a delay too!
Let's add a bass to make this complete:
<MiniRepl
hideHeader
client:visible
tune={`stack(
note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
@ -246,7 +235,6 @@ Try adding `.hush()` at the end of one of the patterns in the stack...
**pan**
<MiniRepl
hideHeader
client:visible
tune={`sound("numbers:1 numbers:2 numbers:3 numbers:4")
.pan("0 0.3 .6 1")
@ -255,13 +243,13 @@ Try adding `.hush()` at the end of one of the patterns in the stack...
**speed**
<MiniRepl hideHeader client:visible tune={`sound("bd rim").speed("<1 2 -1 -2>").room(.2)`} />
<MiniRepl client:visible tune={`sound("bd rim").speed("<1 2 -1 -2>").room(.2)`} />
**fast and slow**
We can use `fast` and `slow` to change the tempo of a pattern outside of Mini-Notation:
<MiniRepl hideHeader client:visible tune={`sound("bd*2,~ rim").slow(2)`} />
<MiniRepl client:visible tune={`sound("bd*2,~ rim").slow(2)`} />
<Box>
@ -273,13 +261,13 @@ What happens if you use a pattern like `.fast("<1 [2 4]>")`?
By the way, inside Mini-Notation, `fast` is `*` and `slow` is `/`.
<MiniRepl hideHeader client:visible tune={`sound("[bd*2,~ rim]*<1 [2 4]>")`} />
<MiniRepl client:visible tune={`sound("[bd*2,~ rim]*<1 [2 4]>")`} />
## automation with signals
Instead of changing values stepwise, we can also control them with signals:
<MiniRepl hideHeader client:visible tune={`sound("hh*16").gain(sine)`} punchcard punchcardLabels={false} />
<MiniRepl client:visible tune={`sound("hh*16").gain(sine)`} punchcard punchcardLabels={false} />
<Box>
@ -295,7 +283,7 @@ The gain is visualized as transparency in the pianoroll.
By default, waves oscillate between 0 to 1. We can change that with `range`:
<MiniRepl hideHeader client:visible tune={`sound("hh*8").lpf(saw.range(500, 2000))`} />
<MiniRepl client:visible tune={`sound("hh*8").lpf(saw.range(500, 2000))`} />
<Box>
@ -306,7 +294,6 @@ What happens if you flip the range values?
We can change the automation speed with slow / fast:
<MiniRepl
hideHeader
client:visible
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
.sound("sawtooth")
@ -321,15 +308,15 @@ The whole automation will now take 8 cycles to repeat.
## Recap
| name | example |
| ----- | -------------------------------------------------------------------------------------------------- |
| lpf | <MiniRepl hideHeader client:visible tune={`note("c2 c3").s("sawtooth").lpf("<400 2000>")`} /> |
| vowel | <MiniRepl hideHeader client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
| gain | <MiniRepl hideHeader client:visible tune={`s("hh*8").gain("[.25 1]*2")`} /> |
| delay | <MiniRepl hideHeader client:visible tune={`s("bd rim").delay(.5)`} /> |
| room | <MiniRepl hideHeader client:visible tune={`s("bd rim").room(.5)`} /> |
| pan | <MiniRepl hideHeader client:visible tune={`s("bd rim").pan("0 1")`} /> |
| speed | <MiniRepl hideHeader client:visible tune={`s("bd rim").speed("<1 2 -1 -2>")`} /> |
| range | <MiniRepl hideHeader client:visible tune={`s("hh*16").lpf(saw.range(200,4000))`} /> |
| name | example |
| ----- | --------------------------------------------------------------------------------------- |
| lpf | <MiniRepl client:visible tune={`note("c2 c3").s("sawtooth").lpf("<400 2000>")`} /> |
| vowel | <MiniRepl client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
| gain | <MiniRepl client:visible tune={`s("hh*8").gain("[.25 1]*2")`} /> |
| delay | <MiniRepl client:visible tune={`s("bd rim").delay(.5)`} /> |
| room | <MiniRepl client:visible tune={`s("bd rim").room(.5)`} /> |
| pan | <MiniRepl client:visible tune={`s("bd rim").pan("0 1")`} /> |
| speed | <MiniRepl client:visible tune={`s("bd rim").speed("<1 2 -1 -2>")`} /> |
| range | <MiniRepl client:visible tune={`s("hh*16").lpf(saw.range(200,4000))`} /> |
Let us now take a look at some of Tidal's typical [pattern effects](/workshop/pattern-effects).

View File

@ -17,7 +17,6 @@ Let's look at how we can play notes
**play notes with numbers**
<MiniRepl
hideHeader
client:visible
tune={`note("48 52 55 59").sound("piano")`}
claviature
@ -39,7 +38,6 @@ Try decimal numbers, like 55.5
**play notes with letters**
<MiniRepl
hideHeader
client:visible
tune={`note("c e g b").sound("piano")`}
claviature
@ -57,7 +55,6 @@ Can you find melodies that are actual words? Hint: ☕ 😉 ⚪
**add flats or sharps to play the black keys**
<MiniRepl
hideHeader
client:visible
tune={`note("db eb gb ab bb").sound("piano")`}
claviature
@ -67,7 +64,6 @@ Can you find melodies that are actual words? Hint: ☕ 😉 ⚪
/>
<MiniRepl
hideHeader
client:visible
tune={`note("c# d# f# g# a#").sound("piano")`}
claviature
@ -79,7 +75,6 @@ Can you find melodies that are actual words? Hint: ☕ 😉 ⚪
**play notes with letters in different octaves**
<MiniRepl
hideHeader
client:visible
tune={`note("c2 e3 g4 b5").sound("piano")`}
claviature
@ -104,7 +99,7 @@ We will also look at ways to make it easier to play the right notes later.
Just like with unpitched sounds, we can change the sound of our notes with `sound`:
<MiniRepl hideHeader client:visible tune={`note("36 43, 52 59 62 64").sound("piano")`} />
<MiniRepl client:visible tune={`note("36 43, 52 59 62 64").sound("piano")`} />
{/* c2 g2, e3 b3 d4 e4 */}
@ -127,7 +122,6 @@ Try out different sounds:
**switch between sounds**
<MiniRepl
hideHeader
client:visible
tune={`note("48 67 63 [62, 58]")
.sound("piano gm_electric_guitar_muted")`}
@ -136,7 +130,6 @@ Try out different sounds:
**stack multiple sounds**
<MiniRepl
hideHeader
client:visible
tune={`note("48 67 63 [62, 58]")
.sound("piano, gm_electric_guitar_muted")`}
@ -156,7 +149,7 @@ We will see more ways to combine patterns later..
{/* [c2 bb1 f2 eb2] */}
<MiniRepl hideHeader client:visible tune={`note("[36 34 41 39]/4").sound("gm_acoustic_bass")`} punchcard />
<MiniRepl client:visible tune={`note("[36 34 41 39]/4").sound("gm_acoustic_bass")`} punchcard />
<Box>
@ -172,7 +165,7 @@ Because it is so common to just play one thing per cycle, you can..
**Play one per cycle with \< \>**
<MiniRepl hideHeader client:visible tune={`note("<36 34 41 39>").sound("gm_acoustic_bass")`} punchcard />
<MiniRepl client:visible tune={`note("<36 34 41 39>").sound("gm_acoustic_bass")`} punchcard />
<Box>
@ -185,7 +178,6 @@ Try adding more notes inside the brackets and notice how it does **not** get fas
{/* <[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2 */}
<MiniRepl
hideHeader
client:visible
tune={`note("<[36 48]*4 [34 46]*4 [41 53]*4 [39 51]*4>/2")
.sound("gm_acoustic_bass")`}
@ -195,7 +187,6 @@ Try adding more notes inside the brackets and notice how it does **not** get fas
**Alternate between multiple things**
<MiniRepl
hideHeader
client:visible
tune={`note("60 <63 62 65 63>")
.sound("gm_xylophone")`}
@ -205,7 +196,6 @@ Try adding more notes inside the brackets and notice how it does **not** get fas
This is also useful for unpitched sounds:
<MiniRepl
hideHeader
client:visible
tune={`sound("bd*2, ~ <sd cp>, [~ hh]*2")
.bank("RolandTR909")`}
@ -217,7 +207,6 @@ This is also useful for unpitched sounds:
Finding the right notes can be difficult.. Scales are here to help:
<MiniRepl
hideHeader
client:visible
tune={`n("0 2 4 <[6,8] [7,9]>")
.scale("C:minor").sound("piano")`}
@ -244,7 +233,6 @@ Try out different scales:
Just like anything, we can automate the scale with a pattern:
<MiniRepl
hideHeader
client:visible
tune={`n("<0 -3>, 2 4 <[6,8] [7,9]>")
.scale("<C:major D:mixolydian>/4")
@ -265,7 +253,7 @@ Take your time and you'll find scales you like!
**Elongate with @**
<MiniRepl hideHeader client:visible tune={`note("c@3 eb").sound("gm_acoustic_bass")`} punchcard />
<MiniRepl client:visible tune={`note("c@3 eb").sound("gm_acoustic_bass")`} punchcard />
<Box>
@ -278,7 +266,6 @@ Try changing that number!
**Elongate within sub-sequences**
<MiniRepl
hideHeader
client:visible
tune={`n("<[4@2 4] [5@2 5] [6@2 6] [5@2 5]>*2")
.scale("<C2:mixolydian F2:mixolydian>/4")
@ -296,7 +283,7 @@ This is also sometimes called triplet swing. You'll often find it in blues and j
**Replicate**
<MiniRepl hideHeader client:visible tune={`note("c!2 [eb,<g a bb a>]").sound("piano")`} punchcard />
<MiniRepl client:visible tune={`note("c!2 [eb,<g a bb a>]").sound("piano")`} punchcard />
<Box>
@ -310,27 +297,26 @@ What's the difference?
Let's recap what we've learned in this chapter:
| Concept | Syntax | Example |
| --------- | ------ | ------------------------------------------------------------------- |
| Slow down | \/ | <MiniRepl hideHeader client:visible tune={`note("[c a f e]/2")`} /> |
| Alternate | \<\> | <MiniRepl hideHeader client:visible tune={`note("c <e g>")`} /> |
| Elongate | @ | <MiniRepl hideHeader client:visible tune={`note("c@3 e")`} /> |
| Replicate | ! | <MiniRepl hideHeader client:visible tune={`note("c!3 e")`} /> |
| Concept | Syntax | Example |
| --------- | ------ | -------------------------------------------------------- |
| Slow down | \/ | <MiniRepl client:visible tune={`note("[c a f e]/2")`} /> |
| Alternate | \<\> | <MiniRepl client:visible tune={`note("c <e g>")`} /> |
| Elongate | @ | <MiniRepl client:visible tune={`note("c@3 e")`} /> |
| Replicate | ! | <MiniRepl client:visible tune={`note("c!3 e")`} /> |
New functions:
| Name | Description | Example |
| ----- | ----------------------------------- | -------------------------------------------------------------------------------------------- |
| note | set pitch as number or letter | <MiniRepl hideHeader client:visible tune={`note("b g e c").sound("piano")`} /> |
| scale | interpret `n` as scale degree | <MiniRepl hideHeader client:visible tune={`n("6 4 2 0").scale("C:minor").sound("piano")`} /> |
| stack | play patterns in parallel (read on) | <MiniRepl hideHeader client:visible tune={`stack(s("bd sd"),note("c eb g"))`} /> |
| Name | Description | Example |
| ----- | ----------------------------------- | --------------------------------------------------------------------------------- |
| note | set pitch as number or letter | <MiniRepl client:visible tune={`note("b g e c").sound("piano")`} /> |
| scale | interpret `n` as scale degree | <MiniRepl client:visible tune={`n("6 4 2 0").scale("C:minor").sound("piano")`} /> |
| stack | play patterns in parallel (read on) | <MiniRepl client:visible tune={`stack(s("bd sd"),note("c eb g"))`} /> |
## Examples
**Classy Bassline**
<MiniRepl
hideHeader
client:visible
tune={`note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
.sound("gm_synth_bass_1")
@ -340,7 +326,6 @@ New functions:
**Classy Melody**
<MiniRepl
hideHeader
client:visible
tune={`n(\`<
[~ 0] 2 [0 2] [~ 2]
@ -354,7 +339,6 @@ New functions:
**Classy Drums**
<MiniRepl
hideHeader
client:visible
tune={`sound("bd*2, ~ <sd cp>, [~ hh]*2")
.bank("RolandTR909")`}
@ -369,7 +353,6 @@ It's called `stack` 😙
</Box>
<MiniRepl
hideHeader
client:visible
tune={`stack(
note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")

View File

@ -15,7 +15,7 @@ This is the first chapter of the Strudel Workshop, nice to have you on board!
The workshop is full of interactive code fields. Let's learn how to use those. Here is one:
<MiniRepl hideHeader client:visible tune={`sound("casio")`} dirt />
<MiniRepl client:visible tune={`sound("casio")`} dirt />
<Box>
@ -33,7 +33,7 @@ Congratulations, you are now live coding!
We have just played a sound with `sound` like this:
<MiniRepl hideHeader client:visible tune={`sound("casio")`} hideHeader />
<MiniRepl client:visible tune={`sound("casio")`} />
<Box>
@ -55,7 +55,7 @@ One Sound can contain multiple samples (audio files).
You can select the sample by appending `:` followed by a number to the name:
<MiniRepl hideHeader client:visible tune={`sound("casio:1")`} hideHeader />
<MiniRepl client:visible tune={`sound("casio:1")`} />
<Box>
@ -72,7 +72,7 @@ For now we'll stick to this little selection of sounds, but we'll find out how t
By default, Strudel comes with a wide selection of drum sounds:
<MiniRepl hideHeader client:visible tune={`sound("bd hh sd oh")`} hideHeader />
<MiniRepl client:visible tune={`sound("bd hh sd oh")`} />
<Box>
@ -90,7 +90,7 @@ Try out different drum sounds!
To change the sound character of our drums, we can use `bank` to change the drum machine:
<MiniRepl hideHeader client:visible tune={`sound("bd hh sd oh").bank("RolandTR909")`} hideHeader />
<MiniRepl client:visible tune={`sound("bd hh sd oh").bank("RolandTR909")`} />
In this example `RolandTR909` is the name of the drum machine that we're using.
It is a famous drum machine for house and techno beats.
@ -115,7 +115,7 @@ There are a lot more, but let's keep it simple for now
In the last example, we already saw that you can play multiple sounds in a sequence by separating them with a space:
<MiniRepl hideHeader client:visible tune={`sound("bd hh sd hh")`} punchcard />
<MiniRepl client:visible tune={`sound("bd hh sd hh")`} punchcard />
Notice how the currently playing sound is highlighted in the code and also visualized below.
@ -127,13 +127,13 @@ Try adding more sounds to the sequence!
**The longer the sequence, the faster it runs**
<MiniRepl hideHeader client:visible tune={`sound("bd bd hh bd rim bd hh bd")`} punchcard />
<MiniRepl client:visible tune={`sound("bd bd hh bd rim bd hh bd")`} punchcard />
The content of a sequence will be squished into what's called a cycle.
**One way to change the tempo is using `cpm`**
<MiniRepl hideHeader client:visible tune={`sound("bd bd hh bd rim bd hh bd").cpm(40)`} punchcard />
<MiniRepl client:visible tune={`sound("bd bd hh bd rim bd hh bd").cpm(40)`} punchcard />
<Box>
@ -147,11 +147,11 @@ We will look at other ways to change the tempo later!
**Add a rests in a sequence with '~'**
<MiniRepl hideHeader client:visible tune={`sound("bd hh ~ rim")`} punchcard />
<MiniRepl client:visible tune={`sound("bd hh ~ rim")`} punchcard />
**Sub-Sequences with [brackets]**
<MiniRepl hideHeader client:visible tune={`sound("bd [hh hh] sd [hh bd]")`} punchcard />
<MiniRepl client:visible tune={`sound("bd [hh hh] sd [hh bd]")`} punchcard />
<Box>
@ -163,15 +163,15 @@ Similar to the whole sequence, the content of a sub-sequence will be squished to
**Multiplication: Speed things up**
<MiniRepl hideHeader client:visible tune={`sound("bd hh*2 rim hh*3")`} punchcard />
<MiniRepl client:visible tune={`sound("bd hh*2 rim hh*3")`} punchcard />
**Multiplication: Speed up sequences**
<MiniRepl hideHeader client:visible tune={`sound("bd [hh rim]*2")`} punchcard />
<MiniRepl client:visible tune={`sound("bd [hh rim]*2")`} punchcard />
**Multiplication: Speeeeeeeeed things up**
<MiniRepl hideHeader client:visible tune={`sound("bd hh*16 rim hh*8")`} punchcard />
<MiniRepl client:visible tune={`sound("bd hh*16 rim hh*8")`} punchcard />
<Box>
@ -181,7 +181,7 @@ Pitch = really fast rhythm
**Sub-Sub-Sequences with [[brackets]]**
<MiniRepl hideHeader client:visible tune={`sound("bd [[rim rim] hh]")`} punchcard />
<MiniRepl client:visible tune={`sound("bd [[rim rim] hh]")`} punchcard />
<Box>
@ -191,15 +191,15 @@ You can go as deep as you want!
**Play sequences in parallel with comma**
<MiniRepl hideHeader client:visible tune={`sound("hh hh hh, bd casio")`} punchcard />
<MiniRepl client:visible tune={`sound("hh hh hh, bd casio")`} punchcard />
You can use as many commas as you want:
<MiniRepl hideHeader client:visible tune={`sound("hh hh hh, bd bd, ~ casio")`} punchcard />
<MiniRepl client:visible tune={`sound("hh hh hh, bd bd, ~ casio")`} punchcard />
Commas can also be used inside sub-sequences:
<MiniRepl hideHeader client:visible tune={`sound("hh hh hh, bd [bd,casio]")`} punchcard />
<MiniRepl client:visible tune={`sound("hh hh hh, bd [bd,casio]")`} punchcard />
<Box>
@ -212,7 +212,6 @@ It is quite common that there are many ways to express the same idea.
**Multiple Lines with backticks**
<MiniRepl
hideHeader
client:visible
tune={`sound(\`bd*2, ~ cp,
~ ~ ~ oh, hh*4,
@ -224,45 +223,45 @@ It is quite common that there are many ways to express the same idea.
Instead of using ":", we can also use the `n` function to select sample numbers:
<MiniRepl hideHeader client:visible tune={`n("0 1 [4 2] 3*2").sound("jazz")`} punchcard />
<MiniRepl client:visible tune={`n("0 1 [4 2] 3*2").sound("jazz")`} punchcard />
This is shorter and more readable than:
<MiniRepl hideHeader client:visible tune={`sound("jazz:0 jazz:1 [jazz:4 jazz:2] jazz:3*2")`} punchcard />
<MiniRepl client:visible tune={`sound("jazz:0 jazz:1 [jazz:4 jazz:2] jazz:3*2")`} punchcard />
## Recap
Now we've learned the basics of the so called Mini-Notation, the rhythm language of Tidal.
This is what we've leared so far:
| Concept | Syntax | Example |
| ----------------- | -------- | -------------------------------------------------------------------------------- |
| Sequence | space | <MiniRepl hideHeader client:visible tune={`sound("bd bd sd hh")`} /> |
| Sample Number | :x | <MiniRepl hideHeader client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
| Rests | ~ | <MiniRepl hideHeader client:visible tune={`sound("metal ~ jazz jazz:1")`} /> |
| Sub-Sequences | \[\] | <MiniRepl hideHeader client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
| Sub-Sub-Sequences | \[\[\]\] | <MiniRepl hideHeader client:visible tune={`sound("bd [metal [jazz sd]]")`} /> |
| Speed up | \* | <MiniRepl hideHeader client:visible tune={`sound("bd sd*2 cp*3")`} /> |
| Parallel | , | <MiniRepl hideHeader client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
| Concept | Syntax | Example |
| ----------------- | -------- | --------------------------------------------------------------------- |
| Sequence | space | <MiniRepl client:visible tune={`sound("bd bd sd hh")`} /> |
| Sample Number | :x | <MiniRepl client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
| Rests | ~ | <MiniRepl client:visible tune={`sound("metal ~ jazz jazz:1")`} /> |
| Sub-Sequences | \[\] | <MiniRepl client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
| Sub-Sub-Sequences | \[\[\]\] | <MiniRepl client:visible tune={`sound("bd [metal [jazz sd]]")`} /> |
| Speed up | \* | <MiniRepl client:visible tune={`sound("bd sd*2 cp*3")`} /> |
| Parallel | , | <MiniRepl client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
The Mini-Notation is usually used inside some function. These are the functions we've seen so far:
| Name | Description | Example |
| ----- | ----------------------------------- | ---------------------------------------------------------------------------------- |
| sound | plays the sound of the given name | <MiniRepl hideHeader client:visible tune={`sound("bd sd")`} /> |
| bank | selects the sound bank | <MiniRepl hideHeader client:visible tune={`sound("bd sd").bank("RolandTR909")`} /> |
| cpm | sets the tempo in cycles per minute | <MiniRepl hideHeader client:visible tune={`sound("bd sd").cpm(90)`} /> |
| n | select sample number | <MiniRepl hideHeader client:visible tune={`n("0 1 4 2").sound("jazz")`} /> |
| Name | Description | Example |
| ----- | ----------------------------------- | ----------------------------------------------------------------------- |
| sound | plays the sound of the given name | <MiniRepl client:visible tune={`sound("bd sd")`} /> |
| bank | selects the sound bank | <MiniRepl client:visible tune={`sound("bd sd").bank("RolandTR909")`} /> |
| cpm | sets the tempo in cycles per minute | <MiniRepl client:visible tune={`sound("bd sd").cpm(90)`} /> |
| n | select sample number | <MiniRepl client:visible tune={`n("0 1 4 2").sound("jazz")`} /> |
## Examples
**Basic rock beat**
<MiniRepl hideHeader client:visible tune={`sound("bd sd, hh*4").bank("RolandTR505").cpm(100/2)`} punchcard />
<MiniRepl client:visible tune={`sound("bd sd, hh*4").bank("RolandTR505").cpm(100/2)`} punchcard />
**Classic house**
<MiniRepl hideHeader client:visible tune={`sound("bd*2, ~ cp, [~ hh]*2").bank("RolandTR909")`} punchcard />
<MiniRepl client:visible tune={`sound("bd*2, ~ cp, [~ hh]*2").bank("RolandTR909")`} punchcard />
<Box>
@ -273,12 +272,11 @@ Certain drum patterns are reused across genres.
We Will Rock you
<MiniRepl hideHeader client:visible tune={`sound("bd*2 cp").bank("RolandTR707").cpm(81/2)`} punchcard />
<MiniRepl client:visible tune={`sound("bd*2 cp").bank("RolandTR707").cpm(81/2)`} punchcard />
**Yellow Magic Orchestra - Firecracker**
<MiniRepl
hideHeader
client:visible
tune={`sound("bd sd, ~ ~ ~ hh ~ hh ~ ~, ~ perc ~ perc:1*2")
.bank("RolandCompurhythm1000")`}
@ -288,7 +286,6 @@ We Will Rock you
**Imitation of a 16 step sequencer**
<MiniRepl
hideHeader
client:visible
tune={`sound(\`
[~ ~ oh ~ ] [~ ~ ~ ~ ] [~ ~ ~ ~ ] [~ ~ ~ ~ ],
@ -302,7 +299,6 @@ We Will Rock you
**Another one**
<MiniRepl
hideHeader
client:visible
tune={`sound(\`
[~ ~ ~ ~ ] [~ ~ ~ ~ ] [~ ~ ~ ~ ] [~ ~ oh:1 ~ ],
@ -316,7 +312,6 @@ We Will Rock you
**Not your average drums**
<MiniRepl
hideHeader
client:visible
tune={`s(\`jazz*2,
insect [crow metal] ~ ~,

View File

@ -15,16 +15,15 @@ In this chapter, we are going to look at functions that are more unique to tidal
**reverse patterns with rev**
<MiniRepl hideHeader client:visible tune={`n("0 1 [4 3] 2").sound("jazz").rev()`} />
<MiniRepl client:visible tune={`n("0 1 [4 3] 2").sound("jazz").rev()`} />
**play pattern left and modify it right with jux**
<MiniRepl hideHeader client:visible tune={`n("0 1 [4 3] 2").sound("jazz").jux(rev)`} />
<MiniRepl client:visible tune={`n("0 1 [4 3] 2").sound("jazz").jux(rev)`} />
This is the same as:
<MiniRepl
hideHeader
client:visible
tune={`stack(
n("0 1 [4 3] 2").sound("jazz").pan(0),
@ -35,7 +34,6 @@ This is the same as:
Let's visualize what happens here:
<MiniRepl
hideHeader
client:visible
tune={`stack(
n("0 1 [4 3] 2").sound("jazz").pan(0).color("cyan"),
@ -52,12 +50,11 @@ Try commenting out one of the two by adding `//` before a line
**multiple tempos**
<MiniRepl hideHeader client:visible tune={`note("c2, eb3 g3 [bb3 c4]").sound("piano").slow("1,2,3")`} />
<MiniRepl client:visible tune={`note("c2, eb3 g3 [bb3 c4]").sound("piano").slow("1,2,3")`} />
This is like doing
<MiniRepl
hideHeader
client:visible
tune={`stack(
note("c2, eb3 g3 [bb3 c4]").s("piano").slow(1).color('cyan'),
@ -76,7 +73,6 @@ Try commenting out one or more by adding `//` before a line
**add**
<MiniRepl
hideHeader
client:visible
tune={`note("c2 [eb3,g3]".add("<0 <1 -1>>"))
.color("<cyan <magenta yellow>>").adsr("[.1 0]:.2:[1 0]")
@ -93,7 +89,6 @@ If you add a number to a note, the note will be treated as if it was a number
We can add as often as we like:
<MiniRepl
hideHeader
client:visible
tune={`note("c2 [eb3,g3]".add("<0 <1 -1>>").add("0,7"))
.color("<cyan <magenta yellow>>").adsr("[.1 0]:.2:[1 0]")
@ -104,7 +99,6 @@ We can add as often as we like:
**add with scale**
<MiniRepl
hideHeader
client:visible
tune={`n("<0 [2 4] <3 5> [~ <4 1>]>*2".add("<0 [0,2,4]>/4"))
.scale("C5:minor").release(.5)
@ -115,7 +109,6 @@ We can add as often as we like:
**time to stack**
<MiniRepl
hideHeader
client:visible
tune={`stack(
n("<0 [2 4] <3 5> [~ <4 1>]>*2".add("<0 [0,2,4]>/4"))
@ -132,11 +125,11 @@ We can add as often as we like:
**ply**
<MiniRepl hideHeader client:visible tune={`sound("hh, bd rim").bank("RolandTR707").ply(2)`} punchcard />
<MiniRepl client:visible tune={`sound("hh, bd rim").bank("RolandTR707").ply(2)`} punchcard />
this is like writing:
<MiniRepl hideHeader client:visible tune={`sound("hh*2, bd*2 rim*2").bank("RolandTR707")`} punchcard />
<MiniRepl client:visible tune={`sound("hh*2, bd*2 rim*2").bank("RolandTR707")`} punchcard />
<Box>
@ -147,7 +140,6 @@ Try patterning the `ply` function, for example using `"<1 2 1 3>"`
**off**
<MiniRepl
hideHeader
client:visible
tune={`n("<0 [4 <3 2>] <2 3> [~ 1]>"
.off(1/8, x=>x.add(4))
@ -166,16 +158,15 @@ In the notation `x=>x.`, the `x` is the shifted pattern, which where modifying.
off is also useful for sounds:
<MiniRepl
hideHeader
client:visible
tune={`s("bd sd,[~ hh]*2").bank("CasioRZ1")
.off(1/8, x=>x.speed(1.5).gain(.25))`}
/>
| name | description | example |
| ---- | ------------------------------ | ---------------------------------------------------------------------------------------------- |
| rev | reverse | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6").scale("C:minor").rev()`} /> |
| jux | split left/right, modify right | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
| add | add numbers / notes | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
| ply | speed up each event n times | <MiniRepl hideHeader client:visible tune={`s("bd sd").ply("<1 2 3>")`} /> |
| off | copy, shift time & modify | <MiniRepl hideHeader client:visible tune={`s("bd sd, hh*4").off(1/8, x=>x.speed(2))`} /> |
| name | description | example |
| ---- | ------------------------------ | ----------------------------------------------------------------------------------- |
| rev | reverse | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").rev()`} /> |
| jux | split left/right, modify right | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
| add | add numbers / notes | <MiniRepl client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
| ply | speed up each event n times | <MiniRepl client:visible tune={`s("bd sd").ply("<1 2 3>")`} /> |
| off | copy, shift time & modify | <MiniRepl client:visible tune={`s("bd sd, hh*4").off(1/8, x=>x.speed(2))`} /> |

View File

@ -11,58 +11,58 @@ This page is just a listing of all functions covered in the workshop!
## Mini Notation
| Concept | Syntax | Example |
| ----------------- | -------- | -------------------------------------------------------------------------------- |
| Sequence | space | <MiniRepl hideHeader client:visible tune={`sound("bd bd sn hh")`} /> |
| Sample Number | :x | <MiniRepl hideHeader client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
| Rests | ~ | <MiniRepl hideHeader client:visible tune={`sound("metal ~ jazz jazz:1")`} /> |
| Sub-Sequences | \[\] | <MiniRepl hideHeader client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
| Sub-Sub-Sequences | \[\[\]\] | <MiniRepl hideHeader client:visible tune={`sound("bd [metal [jazz sn]]")`} /> |
| Speed up | \* | <MiniRepl hideHeader client:visible tune={`sound("bd sn*2 cp*3")`} /> |
| Parallel | , | <MiniRepl hideHeader client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
| Slow down | \/ | <MiniRepl hideHeader client:visible tune={`note("[c a f e]/2")`} /> |
| Alternate | \<\> | <MiniRepl hideHeader client:visible tune={`note("c <e g>")`} /> |
| Elongate | @ | <MiniRepl hideHeader client:visible tune={`note("c@3 e")`} /> |
| Replicate | ! | <MiniRepl hideHeader client:visible tune={`note("c!3 e")`} /> |
| Concept | Syntax | Example |
| ----------------- | -------- | --------------------------------------------------------------------- |
| Sequence | space | <MiniRepl client:visible tune={`sound("bd bd sn hh")`} /> |
| Sample Number | :x | <MiniRepl client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
| Rests | ~ | <MiniRepl client:visible tune={`sound("metal ~ jazz jazz:1")`} /> |
| Sub-Sequences | \[\] | <MiniRepl client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
| Sub-Sub-Sequences | \[\[\]\] | <MiniRepl client:visible tune={`sound("bd [metal [jazz sn]]")`} /> |
| Speed up | \* | <MiniRepl client:visible tune={`sound("bd sn*2 cp*3")`} /> |
| Parallel | , | <MiniRepl client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
| Slow down | \/ | <MiniRepl client:visible tune={`note("[c a f e]/2")`} /> |
| Alternate | \<\> | <MiniRepl client:visible tune={`note("c <e g>")`} /> |
| Elongate | @ | <MiniRepl client:visible tune={`note("c@3 e")`} /> |
| Replicate | ! | <MiniRepl client:visible tune={`note("c!3 e")`} /> |
## Sounds
| Name | Description | Example |
| ----- | --------------------------------- | ---------------------------------------------------------------------------------- |
| sound | plays the sound of the given name | <MiniRepl hideHeader client:visible tune={`sound("bd sd")`} /> |
| bank | selects the sound bank | <MiniRepl hideHeader client:visible tune={`sound("bd sd").bank("RolandTR909")`} /> |
| n | select sample number | <MiniRepl hideHeader client:visible tune={`n("0 1 4 2").sound("jazz")`} /> |
| Name | Description | Example |
| ----- | --------------------------------- | ----------------------------------------------------------------------- |
| sound | plays the sound of the given name | <MiniRepl client:visible tune={`sound("bd sd")`} /> |
| bank | selects the sound bank | <MiniRepl client:visible tune={`sound("bd sd").bank("RolandTR909")`} /> |
| n | select sample number | <MiniRepl client:visible tune={`n("0 1 4 2").sound("jazz")`} /> |
## Notes
| Name | Description | Example |
| --------- | ----------------------------- | -------------------------------------------------------------------------------------------- |
| note | set pitch as number or letter | <MiniRepl hideHeader client:visible tune={`note("b g e c").sound("piano")`} /> |
| n + scale | set note in scale | <MiniRepl hideHeader client:visible tune={`n("6 4 2 0").scale("C:minor").sound("piano")`} /> |
| stack | play patterns in parallel | <MiniRepl hideHeader client:visible tune={`stack(s("bd sd"),note("c eb g"))`} /> |
| Name | Description | Example |
| --------- | ----------------------------- | --------------------------------------------------------------------------------- |
| note | set pitch as number or letter | <MiniRepl client:visible tune={`note("b g e c").sound("piano")`} /> |
| n + scale | set note in scale | <MiniRepl client:visible tune={`n("6 4 2 0").scale("C:minor").sound("piano")`} /> |
| stack | play patterns in parallel | <MiniRepl client:visible tune={`stack(s("bd sd"),note("c eb g"))`} /> |
## Audio Effects
| name | example |
| ----- | -------------------------------------------------------------------------------------------------- |
| lpf | <MiniRepl hideHeader client:visible tune={`note("c2 c3").s("sawtooth").lpf("<400 2000>")`} /> |
| vowel | <MiniRepl hideHeader client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
| gain | <MiniRepl hideHeader client:visible tune={`s("hh*8").gain("[.25 1]*2")`} /> |
| delay | <MiniRepl hideHeader client:visible tune={`s("bd rim").delay(.5)`} /> |
| room | <MiniRepl hideHeader client:visible tune={`s("bd rim").room(.5)`} /> |
| pan | <MiniRepl hideHeader client:visible tune={`s("bd rim").pan("0 1")`} /> |
| speed | <MiniRepl hideHeader client:visible tune={`s("bd rim").speed("<1 2 -1 -2>")`} /> |
| range | <MiniRepl hideHeader client:visible tune={`s("hh*16").lpf(saw.range(200,4000))`} /> |
| name | example |
| ----- | --------------------------------------------------------------------------------------- |
| lpf | <MiniRepl client:visible tune={`note("c2 c3").s("sawtooth").lpf("<400 2000>")`} /> |
| vowel | <MiniRepl client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
| gain | <MiniRepl client:visible tune={`s("hh*8").gain("[.25 1]*2")`} /> |
| delay | <MiniRepl client:visible tune={`s("bd rim").delay(.5)`} /> |
| room | <MiniRepl client:visible tune={`s("bd rim").room(.5)`} /> |
| pan | <MiniRepl client:visible tune={`s("bd rim").pan("0 1")`} /> |
| speed | <MiniRepl client:visible tune={`s("bd rim").speed("<1 2 -1 -2>")`} /> |
| range | <MiniRepl client:visible tune={`s("hh*16").lpf(saw.range(200,4000))`} /> |
## Pattern Effects
| name | description | example |
| ---- | ----------------------------------- | ---------------------------------------------------------------------------------------------- |
| cpm | sets the tempo in cycles per minute | <MiniRepl hideHeader client:visible tune={`sound("bd sd").cpm(90)`} /> |
| fast | speed up | <MiniRepl hideHeader client:visible tune={`sound("bd sd").fast(2)`} /> |
| slow | slow down | <MiniRepl hideHeader client:visible tune={`sound("bd sd").slow(2)`} /> |
| rev | reverse | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6").scale("C:minor").rev()`} /> |
| jux | split left/right, modify right | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
| add | add numbers / notes | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
| ply | speed up each event n times | <MiniRepl hideHeader client:visible tune={`s("bd sd").ply("<1 2 3>")`} /> |
| off | copy, shift time & modify | <MiniRepl hideHeader client:visible tune={`s("bd sd, hh*4").off(1/8, x=>x.speed(2))`} /> |
| name | description | example |
| ---- | ----------------------------------- | ----------------------------------------------------------------------------------- |
| cpm | sets the tempo in cycles per minute | <MiniRepl client:visible tune={`sound("bd sd").cpm(90)`} /> |
| fast | speed up | <MiniRepl client:visible tune={`sound("bd sd").fast(2)`} /> |
| slow | slow down | <MiniRepl client:visible tune={`sound("bd sd").slow(2)`} /> |
| rev | reverse | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").rev()`} /> |
| jux | split left/right, modify right | <MiniRepl client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
| add | add numbers / notes | <MiniRepl client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
| ply | speed up each event n times | <MiniRepl client:visible tune={`s("bd sd").ply("<1 2 3>")`} /> |
| off | copy, shift time & modify | <MiniRepl client:visible tune={`s("bd sd, hh*4").off(1/8, x=>x.speed(2))`} /> |

View File

@ -8,9 +8,10 @@ import { code2hash, getDrawContext, logger, silence } from '@strudel.cycles/core
import cx from '@src/cx.mjs';
import { transpiler } from '@strudel.cycles/transpiler';
import { getAudioContext, initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio';
import { defaultAudioDeviceName, getAudioDevices, setAudioDevice } from './panel/AudioDeviceSelector';
import { defaultAudioDeviceName } from '../settings.mjs';
import { getAudioDevices, setAudioDevice } from './util.mjs';
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
import { createContext, useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import {
initUserCode,
setActivePattern,
@ -24,12 +25,11 @@ import Loader from './Loader';
import { Panel } from './panel/Panel';
import { useStore } from '@nanostores/react';
import { prebake } from './prebake.mjs';
import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs';
import { getRandomTune, initCode, loadModules, shareCode, ReplContext } from './util.mjs';
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
import './Repl.css';
const { code: randomTune, name } = getRandomTune();
export const ReplContext = createContext(null);
const { latestCode } = settingsMap.get();

View File

@ -1,42 +1,8 @@
import React, { useState } from 'react';
import { getAudioContext, initializeAudioOutput, setDefaultAudioContext } from '@strudel.cycles/webaudio';
import { getAudioDevices, setAudioDevice } from '../util.mjs';
import { SelectInput } from './SelectInput';
import { logger } from '@strudel.cycles/core';
const initdevices = new Map();
export const defaultAudioDeviceName = 'System Standard';
export const getAudioDevices = async () => {
await navigator.mediaDevices.getUserMedia({ audio: true });
let mediaDevices = await navigator.mediaDevices.enumerateDevices();
mediaDevices = mediaDevices.filter((device) => device.kind === 'audiooutput' && device.deviceId !== 'default');
const devicesMap = new Map();
devicesMap.set(defaultAudioDeviceName, '');
mediaDevices.forEach((device) => {
devicesMap.set(device.label, device.deviceId);
});
return devicesMap;
};
export const setAudioDevice = async (id) => {
let audioCtx = getAudioContext();
if (audioCtx.sinkId === id) {
return;
}
await audioCtx.suspend();
await audioCtx.close();
audioCtx = setDefaultAudioContext();
await audioCtx.resume();
const isValidID = (id ?? '').length > 0;
if (isValidID) {
try {
await audioCtx.setSinkId(id);
} catch {
logger('failed to set audio interface', 'warning');
}
}
initializeAudioOutput();
};
// Allows the user to select an audio interface for Strudel to play through
export function AudioDeviceSelector({ audioDeviceName, onChange, isDisabled }) {

View File

@ -1,6 +1,8 @@
import { DocumentDuplicateIcon, PencilSquareIcon, TrashIcon } from '@heroicons/react/20/solid';
import { useMemo } from 'react';
import {
$featuredPatterns,
$publicPatterns,
clearUserPatterns,
deleteActivePattern,
duplicateActivePattern,
@ -14,6 +16,8 @@ import {
useSettings,
} from '../../settings.mjs';
import * as tunes from '../tunes.mjs';
import { useStore } from '@nanostores/react';
import { getMetadata } from '../../metadata_parser';
function classNames(...classes) {
return classes.filter(Boolean).join(' ');
@ -22,6 +26,8 @@ function classNames(...classes) {
export function PatternsTab({ context }) {
const { userPatterns } = useSettings();
const activePattern = useActivePattern();
const featuredPatterns = useStore($featuredPatterns);
const publicPatterns = useStore($publicPatterns);
const isExample = useMemo(() => activePattern && !!tunes[activePattern], [activePattern]);
return (
<div className="px-4 w-full dark:text-white text-stone-900 space-y-4 pb-4">
@ -94,8 +100,52 @@ export function PatternsTab({ context }) {
</button>
</div>
</section>
{featuredPatterns && (
<section>
<h2 className="text-xl mb-2">Featured Patterns</h2>
<div className="font-mono text-sm">
{featuredPatterns.map((pattern) => (
<a
key={pattern.id}
className={classNames(
'mr-4 hover:opacity-50 cursor-pointer block',
pattern.hash === activePattern ? 'outline outline-1' : '',
)}
onClick={() => {
setActivePattern(pattern.hash);
context.handleUpdate(pattern.code, true);
}}
>
<PatternLabel pattern={pattern} />
</a>
))}
</div>
</section>
)}
{publicPatterns && (
<section>
<h2 className="text-xl mb-2">Last Creations</h2>
<div className="font-mono text-sm">
{publicPatterns.map((pattern) => (
<a
key={'public-' + pattern.id}
className={classNames(
'mr-4 hover:opacity-50 cursor-pointer block', // inline-block
pattern.hash === activePattern ? 'outline outline-1' : '',
)}
onClick={() => {
setActivePattern(pattern.hash);
context.handleUpdate(pattern.code, true);
}}
>
<PatternLabel pattern={pattern} />
</a>
))}
</div>
</section>
)}
<section>
<h2 className="text-xl mb-2">Examples</h2>
<h2 className="text-xl mb-2">Stock Examples</h2>
<div className="font-mono text-sm">
{Object.entries(tunes).map(([key, tune]) => (
<a
@ -117,3 +167,12 @@ export function PatternsTab({ context }) {
</div>
);
}
export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
const meta = useMemo(() => getMetadata(pattern.code), [pattern]);
return (
<>
{pattern.id}. {meta.title || pattern.hash} by {Array.isArray(meta.by) ? meta.by.join(',') : 'Anonymous'}
</>
);
}

View File

@ -57,32 +57,36 @@ export function SoundsTab() {
<ImportSoundsButton onComplete={() => settingsMap.setKey('soundsFilter', 'user')} />
</div>
<div className="min-h-0 max-h-full grow overflow-auto font-mono text-sm break-normal">
{soundEntries.map(([name, { data, onTrigger }]) => (
<span
key={name}
className="cursor-pointer hover:opacity-50"
onMouseDown={async () => {
const ctx = getAudioContext();
const params = {
note: ['synth', 'soundfont'].includes(data.type) ? 'a3' : undefined,
s: name,
clip: 1,
release: 0.5,
};
const time = ctx.currentTime + 0.05;
const onended = () => trigRef.current?.node?.disconnect();
trigRef.current = Promise.resolve(onTrigger(time, params, onended));
trigRef.current.then((ref) => {
connectToDestination(ref?.node);
});
}}
>
{' '}
{name}
{data?.type === 'sample' ? `(${getSamples(data.samples)})` : ''}
{data?.type === 'soundfont' ? `(${data.fonts.length})` : ''}
</span>
))}
{soundEntries.map(([name, { data, onTrigger }]) => {
return (
<span
key={name}
className="cursor-pointer hover:opacity-50"
onMouseDown={async () => {
const ctx = getAudioContext();
const params = {
note: ['synth', 'soundfont'].includes(data.type) ? 'a3' : undefined,
s: name,
clip: 1,
release: 0.5,
sustain: 1,
duration: 0.5,
};
const time = ctx.currentTime + 0.05;
const onended = () => trigRef.current?.node?.disconnect();
trigRef.current = Promise.resolve(onTrigger(time, params, onended));
trigRef.current.then((ref) => {
connectToDestination(ref?.node);
});
}}
>
{' '}
{name}
{data?.type === 'sample' ? `(${getSamples(data.samples)})` : ''}
{data?.type === 'soundfont' ? `(${data.fonts.length})` : ''}
</span>
);
})}
{!soundEntries.length ? 'No custom sounds loaded in this pattern (yet).' : ''}
</div>
</div>

View File

@ -1,18 +1,45 @@
import { controls, evalScope, hash2code, logger } from '@strudel.cycles/core';
import { settingPatterns } from '../settings.mjs';
import { settingPatterns, defaultAudioDeviceName } from '../settings.mjs';
import { getAudioContext, initializeAudioOutput, setDefaultAudioContext } from '@strudel.cycles/webaudio';
import { isTauri } from '../tauri.mjs';
import './Repl.css';
import * as tunes from './tunes.mjs';
import { createClient } from '@supabase/supabase-js';
import { nanoid } from 'nanoid';
import { writeText } from '@tauri-apps/api/clipboard';
import { createContext } from 'react';
import { $publicPatterns, $featuredPatterns } from '../settings.mjs';
// Create a single supabase client for interacting with your database
const supabase = createClient(
export const supabase = createClient(
'https://pidxdsxphlhzjnzmifth.supabase.co',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM',
);
export function loadPublicPatterns() {
return supabase.from('code').select().eq('public', true).limit(20).order('id', { ascending: false });
}
export function loadFeaturedPatterns() {
return supabase.from('code').select().eq('featured', true).limit(20).order('id', { ascending: false });
}
async function loadDBPatterns() {
try {
const { data: publicPatterns } = await loadPublicPatterns();
const { data: featuredPatterns } = await loadFeaturedPatterns();
$publicPatterns.set(publicPatterns);
$featuredPatterns.set(featuredPatterns);
} catch (err) {
console.error('error loading patterns');
}
}
if (typeof window !== 'undefined') {
loadDBPatterns();
}
export async function initCode() {
// load code from url hash (either short hash from database or decode long hash)
try {
@ -87,10 +114,13 @@ export async function shareCode(codeToShare) {
logger(`Link already generated!`, 'error');
return;
}
const isPublic = confirm(
'Do you want your pattern to be public? If no, press cancel and you will get just a private link.',
);
// generate uuid in the browser
const hash = nanoid(12);
const shareUrl = window.location.origin + window.location.pathname + '?' + hash;
const { data, error } = await supabase.from('code').insert([{ code: codeToShare, hash }]);
const { error } = await supabase.from('code').insert([{ code: codeToShare, hash, ['public']: isPublic }]);
if (!error) {
lastShared = codeToShare;
// copy shareUrl to clipboard
@ -110,3 +140,37 @@ export async function shareCode(codeToShare) {
logger(message);
}
}
export const ReplContext = createContext(null);
export const getAudioDevices = async () => {
await navigator.mediaDevices.getUserMedia({ audio: true });
let mediaDevices = await navigator.mediaDevices.enumerateDevices();
mediaDevices = mediaDevices.filter((device) => device.kind === 'audiooutput' && device.deviceId !== 'default');
const devicesMap = new Map();
devicesMap.set(defaultAudioDeviceName, '');
mediaDevices.forEach((device) => {
devicesMap.set(device.label, device.deviceId);
});
return devicesMap;
};
export const setAudioDevice = async (id) => {
let audioCtx = getAudioContext();
if (audioCtx.sinkId === id) {
return;
}
await audioCtx.suspend();
await audioCtx.close();
audioCtx = setDefaultAudioContext();
await audioCtx.resume();
const isValidID = (id ?? '').length > 0;
if (isValidID) {
try {
await audioCtx.setSinkId(id);
} catch {
logger('failed to set audio interface', 'warning');
}
}
initializeAudioOutput();
};

View File

@ -1,10 +1,15 @@
import { atom } from 'nanostores';
import { persistentMap, persistentAtom } from '@nanostores/persistent';
import { useStore } from '@nanostores/react';
import { register } from '@strudel.cycles/core';
import * as tunes from './repl/tunes.mjs';
import { defaultAudioDeviceName } from './repl/panel/AudioDeviceSelector';
import { logger } from '@strudel.cycles/core';
export let $publicPatterns = atom([]);
export let $featuredPatterns = atom([]);
export const defaultAudioDeviceName = 'System Standard';
export const defaultSettings = {
activeFooter: 'intro',
keybindings: 'codemirror',
@ -171,11 +176,25 @@ export function updateUserCode(code) {
setActivePattern(example);
return;
}
const publicPattern = $publicPatterns.get().find((pat) => pat.code === code);
if (publicPattern) {
setActivePattern(publicPattern.hash);
return;
}
const featuredPattern = $featuredPatterns.get().find((pat) => pat.code === code);
if (featuredPattern) {
setActivePattern(featuredPattern.hash);
return;
}
if (!activePattern) {
// create new user pattern
activePattern = newUserPattern();
setActivePattern(activePattern);
} else if (!!tunes[activePattern] && code !== tunes[activePattern]) {
} else if (
(!!tunes[activePattern] && code !== tunes[activePattern]) || // fork example tune?
$publicPatterns.get().find((p) => p.hash === activePattern) || // fork public pattern?
$featuredPatterns.get().find((p) => p.hash === activePattern) // fork featured pattern?
) {
// fork example
activePattern = getNextCloneName(activePattern);
setActivePattern(activePattern);