mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-12 06:08:37 +00:00
Merge remote-tracking branch 'upstream/main' into line_wrap
This commit is contained in:
commit
aa51c2b6dd
@ -52,8 +52,7 @@
|
||||
"@strudel.cycles/webaudio": "workspace:*",
|
||||
"@strudel.cycles/xen": "workspace:*",
|
||||
"acorn": "^8.8.1",
|
||||
"dependency-tree": "^9.0.0",
|
||||
"vitest": "^0.28.0"
|
||||
"dependency-tree": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/ui": "^0.28.0",
|
||||
@ -66,6 +65,7 @@
|
||||
"jsdoc-to-markdown": "^8.0.0",
|
||||
"lerna": "^6.6.1",
|
||||
"prettier": "^2.8.8",
|
||||
"rollup-plugin-visualizer": "^5.8.1"
|
||||
"rollup-plugin-visualizer": "^5.8.1",
|
||||
"vitest": "^0.28.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Pattern, sequence } from './pattern.mjs';
|
||||
import { Pattern, register, sequence } from './pattern.mjs';
|
||||
import { zipWith } from './util.mjs';
|
||||
|
||||
const controls = {};
|
||||
@ -810,4 +810,15 @@ generic_params.forEach(([names, ...aliases]) => {
|
||||
controls.createParams = (...names) =>
|
||||
names.reduce((acc, name) => Object.assign(acc, { [name]: controls.createParam(name) }), {});
|
||||
|
||||
controls.adsr = register('adsr', (adsr, pat) => {
|
||||
adsr = !Array.isArray(adsr) ? [adsr] : adsr;
|
||||
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;
|
||||
return pat.set({ decay, sustain });
|
||||
});
|
||||
|
||||
export default controls;
|
||||
|
||||
@ -1582,6 +1582,24 @@ export const range2 = register('range2', function (min, max, pat) {
|
||||
return pat.fromBipolar()._range(min, max);
|
||||
});
|
||||
|
||||
/**
|
||||
* Allows dividing numbers via list notation using ":".
|
||||
* Returns a new pattern with just numbers.
|
||||
* @name ratio
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* ratio("1, 5:4, 3:2").mul(110).freq().s("piano").slow(2)
|
||||
*/
|
||||
export const ratio = register('ratio', (pat) =>
|
||||
pat.fmap((v) => {
|
||||
if (!Array.isArray(v)) {
|
||||
return v;
|
||||
}
|
||||
return v.slice(1).reduce((acc, n) => acc / n, v[0]);
|
||||
}),
|
||||
);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Structural and temporal transformations
|
||||
|
||||
@ -1677,6 +1695,9 @@ export const ply = register('ply', function (factor, pat) {
|
||||
* s("<bd sd> hh").fast(2) // s("[<bd sd> hh]*2")
|
||||
*/
|
||||
export const { fast, density } = register(['fast', 'density'], function (factor, pat) {
|
||||
if (factor === 0) {
|
||||
return silence;
|
||||
}
|
||||
factor = Fraction(factor);
|
||||
const fastQuery = pat.withQueryTime((t) => t.mul(factor));
|
||||
return fastQuery.withHapTime((t) => t.div(factor));
|
||||
@ -1703,6 +1724,9 @@ export const hurry = register('hurry', function (r, pat) {
|
||||
* s("<bd sd> hh").slow(2) // s("[<bd sd> hh]/2")
|
||||
*/
|
||||
export const { slow, sparsity } = register(['slow', 'sparsity'], function (factor, pat) {
|
||||
if (factor === 0) {
|
||||
return silence;
|
||||
}
|
||||
return pat._fast(Fraction(1).div(factor));
|
||||
});
|
||||
|
||||
|
||||
@ -50,6 +50,7 @@ Pattern.prototype.pianoroll = function ({
|
||||
timeframe: timeframeProp,
|
||||
fold = 0,
|
||||
vertical = 0,
|
||||
labels = 0,
|
||||
} = {}) {
|
||||
const ctx = getDrawContext();
|
||||
const w = ctx.canvas.width;
|
||||
@ -87,7 +88,7 @@ Pattern.prototype.pianoroll = function ({
|
||||
const isActive = event.whole.begin <= t && event.whole.end > t;
|
||||
ctx.fillStyle = event.context?.color || inactive;
|
||||
ctx.strokeStyle = event.context?.color || active;
|
||||
ctx.globalAlpha = event.context.velocity ?? 1;
|
||||
ctx.globalAlpha = event.context.velocity ?? event.value?.gain ?? 1;
|
||||
const timePx = scale((event.whole.begin - (flipTime ? to : from)) / timeExtent, ...timeRange);
|
||||
let durationPx = scale(event.duration / timeExtent, 0, timeAxis);
|
||||
const value = getValue(event);
|
||||
@ -114,6 +115,14 @@ Pattern.prototype.pianoroll = function ({
|
||||
];
|
||||
}
|
||||
isActive ? ctx.strokeRect(...coords) : ctx.fillRect(...coords);
|
||||
if (labels) {
|
||||
const label = event.value.note ?? event.value.s + (event.value.n ? `:${event.value.n}` : '');
|
||||
ctx.font = `${barThickness * 0.75}px monospace`;
|
||||
ctx.strokeStyle = 'black';
|
||||
ctx.fillStyle = isActive ? 'white' : 'black';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText(label, ...coords);
|
||||
}
|
||||
});
|
||||
ctx.globalAlpha = 1; // reset!
|
||||
const playheadPosition = scale(-from / timeExtent, ...timeRange);
|
||||
@ -181,6 +190,7 @@ export function pianoroll({
|
||||
timeframe: timeframeProp,
|
||||
fold = 0,
|
||||
vertical = 0,
|
||||
labels = false,
|
||||
ctx,
|
||||
} = {}) {
|
||||
const w = ctx.canvas.width;
|
||||
@ -240,7 +250,7 @@ export function pianoroll({
|
||||
const color = event.value?.color || event.context?.color;
|
||||
ctx.fillStyle = color || inactive;
|
||||
ctx.strokeStyle = color || active;
|
||||
ctx.globalAlpha = event.context.velocity ?? 1;
|
||||
ctx.globalAlpha = event.context.velocity ?? event.value?.gain ?? 1;
|
||||
const timePx = scale((event.whole.begin - (flipTime ? to : from)) / timeExtent, ...timeRange);
|
||||
let durationPx = scale(event.duration / timeExtent, 0, timeAxis);
|
||||
const value = getValue(event);
|
||||
@ -267,6 +277,14 @@ export function pianoroll({
|
||||
];
|
||||
}
|
||||
isActive ? ctx.strokeRect(...coords) : ctx.fillRect(...coords);
|
||||
if (labels) {
|
||||
const label = event.value.note ?? event.value.s + (event.value.n ? `:${event.value.n}` : '');
|
||||
ctx.font = `${barThickness * 0.75}px monospace`;
|
||||
ctx.strokeStyle = 'black';
|
||||
ctx.fillStyle = isActive ? 'white' : 'black';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText(label, ...coords);
|
||||
}
|
||||
});
|
||||
ctx.globalAlpha = 1; // reset!
|
||||
const playheadPosition = scale(-from / timeExtent, ...timeRange);
|
||||
|
||||
40
packages/core/test/solmization.test.js
Normal file
40
packages/core/test/solmization.test.js
Normal file
@ -0,0 +1,40 @@
|
||||
/*test for issue 302 support alternative solmization types */
|
||||
import { sol2note } from '../util.mjs';
|
||||
import { test } from 'vitest';
|
||||
import assert from 'assert';
|
||||
|
||||
test('solmization - letters', () => {
|
||||
const result = sol2note(60, 'letters');
|
||||
const expected = 'C4';
|
||||
assert.equal(result, expected);
|
||||
});
|
||||
|
||||
test('solmization - solfeggio', () => {
|
||||
const result = sol2note(60, 'solfeggio');
|
||||
const expected = 'Do4';
|
||||
assert.equal(result, expected);
|
||||
});
|
||||
|
||||
test('solmization - indian', () => {
|
||||
const result = sol2note(60, 'indian');
|
||||
const expected = 'Sa4';
|
||||
assert.equal(result, expected);
|
||||
});
|
||||
|
||||
test('solmization - german', () => {
|
||||
const result = sol2note(60, 'german');
|
||||
const expected = 'C4';
|
||||
assert.equal(result, expected);
|
||||
});
|
||||
|
||||
test('solmization - byzantine', () => {
|
||||
const result = sol2note(60, 'byzantine');
|
||||
const expected = 'Ni4';
|
||||
assert.equal(result, expected);
|
||||
});
|
||||
|
||||
test('solmization - japanese', () => {
|
||||
const result = sol2note(60, 'japanese');
|
||||
const expected = 'I4';
|
||||
assert.equal(result, expected);
|
||||
});
|
||||
@ -6,12 +6,12 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
// returns true if the given string is a note
|
||||
export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name);
|
||||
export const isNote = (name) => /^[a-gA-G][#bs]*[0-9]?$/.test(name);
|
||||
export const isNote = (name) => /^[a-gA-G][#bsf]*[0-9]?$/.test(name);
|
||||
export const tokenizeNote = (note) => {
|
||||
if (typeof note !== 'string') {
|
||||
return [];
|
||||
}
|
||||
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bs]*)([0-9])?$/)?.slice(1) || [];
|
||||
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)([0-9])?$/)?.slice(1) || [];
|
||||
if (!pc) {
|
||||
return [];
|
||||
}
|
||||
@ -25,7 +25,7 @@ export const noteToMidi = (note) => {
|
||||
throw new Error('not a note: "' + note + '"');
|
||||
}
|
||||
const chroma = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }[pc.toLowerCase()];
|
||||
const offset = acc?.split('').reduce((o, char) => o + { '#': 1, b: -1, s: 1 }[char], 0) || 0;
|
||||
const offset = acc?.split('').reduce((o, char) => o + { '#': 1, b: -1, s: 1, f: -1 }[char], 0) || 0;
|
||||
return (Number(oct) + 1) * 12 + chroma + offset;
|
||||
};
|
||||
export const midiToFreq = (n) => {
|
||||
@ -67,13 +67,14 @@ export const getFreq = (noteOrMidi) => {
|
||||
return midiToFreq(noteToMidi(noteOrMidi));
|
||||
};
|
||||
|
||||
const pcs = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
|
||||
/**
|
||||
* @deprecated does not appear to be referenced or invoked anywhere in the codebase
|
||||
* @deprecated only used in workshop (first-notes)
|
||||
* @noAutocomplete
|
||||
*/
|
||||
export const midi2note = (n) => {
|
||||
const oct = Math.floor(n / 12) - 1;
|
||||
const pc = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'][n % 12];
|
||||
const pc = pcs[n % 12];
|
||||
return pc + oct;
|
||||
};
|
||||
|
||||
@ -212,3 +213,61 @@ export const splitAt = function (index, value) {
|
||||
};
|
||||
|
||||
export const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i]));
|
||||
|
||||
export const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||
|
||||
/* solmization, not used yet */
|
||||
const solfeggio = ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si']; /*solffegio notes*/
|
||||
const indian = [
|
||||
'Sa',
|
||||
'Re',
|
||||
'Ga',
|
||||
'Ma',
|
||||
'Pa',
|
||||
'Dha',
|
||||
'Ni',
|
||||
]; /*indian musical notes, seems like they do not use flats or sharps*/
|
||||
const german = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Hb', 'H']; /*german & dutch musical notes*/
|
||||
const byzantine = [
|
||||
'Ni',
|
||||
'Pab',
|
||||
'Pa',
|
||||
'Voub',
|
||||
'Vou',
|
||||
'Ga',
|
||||
'Dib',
|
||||
'Di',
|
||||
'Keb',
|
||||
'Ke',
|
||||
'Zob',
|
||||
'Zo',
|
||||
]; /*byzantine musical notes*/
|
||||
const japanese = [
|
||||
'I',
|
||||
'Ro',
|
||||
'Ha',
|
||||
'Ni',
|
||||
'Ho',
|
||||
'He',
|
||||
'To',
|
||||
]; /*traditional japanese musical notes, seems like they do not use falts or sharps*/
|
||||
|
||||
const english = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
|
||||
|
||||
export const sol2note = (n, notation = 'letters') => {
|
||||
const pc =
|
||||
notation === 'solfeggio'
|
||||
? solfeggio /*check if its is any of the following*/
|
||||
: notation === 'indian'
|
||||
? indian
|
||||
: notation === 'german'
|
||||
? german
|
||||
: notation === 'byzantine'
|
||||
? byzantine
|
||||
: notation === 'japanese'
|
||||
? japanese
|
||||
: english; /*if not use standard version*/
|
||||
const note = pc[n % 12]; /*calculating the midi value to the note*/
|
||||
const oct = Math.floor(n / 12) - 1;
|
||||
return note + oct;
|
||||
};
|
||||
|
||||
@ -2,12 +2,12 @@ import React, { useMemo } from 'react';
|
||||
import _CodeMirror from '@uiw/react-codemirror';
|
||||
import { EditorView, Decoration } from '@codemirror/view';
|
||||
import { StateField, StateEffect } from '@codemirror/state';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { javascript, javascriptLanguage } from '@codemirror/lang-javascript';
|
||||
import strudelTheme from '../themes/strudel-theme';
|
||||
import './style.css';
|
||||
import { useCallback } from 'react';
|
||||
import { autocompletion } from '@codemirror/autocomplete';
|
||||
//import { strudelAutocomplete } from './Autocomplete';
|
||||
import { strudelAutocomplete } from './Autocomplete';
|
||||
import { vim } from '@replit/codemirror-vim';
|
||||
import { emacs } from '@replit/codemirror-emacs';
|
||||
|
||||
@ -92,10 +92,8 @@ const staticExtensions = [
|
||||
javascript(),
|
||||
highlightField,
|
||||
flashField,
|
||||
javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }),
|
||||
EditorView.lineWrapping,
|
||||
// javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }),
|
||||
// autocompletion({ override: [strudelAutocomplete] }),
|
||||
autocompletion({ override: [] }), // wait for https://github.com/uiwjs/react-codemirror/pull/458
|
||||
];
|
||||
|
||||
export default function CodeMirror({
|
||||
@ -105,6 +103,8 @@ export default function CodeMirror({
|
||||
onSelectionChange,
|
||||
theme,
|
||||
keybindings,
|
||||
isLineNumbersDisplayed,
|
||||
isAutoCompletionEnabled,
|
||||
fontSize = 18,
|
||||
fontFamily = 'monospace',
|
||||
options,
|
||||
@ -116,12 +116,14 @@ export default function CodeMirror({
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
const handleOnCreateEditor = useCallback(
|
||||
(view) => {
|
||||
onViewChanged?.(view);
|
||||
},
|
||||
[onViewChanged],
|
||||
);
|
||||
|
||||
const handleOnUpdate = useCallback(
|
||||
(viewUpdate) => {
|
||||
if (viewUpdate.selectionSet && onSelectionChange) {
|
||||
@ -130,16 +132,27 @@ export default function CodeMirror({
|
||||
},
|
||||
[onSelectionChange],
|
||||
);
|
||||
|
||||
const extensions = useMemo(() => {
|
||||
let _extensions = [...staticExtensions];
|
||||
let bindings = {
|
||||
vim,
|
||||
emacs,
|
||||
};
|
||||
|
||||
if (bindings[keybindings]) {
|
||||
return [...staticExtensions, bindings[keybindings]()];
|
||||
_extensions.push(bindings[keybindings]());
|
||||
}
|
||||
return staticExtensions;
|
||||
}, [keybindings]);
|
||||
|
||||
if (isAutoCompletionEnabled) {
|
||||
_extensions.push(javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }));
|
||||
} else {
|
||||
_extensions.push(autocompletion({ override: [] }));
|
||||
}
|
||||
|
||||
return _extensions;
|
||||
}, [keybindings, isAutoCompletionEnabled]);
|
||||
|
||||
return (
|
||||
<div style={{ fontSize, fontFamily }} className="w-full">
|
||||
<_CodeMirror
|
||||
@ -149,6 +162,7 @@ export default function CodeMirror({
|
||||
onCreateEditor={handleOnCreateEditor}
|
||||
onUpdate={handleOnUpdate}
|
||||
extensions={extensions}
|
||||
basicSetup={{ lineNumbers: isLineNumbersDisplayed }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -18,16 +18,24 @@ export function MiniRepl({
|
||||
tune,
|
||||
hideOutsideView = false,
|
||||
enableKeyboard,
|
||||
onTrigger,
|
||||
drawTime,
|
||||
punchcard,
|
||||
punchcardLabels,
|
||||
onPaint,
|
||||
canvasHeight = 200,
|
||||
fontSize = 18,
|
||||
fontFamily,
|
||||
hideHeader = false,
|
||||
theme,
|
||||
keybindings,
|
||||
isLineNumbersDisplayed,
|
||||
}) {
|
||||
drawTime = drawTime || (punchcard ? [0, 4] : undefined);
|
||||
const evalOnMount = !!drawTime;
|
||||
const drawContext = useCallback(
|
||||
!!drawTime ? (canvasId) => document.querySelector('#' + canvasId)?.getContext('2d') : null,
|
||||
[drawTime],
|
||||
punchcard ? (canvasId) => document.querySelector('#' + canvasId)?.getContext('2d') : null,
|
||||
[punchcard],
|
||||
);
|
||||
const {
|
||||
code,
|
||||
@ -47,7 +55,18 @@ export function MiniRepl({
|
||||
} = useStrudel({
|
||||
initialCode: tune,
|
||||
defaultOutput: webaudioOutput,
|
||||
editPattern: (pat) => (punchcard ? pat.punchcard() : pat),
|
||||
editPattern: (pat, id) => {
|
||||
//pat = pat.withContext((ctx) => ({ ...ctx, id }));
|
||||
if (onTrigger) {
|
||||
pat = pat.onTrigger(onTrigger, false);
|
||||
}
|
||||
if (onPaint) {
|
||||
pat = pat.onPaint(onPaint);
|
||||
} else if (punchcard) {
|
||||
pat = pat.punchcard({ labels: punchcardLabels });
|
||||
}
|
||||
return pat;
|
||||
},
|
||||
getTime,
|
||||
evalOnMount,
|
||||
drawContext,
|
||||
@ -82,7 +101,7 @@ export function MiniRepl({
|
||||
e.preventDefault();
|
||||
flash(view);
|
||||
await activateCode();
|
||||
} else if (e.key === '.') {
|
||||
} else if (e.key === '.' || e.code === 'Period') {
|
||||
stop();
|
||||
e.preventDefault();
|
||||
}
|
||||
@ -101,7 +120,7 @@ export function MiniRepl({
|
||||
// const logId = data?.pattern?.meta?.id;
|
||||
if (logId === replId) {
|
||||
setLog((l) => {
|
||||
return l.concat([e.detail]).slice(-10);
|
||||
return l.concat([e.detail]).slice(-8);
|
||||
});
|
||||
}
|
||||
}, []),
|
||||
@ -109,33 +128,46 @@ export function MiniRepl({
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-t-md bg-background border border-lineHighlight" ref={ref}>
|
||||
<div className="flex justify-between bg-lineHighlight">
|
||||
<div className="flex">
|
||||
<button
|
||||
className={cx(
|
||||
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background',
|
||||
started ? 'animate-pulse' : '',
|
||||
)}
|
||||
onClick={() => togglePlay()}
|
||||
>
|
||||
<Icon type={started ? 'stop' : 'play'} />
|
||||
</button>
|
||||
<button
|
||||
className={cx(
|
||||
'w-16 flex items-center justify-center p-1 text-foreground border-lineHighlight bg-lineHighlight',
|
||||
isDirty ? 'text-foreground hover:bg-background cursor-pointer' : 'opacity-50 cursor-not-allowed',
|
||||
)}
|
||||
onClick={() => activateCode()}
|
||||
>
|
||||
<Icon type="refresh" />
|
||||
</button>
|
||||
{!hideHeader && (
|
||||
<div className="flex justify-between bg-lineHighlight">
|
||||
<div className="flex">
|
||||
<button
|
||||
className={cx(
|
||||
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background',
|
||||
started ? 'animate-pulse' : '',
|
||||
)}
|
||||
onClick={() => togglePlay()}
|
||||
>
|
||||
<Icon type={started ? 'stop' : 'play'} />
|
||||
</button>
|
||||
<button
|
||||
className={cx(
|
||||
'w-16 flex items-center justify-center p-1 text-foreground border-lineHighlight bg-lineHighlight',
|
||||
isDirty ? 'text-foreground hover:bg-background cursor-pointer' : 'opacity-50 cursor-not-allowed',
|
||||
)}
|
||||
onClick={() => activateCode()}
|
||||
>
|
||||
<Icon type="refresh" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{error && <div className="text-right p-1 text-sm text-red-200">{error.message}</div>}
|
||||
</div>
|
||||
)}
|
||||
<div className="overflow-auto relative">
|
||||
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} theme={theme} />}
|
||||
{show && (
|
||||
<CodeMirror6
|
||||
value={code}
|
||||
onChange={setCode}
|
||||
onViewChanged={setView}
|
||||
theme={theme}
|
||||
fontFamily={fontFamily}
|
||||
fontSize={fontSize}
|
||||
keybindings={keybindings}
|
||||
isLineNumbersDisplayed={isLineNumbersDisplayed}
|
||||
/>
|
||||
)}
|
||||
{error && <div className="text-right p-1 text-md text-red-200">{error.message}</div>}
|
||||
</div>
|
||||
{drawTime && (
|
||||
{punchcard && (
|
||||
<canvas
|
||||
id={canvasId}
|
||||
className="w-full pointer-events-none"
|
||||
|
||||
@ -24,3 +24,7 @@
|
||||
.cm-theme-light {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
footer {
|
||||
z-index: 0 !important;
|
||||
}
|
||||
|
||||
@ -29,7 +29,8 @@ function useStrudel({
|
||||
const [pattern, setPattern] = useState();
|
||||
const [started, setStarted] = useState(false);
|
||||
const isDirty = code !== activeCode;
|
||||
const shouldPaint = useCallback((pat) => !!(pat?.context?.onPaint && drawContext), [drawContext]);
|
||||
//const shouldPaint = useCallback((pat) => !!(pat?.context?.onPaint && drawContext), [drawContext]);
|
||||
const shouldPaint = useCallback((pat) => !!pat?.context?.onPaint, []);
|
||||
|
||||
// TODO: make sure this hook reruns when scheduler.started changes
|
||||
const { scheduler, evaluate, start, stop, pause, setCps } = useMemo(
|
||||
|
||||
@ -85,7 +85,12 @@ export async function initAudioOnFirstClick() {
|
||||
}
|
||||
|
||||
let delays = {};
|
||||
const maxfeedback = 0.98;
|
||||
function getDelay(orbit, delaytime, delayfeedback, t) {
|
||||
if (delayfeedback > maxfeedback) {
|
||||
logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`);
|
||||
}
|
||||
delayfeedback = strudel.clamp(delayfeedback, 0, 0.98);
|
||||
if (!delays[orbit]) {
|
||||
const ac = getAudioContext();
|
||||
const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
|
||||
|
||||
105
pnpm-lock.yaml
generated
105
pnpm-lock.yaml
generated
@ -28,9 +28,6 @@ importers:
|
||||
dependency-tree:
|
||||
specifier: ^9.0.0
|
||||
version: 9.0.0
|
||||
vitest:
|
||||
specifier: ^0.28.0
|
||||
version: 0.28.0(@vitest/ui@0.28.0)
|
||||
devDependencies:
|
||||
'@vitest/ui':
|
||||
specifier: ^0.28.0
|
||||
@ -65,6 +62,9 @@ importers:
|
||||
rollup-plugin-visualizer:
|
||||
specifier: ^5.8.1
|
||||
version: 5.9.0
|
||||
vitest:
|
||||
specifier: ^0.28.0
|
||||
version: 0.28.0(@vitest/ui@0.28.0)
|
||||
|
||||
packages/codemirror:
|
||||
dependencies:
|
||||
@ -92,7 +92,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
|
||||
packages/core:
|
||||
dependencies:
|
||||
@ -102,7 +102,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
vitest:
|
||||
specifier: ^0.28.0
|
||||
version: 0.28.0(@vitest/ui@0.28.0)
|
||||
@ -127,7 +127,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
|
||||
packages/core/examples/vite-vanilla-repl-cm6:
|
||||
dependencies:
|
||||
@ -155,7 +155,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.2
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
|
||||
packages/csound:
|
||||
dependencies:
|
||||
@ -171,7 +171,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
|
||||
packages/embed: {}
|
||||
|
||||
@ -204,7 +204,7 @@ importers:
|
||||
version: link:../mini
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
vitest:
|
||||
specifier: ^0.28.0
|
||||
version: 0.28.0(@vitest/ui@0.28.0)
|
||||
@ -223,7 +223,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
|
||||
packages/mini:
|
||||
dependencies:
|
||||
@ -236,7 +236,7 @@ importers:
|
||||
version: 3.0.2
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
vitest:
|
||||
specifier: ^0.28.0
|
||||
version: 0.28.0(@vitest/ui@0.28.0)
|
||||
@ -255,7 +255,7 @@ importers:
|
||||
version: 5.8.1
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
|
||||
packages/react:
|
||||
dependencies:
|
||||
@ -325,7 +325,7 @@ importers:
|
||||
version: 3.3.2
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
|
||||
packages/react/examples/nano-repl:
|
||||
dependencies:
|
||||
@ -380,7 +380,7 @@ importers:
|
||||
version: 3.3.2
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
|
||||
packages/serial:
|
||||
dependencies:
|
||||
@ -390,7 +390,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
|
||||
packages/soundfonts:
|
||||
dependencies:
|
||||
@ -412,7 +412,7 @@ importers:
|
||||
version: 3.3.1
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
|
||||
packages/tonal:
|
||||
dependencies:
|
||||
@ -431,7 +431,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
vitest:
|
||||
specifier: ^0.28.0
|
||||
version: 0.28.0(@vitest/ui@0.28.0)
|
||||
@ -447,7 +447,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
vitest:
|
||||
specifier: ^0.28.0
|
||||
version: 0.28.0(@vitest/ui@0.28.0)
|
||||
@ -469,7 +469,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
vitest:
|
||||
specifier: ^0.28.0
|
||||
version: 0.28.0(@vitest/ui@0.28.0)
|
||||
@ -494,7 +494,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
|
||||
packages/web/examples/repl-example:
|
||||
dependencies:
|
||||
@ -504,7 +504,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.2
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
|
||||
packages/webaudio:
|
||||
dependencies:
|
||||
@ -517,7 +517,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
|
||||
packages/webdirt:
|
||||
dependencies:
|
||||
@ -533,7 +533,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
vitest:
|
||||
specifier: ^0.28.0
|
||||
version: 0.28.0(@vitest/ui@0.28.0)
|
||||
@ -546,7 +546,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(@types/node@18.16.3)
|
||||
version: 4.3.3(@types/node@18.11.18)
|
||||
vitest:
|
||||
specifier: ^0.28.0
|
||||
version: 0.28.0(@vitest/ui@0.28.0)
|
||||
@ -646,6 +646,9 @@ importers:
|
||||
canvas:
|
||||
specifier: ^2.11.2
|
||||
version: 2.11.2
|
||||
claviature:
|
||||
specifier: ^0.1.0
|
||||
version: 0.1.0
|
||||
fraction.js:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
@ -3534,6 +3537,7 @@ packages:
|
||||
|
||||
/@polka/url@1.0.0-next.21:
|
||||
resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
|
||||
dev: true
|
||||
|
||||
/@proload/core@0.3.3:
|
||||
resolution: {integrity: sha512-7dAFWsIK84C90AMl24+N/ProHKm4iw0akcnoKjRvbfHifJZBLhaDsDus1QJmhG12lXj4e/uB/8mB/0aduCW+NQ==}
|
||||
@ -3981,9 +3985,11 @@ packages:
|
||||
resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
|
||||
dependencies:
|
||||
'@types/chai': 4.3.4
|
||||
dev: true
|
||||
|
||||
/@types/chai@4.3.4:
|
||||
resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==}
|
||||
dev: true
|
||||
|
||||
/@types/debug@4.1.7:
|
||||
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
|
||||
@ -4060,6 +4066,7 @@ packages:
|
||||
|
||||
/@types/node@18.11.18:
|
||||
resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==}
|
||||
dev: true
|
||||
|
||||
/@types/node@18.16.3:
|
||||
resolution: {integrity: sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==}
|
||||
@ -4502,7 +4509,7 @@ packages:
|
||||
'@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.21.5)
|
||||
'@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.5)
|
||||
react-refresh: 0.14.0
|
||||
vite: 4.3.3(@types/node@18.16.3)
|
||||
vite: 4.3.3(@types/node@18.11.18)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@ -4513,6 +4520,7 @@ packages:
|
||||
'@vitest/spy': 0.28.0
|
||||
'@vitest/utils': 0.28.0
|
||||
chai: 4.3.7
|
||||
dev: true
|
||||
|
||||
/@vitest/runner@0.28.0:
|
||||
resolution: {integrity: sha512-SXQO9aubp7Hg4DV4D5DP70wJ/4o0krH1gAPrSt+rhEZQbQvMaBJAHWOxEibwzLkklgoHreaMEvETFILkGQWXww==}
|
||||
@ -4520,11 +4528,13 @@ packages:
|
||||
'@vitest/utils': 0.28.0
|
||||
p-limit: 4.0.0
|
||||
pathe: 1.1.0
|
||||
dev: true
|
||||
|
||||
/@vitest/spy@0.28.0:
|
||||
resolution: {integrity: sha512-gYBDQIP0QDvxrscl2Id0BTbzLUbuAzFiFur3eHxH9Yt5cM6YCH/kxBrSHhmXTbu92UenLx53Gwq17u5N0zGNDQ==}
|
||||
dependencies:
|
||||
tinyspy: 1.0.2
|
||||
dev: true
|
||||
|
||||
/@vitest/ui@0.28.0:
|
||||
resolution: {integrity: sha512-ihcVEx8t1gZXMboPGcIvoHk+PxiW5USxDMqnZOeUVIUm+XrRCtoJ96YDXdeR6MyPWeYLBPXfBWSxp5gMqoNSkw==}
|
||||
@ -4534,6 +4544,7 @@ packages:
|
||||
pathe: 1.1.0
|
||||
picocolors: 1.0.0
|
||||
sirv: 2.0.2
|
||||
dev: true
|
||||
|
||||
/@vitest/utils@0.28.0:
|
||||
resolution: {integrity: sha512-Dt+jDZbwriZWzJ5Hi9nAUnz9IPgNb+ACE96tWiXPp/u9NmCYWIWcuNoUOYS8HQyGFz31GiNYGvaZ4ZEDjAgi1g==}
|
||||
@ -4543,6 +4554,7 @@ packages:
|
||||
loupe: 2.3.6
|
||||
picocolors: 1.0.0
|
||||
pretty-format: 27.5.1
|
||||
dev: true
|
||||
|
||||
/@vscode/emmet-helper@2.8.6:
|
||||
resolution: {integrity: sha512-IIB8jbiKy37zN8bAIHx59YmnIelY78CGHtThnibD/d3tQOKRY83bYVi9blwmZVUZh6l9nfkYH3tvReaiNxY9EQ==}
|
||||
@ -4608,6 +4620,7 @@ packages:
|
||||
/acorn-walk@8.2.0:
|
||||
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
dev: true
|
||||
|
||||
/acorn@8.8.2:
|
||||
resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==}
|
||||
@ -4732,6 +4745,7 @@ packages:
|
||||
/ansi-styles@5.2.0:
|
||||
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/ansi-styles@6.2.1:
|
||||
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
||||
@ -4881,6 +4895,7 @@ packages:
|
||||
|
||||
/assertion-error@1.1.0:
|
||||
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
|
||||
dev: true
|
||||
|
||||
/ast-module-types@3.0.0:
|
||||
resolution: {integrity: sha512-CMxMCOCS+4D+DkOQfuZf+vLrSEmY/7xtORwdxs4wtcC1wVgvk2MqFFTwQCFhvWsI4KPU9lcWXPI8DgRiz+xetQ==}
|
||||
@ -5150,6 +5165,7 @@ packages:
|
||||
|
||||
/buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
dev: true
|
||||
|
||||
/buffer@5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||
@ -5200,6 +5216,7 @@ packages:
|
||||
/cac@6.7.14:
|
||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/cacache@16.1.3:
|
||||
resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==}
|
||||
@ -5329,6 +5346,7 @@ packages:
|
||||
loupe: 2.3.6
|
||||
pathval: 1.1.1
|
||||
type-detect: 4.0.8
|
||||
dev: true
|
||||
|
||||
/chalk@2.4.2:
|
||||
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
|
||||
@ -5376,6 +5394,7 @@ packages:
|
||||
|
||||
/check-error@1.0.2:
|
||||
resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
|
||||
dev: true
|
||||
|
||||
/chokidar@3.5.3:
|
||||
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
|
||||
@ -5413,6 +5432,10 @@ packages:
|
||||
resolution: {integrity: sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
/claviature@0.1.0:
|
||||
resolution: {integrity: sha512-Ai12axNwQ7x/F9QAj64RYKsgvi5Y33+X3GUSKAC/9s/adEws8TSSc0efeiqhKNGKBo6rT/c+CSCwSXzXxwxZzQ==}
|
||||
dev: false
|
||||
|
||||
/clean-stack@2.2.0:
|
||||
resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
|
||||
engines: {node: '>=6'}
|
||||
@ -5450,6 +5473,7 @@ packages:
|
||||
dependencies:
|
||||
slice-ansi: 5.0.0
|
||||
string-width: 5.1.2
|
||||
dev: true
|
||||
|
||||
/cli-width@3.0.0:
|
||||
resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
|
||||
@ -5929,6 +5953,7 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
type-detect: 4.0.8
|
||||
dev: true
|
||||
|
||||
/deep-extend@0.6.0:
|
||||
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
||||
@ -7127,6 +7152,7 @@ packages:
|
||||
|
||||
/get-func-name@2.0.0:
|
||||
resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
|
||||
dev: true
|
||||
|
||||
/get-intrinsic@1.2.0:
|
||||
resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==}
|
||||
@ -7958,6 +7984,7 @@ packages:
|
||||
/is-fullwidth-code-point@4.0.0:
|
||||
resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
|
||||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/is-glob@4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
@ -8601,6 +8628,7 @@ packages:
|
||||
/local-pkg@0.4.3:
|
||||
resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
|
||||
engines: {node: '>=14'}
|
||||
dev: true
|
||||
|
||||
/locate-path@2.0.0:
|
||||
resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==}
|
||||
@ -8693,6 +8721,7 @@ packages:
|
||||
resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==}
|
||||
dependencies:
|
||||
get-func-name: 2.0.0
|
||||
dev: true
|
||||
|
||||
/lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
@ -9587,6 +9616,7 @@ packages:
|
||||
pathe: 1.1.0
|
||||
pkg-types: 1.0.2
|
||||
ufo: 1.1.1
|
||||
dev: true
|
||||
|
||||
/modify-values@1.0.1:
|
||||
resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==}
|
||||
@ -9632,6 +9662,7 @@ packages:
|
||||
/mrmime@1.0.1:
|
||||
resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/ms@2.0.0:
|
||||
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
||||
@ -10312,6 +10343,7 @@ packages:
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dependencies:
|
||||
yocto-queue: 1.0.0
|
||||
dev: true
|
||||
|
||||
/p-locate@2.0.0:
|
||||
resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==}
|
||||
@ -10564,9 +10596,11 @@ packages:
|
||||
|
||||
/pathe@1.1.0:
|
||||
resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==}
|
||||
dev: true
|
||||
|
||||
/pathval@1.1.1:
|
||||
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
||||
dev: true
|
||||
|
||||
/peggy@3.0.2:
|
||||
resolution: {integrity: sha512-n7chtCbEoGYRwZZ0i/O3t1cPr6o+d9Xx4Zwy2LYfzv0vjchMBU0tO+qYYyvZloBPcgRgzYvALzGWHe609JjEpg==}
|
||||
@ -10643,6 +10677,7 @@ packages:
|
||||
jsonc-parser: 3.2.0
|
||||
mlly: 1.2.0
|
||||
pathe: 1.1.0
|
||||
dev: true
|
||||
|
||||
/pkg@5.8.1:
|
||||
resolution: {integrity: sha512-CjBWtFStCfIiT4Bde9QpJy0KeH19jCfwZRJqHFDFXfhUklCx8JoFmMj3wgnEYIwGmZVNkhsStPHEOnrtrQhEXA==}
|
||||
@ -10844,6 +10879,7 @@ packages:
|
||||
ansi-regex: 5.0.1
|
||||
ansi-styles: 5.2.0
|
||||
react-is: 17.0.2
|
||||
dev: true
|
||||
|
||||
/pretty-format@29.4.3:
|
||||
resolution: {integrity: sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA==}
|
||||
@ -11003,6 +11039,7 @@ packages:
|
||||
|
||||
/react-is@17.0.2:
|
||||
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
||||
dev: true
|
||||
|
||||
/react-is@18.2.0:
|
||||
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
|
||||
@ -11756,6 +11793,7 @@ packages:
|
||||
|
||||
/siginfo@2.0.0:
|
||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||
dev: true
|
||||
|
||||
/signal-exit@3.0.7:
|
||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||
@ -11803,6 +11841,7 @@ packages:
|
||||
'@polka/url': 1.0.0-next.21
|
||||
mrmime: 1.0.1
|
||||
totalist: 3.0.0
|
||||
dev: true
|
||||
|
||||
/sisteransi@1.0.5:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
@ -11821,6 +11860,7 @@ packages:
|
||||
dependencies:
|
||||
ansi-styles: 6.2.1
|
||||
is-fullwidth-code-point: 4.0.0
|
||||
dev: true
|
||||
|
||||
/smart-buffer@4.2.0:
|
||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||
@ -11879,6 +11919,7 @@ packages:
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
source-map: 0.6.1
|
||||
dev: true
|
||||
|
||||
/source-map@0.5.7:
|
||||
resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
|
||||
@ -11961,6 +12002,7 @@ packages:
|
||||
|
||||
/stackback@0.0.2:
|
||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||
dev: true
|
||||
|
||||
/standardized-audio-context@25.3.37:
|
||||
resolution: {integrity: sha512-lr0+RH/IJXYMts95oYKIJ+orTmstOZN3GXWVGmlkbMj8OLahREkRh7DhNGLYgBGDkBkhhc4ev5pYGSFN3gltHw==}
|
||||
@ -11972,6 +12014,7 @@ packages:
|
||||
|
||||
/std-env@3.3.2:
|
||||
resolution: {integrity: sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==}
|
||||
dev: true
|
||||
|
||||
/stdopt@2.2.0:
|
||||
resolution: {integrity: sha512-D/p41NgXOkcj1SeGhfXOwv9z1K6EV3sjAUY5aeepVbgEHv7DpKWLTjhjScyzMWAQCAgUQys1mjH0eArm4cjRGw==}
|
||||
@ -12142,6 +12185,7 @@ packages:
|
||||
resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==}
|
||||
dependencies:
|
||||
acorn: 8.8.2
|
||||
dev: true
|
||||
|
||||
/strong-log-transformer@2.1.0:
|
||||
resolution: {integrity: sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==}
|
||||
@ -12428,14 +12472,17 @@ packages:
|
||||
|
||||
/tinybench@2.5.0:
|
||||
resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==}
|
||||
dev: true
|
||||
|
||||
/tinypool@0.3.1:
|
||||
resolution: {integrity: sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
dev: true
|
||||
|
||||
/tinyspy@1.0.2:
|
||||
resolution: {integrity: sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
dev: true
|
||||
|
||||
/tmp@0.0.33:
|
||||
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
|
||||
@ -12471,6 +12518,7 @@ packages:
|
||||
/totalist@3.0.0:
|
||||
resolution: {integrity: sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
@ -12577,6 +12625,7 @@ packages:
|
||||
/type-detect@4.0.8:
|
||||
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/type-fest@0.13.1:
|
||||
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
|
||||
@ -12677,6 +12726,7 @@ packages:
|
||||
|
||||
/ufo@1.1.1:
|
||||
resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==}
|
||||
dev: true
|
||||
|
||||
/uglify-js@3.17.4:
|
||||
resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==}
|
||||
@ -13052,6 +13102,7 @@ packages:
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
dev: true
|
||||
|
||||
/vite-plugin-pwa@0.14.7(vite@4.3.3)(workbox-build@6.5.4)(workbox-window@6.5.4):
|
||||
resolution: {integrity: sha512-dNJaf0fYOWncmjxv9HiSa2xrSjipjff7IkYE5oIUJ2x5HKu3cXgA8LRgzOwTc5MhwyFYRSU0xyN0Phbx3NsQYw==}
|
||||
@ -13103,6 +13154,7 @@ packages:
|
||||
rollup: 3.21.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/vite@4.3.3(@types/node@18.16.3):
|
||||
resolution: {integrity: sha512-MwFlLBO4udZXd+VBcezo3u8mC77YQk+ik+fbc0GZWGgzfbPP+8Kf0fldhARqvSYmtIWoAJ5BXPClUbMTlqFxrA==}
|
||||
@ -13200,6 +13252,7 @@ packages:
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
dev: true
|
||||
|
||||
/vscode-css-languageservice@6.2.3:
|
||||
resolution: {integrity: sha512-EAyhyIVHpEaf+GjtI+tVe7SekdoANfG0aubnspsQwak3Qkimn/97FpAufNyXk636ngW05pjNKAR9zyTCzo6avQ==}
|
||||
@ -13387,6 +13440,7 @@ packages:
|
||||
dependencies:
|
||||
siginfo: 2.0.0
|
||||
stackback: 0.0.2
|
||||
dev: true
|
||||
|
||||
/wide-align@1.1.5:
|
||||
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
||||
@ -13720,6 +13774,7 @@ packages:
|
||||
/yocto-queue@1.0.0:
|
||||
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
|
||||
engines: {node: '>=12.20'}
|
||||
dev: true
|
||||
|
||||
/zod@3.21.4:
|
||||
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
|
||||
|
||||
@ -3193,6 +3193,23 @@ exports[`runs examples > example "rarely" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "ratio" example index 0 1`] = `
|
||||
[
|
||||
"[ (0/1 → 1/1) ⇝ 2/1 | freq:110 s:piano ]",
|
||||
"[ (0/1 → 1/1) ⇝ 2/1 | freq:137.5 s:piano ]",
|
||||
"[ (0/1 → 1/1) ⇝ 2/1 | freq:165 s:piano ]",
|
||||
"[ 0/1 ⇜ (1/1 → 2/1) | freq:110 s:piano ]",
|
||||
"[ 0/1 ⇜ (1/1 → 2/1) | freq:137.5 s:piano ]",
|
||||
"[ 0/1 ⇜ (1/1 → 2/1) | freq:165 s:piano ]",
|
||||
"[ (2/1 → 3/1) ⇝ 4/1 | freq:110 s:piano ]",
|
||||
"[ (2/1 → 3/1) ⇝ 4/1 | freq:137.5 s:piano ]",
|
||||
"[ (2/1 → 3/1) ⇝ 4/1 | freq:165 s:piano ]",
|
||||
"[ 2/1 ⇜ (3/1 → 4/1) | freq:110 s:piano ]",
|
||||
"[ 2/1 ⇜ (3/1 → 4/1) | freq:137.5 s:piano ]",
|
||||
"[ 2/1 ⇜ (3/1 → 4/1) | freq:165 s:piano ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "release" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:c3 release:0 ]",
|
||||
|
||||
246
test/metadata.test.mjs
Normal file
246
test/metadata.test.mjs
Normal file
@ -0,0 +1,246 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { getMetadata } from '../website/src/pages/metadata_parser';
|
||||
|
||||
describe.concurrent('Metadata parser', () => {
|
||||
it('loads a tag from inline comment', async () => {
|
||||
const tune = `// @title Awesome song`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
title: 'Awesome song',
|
||||
});
|
||||
});
|
||||
|
||||
it('loads many tags from inline comments', async () => {
|
||||
const tune = `// @title Awesome song
|
||||
// @by Sam`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
title: 'Awesome song',
|
||||
by: ['Sam'],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads many tags from one inline comment', async () => {
|
||||
const tune = `// @title Awesome song @by Sam`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
title: 'Awesome song',
|
||||
by: ['Sam'],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads a tag from a block comment', async () => {
|
||||
const tune = `/* @title Awesome song */`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
title: 'Awesome song',
|
||||
});
|
||||
});
|
||||
|
||||
it('loads many tags from a block comment', async () => {
|
||||
const tune = `/*
|
||||
@title Awesome song
|
||||
@by Sam
|
||||
*/`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
title: 'Awesome song',
|
||||
by: ['Sam'],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads many tags from many block comments', async () => {
|
||||
const tune = `/* @title Awesome song */
|
||||
/* @by Sam */`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
title: 'Awesome song',
|
||||
by: ['Sam'],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads many tags from mixed comments', async () => {
|
||||
const tune = `/* @title Awesome song */
|
||||
// @by Sam
|
||||
`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
title: 'Awesome song',
|
||||
by: ['Sam'],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads a title tag with quotes syntax', async () => {
|
||||
const tune = `// "Awesome song"`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
title: 'Awesome song',
|
||||
});
|
||||
});
|
||||
|
||||
it('loads a title tag with quotes syntax among other tags', async () => {
|
||||
const tune = `// "Awesome song" made @by Sam`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
title: 'Awesome song',
|
||||
by: ['Sam'],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads a title tag with quotes syntax from block comment', async () => {
|
||||
const tune = `/* "Awesome song"
|
||||
@by Sam */`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
title: 'Awesome song',
|
||||
by: ['Sam'],
|
||||
});
|
||||
});
|
||||
|
||||
it('does not load a title tag with quotes syntax after a prefix', async () => {
|
||||
const tune = `// I don't care about those "metadata".`;
|
||||
expect(getMetadata(tune)).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('does not load a title tag with quotes syntax after an other comment', async () => {
|
||||
const tune = `// I don't care about those
|
||||
// "metadata"`;
|
||||
expect(getMetadata(tune)).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('does not load a title tag with quotes syntax after other tags', async () => {
|
||||
const tune = `/*
|
||||
@by Sam aka "Lady Strudel"
|
||||
"Sandyyy"
|
||||
*/`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
by: ['Sam aka "Lady Strudel"', '"Sandyyy"'],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads a tag list with comma-separated values syntax', async () => {
|
||||
const tune = `// @by Sam, Sandy`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
by: ['Sam', 'Sandy'],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads a tag list with duplicate keys syntax', async () => {
|
||||
const tune = `// @by Sam
|
||||
// @by Sandy`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
by: ['Sam', 'Sandy'],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads a tag list with duplicate keys syntax, with prefixes', async () => {
|
||||
const tune = `// song @by Sam
|
||||
// samples @by Sandy`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
by: ['Sam', 'Sandy'],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads many tag lists with duplicate keys syntax, within code', async () => {
|
||||
const tune = `note("a3 c#4 e4 a4") // @by Sam @license CC0
|
||||
s("bd hh sd hh") // @by Sandy @license CC BY-NC-SA`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
by: ['Sam', 'Sandy'],
|
||||
license: ['CC0', 'CC BY-NC-SA'],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads a tag list with duplicate keys syntax from block comment', async () => {
|
||||
const tune = `/* @by Sam
|
||||
@by Sandy */`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
by: ['Sam', 'Sandy'],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads a tag list with newline syntax', async () => {
|
||||
const tune = `/*
|
||||
@by Sam
|
||||
Sandy */`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
by: ['Sam', 'Sandy'],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads a multiline tag from block comment', async () => {
|
||||
const tune = `/*
|
||||
@details I wrote this song in February 19th, 2023.
|
||||
It was around midnight and I was lying on
|
||||
the sofa in the living room.
|
||||
*/`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
details:
|
||||
'I wrote this song in February 19th, 2023. ' +
|
||||
'It was around midnight and I was lying on the sofa in the living room.',
|
||||
});
|
||||
});
|
||||
|
||||
it('loads a multiline tag from block comment with duplicate keys', async () => {
|
||||
const tune = `/*
|
||||
@details I wrote this song in February 19th, 2023.
|
||||
@details It was around midnight and I was lying on
|
||||
the sofa in the living room.
|
||||
*/`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
details:
|
||||
'I wrote this song in February 19th, 2023. ' +
|
||||
'It was around midnight and I was lying on the sofa in the living room.',
|
||||
});
|
||||
});
|
||||
|
||||
it('loads a multiline tag from inline comments', async () => {
|
||||
const tune = `// @details I wrote this song in February 19th, 2023.
|
||||
// @details It was around midnight and I was lying on
|
||||
// @details the sofa in the living room.
|
||||
*/`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
details:
|
||||
'I wrote this song in February 19th, 2023. ' +
|
||||
'It was around midnight and I was lying on the sofa in the living room.',
|
||||
});
|
||||
});
|
||||
|
||||
it('loads empty tags from inline comments', async () => {
|
||||
const tune = `// @title
|
||||
// @by`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
title: '',
|
||||
by: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads tags with whitespaces from inline comments', async () => {
|
||||
const tune = ` // @title Awesome song
|
||||
// @by Sam Tagada `;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
title: 'Awesome song',
|
||||
by: ['Sam Tagada'],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads tags with whitespaces from block comment', async () => {
|
||||
const tune = ` /* @title Awesome song
|
||||
@by Sam Tagada */ `;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
title: 'Awesome song',
|
||||
by: ['Sam Tagada'],
|
||||
});
|
||||
});
|
||||
|
||||
it('loads empty tags from block comment', async () => {
|
||||
const tune = `/* @title
|
||||
@by */`;
|
||||
expect(getMetadata(tune)).toStrictEqual({
|
||||
title: '',
|
||||
by: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('does not load tags if there is not', async () => {
|
||||
const tune = `note("a3 c#4 e4 a4")`;
|
||||
expect(getMetadata(tune)).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('does not load code that looks like a metadata tag', async () => {
|
||||
const tune = `const str1 = '@title Awesome song'`;
|
||||
// need a lexer to avoid this one, but it's a pretty rare use case:
|
||||
// const tune = `const str1 = '// @title Awesome song'`;
|
||||
|
||||
expect(getMetadata(tune)).toStrictEqual({});
|
||||
});
|
||||
});
|
||||
@ -4,16 +4,16 @@
|
||||
"version": "0.6.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev": "astro dev --host 0.0.0.0",
|
||||
"start": "astro dev",
|
||||
"check": "astro check && tsc",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview --port 3009",
|
||||
"preview": "astro preview --port 3009 --host 0.0.0.0",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@algolia/client-search": "^4.17.0",
|
||||
"@astrojs/mdx": "^0.19.0",
|
||||
"@astrojs/mdx": "^0.19.0",
|
||||
"@astrojs/react": "^2.1.1",
|
||||
"@astrojs/tailwind": "^3.1.1",
|
||||
"@docsearch/css": "^3.3.4",
|
||||
@ -43,6 +43,7 @@
|
||||
"@uiw/codemirror-themes-all": "^4.19.16",
|
||||
"astro": "^2.3.2",
|
||||
"canvas": "^2.11.2",
|
||||
"claviature": "^0.1.0",
|
||||
"fraction.js": "^4.2.0",
|
||||
"nanoid": "^4.0.2",
|
||||
"nanostores": "^0.8.1",
|
||||
|
||||
BIN
website/public/icons/strudel_icon.png
Normal file
BIN
website/public/icons/strudel_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
10
website/src/components/Box.astro
Normal file
10
website/src/components/Box.astro
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
import LightBulbIcon from '@heroicons/react/20/solid/LightBulbIcon';
|
||||
//import MusicalNoteIcon from '@heroicons/react/20/solid/MusicalNoteIcon';
|
||||
---
|
||||
|
||||
<div class="py-1 px-6 pr-10 bg-lineHighlight relative mb-4">
|
||||
<div><slot /></div>
|
||||
<LightBulbIcon className="w-5 h-5 absolute top-4 right-4" />
|
||||
<!-- <MusicalNoteIcon className="w-5 h-5 absolute top-4 right-4" /> -->
|
||||
</div>
|
||||
24
website/src/components/Claviature.jsx
Normal file
24
website/src/components/Claviature.jsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { getClaviature } from 'claviature';
|
||||
import React from 'react';
|
||||
|
||||
export default function Claviature({ options, onClick, onMouseDown, onMouseUp, onMouseLeave }) {
|
||||
const svg = getClaviature({
|
||||
options,
|
||||
onClick,
|
||||
onMouseDown,
|
||||
onMouseUp,
|
||||
onMouseLeave,
|
||||
});
|
||||
return (
|
||||
<svg {...svg.attributes}>
|
||||
{svg.children.map((el, i) => {
|
||||
const TagName = el.name;
|
||||
return (
|
||||
<TagName key={`${el.name}-${i}`} {...el.attributes}>
|
||||
{el.value}
|
||||
</TagName>
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
// import { getLanguageFromURL } from '../../languages';
|
||||
import { getLanguageFromURL } from '../../languages';
|
||||
import { SIDEBAR } from '../../config';
|
||||
|
||||
type Props = {
|
||||
@ -10,7 +10,7 @@ const { currentPage } = Astro.props as Props;
|
||||
const { BASE_URL } = import.meta.env;
|
||||
let currentPageMatch = currentPage.slice(BASE_URL.length, currentPage.endsWith('/') ? -1 : undefined);
|
||||
|
||||
const langCode = 'en'; // getLanguageFromURL(currentPage);
|
||||
const langCode = getLanguageFromURL(currentPage) || 'en';
|
||||
const sidebar = SIDEBAR[langCode];
|
||||
---
|
||||
|
||||
|
||||
19
website/src/components/QA.tsx
Normal file
19
website/src/components/QA.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import ChevronDownIcon from '@heroicons/react/20/solid/ChevronDownIcon';
|
||||
import ChevronUpIcon from '@heroicons/react/20/solid/ChevronUpIcon';
|
||||
import React from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function QA({ children, q }) {
|
||||
const [visible, setVisible] = useState(false);
|
||||
return (
|
||||
<div className="py-4 px-6 pr-10 bg-lineHighlight relative mb-4">
|
||||
<div className="cursor-pointer" onClick={() => setVisible((v) => !v)}>
|
||||
<div>{q}</div>
|
||||
<a className="p-1 absolute top-4 right-4">
|
||||
{visible ? <ChevronUpIcon className="w-5 h-5" /> : <ChevronDownIcon className="w-5 h-5" />}
|
||||
</a>
|
||||
</div>
|
||||
{visible && <div>{children}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -24,6 +24,7 @@ export type Frontmatter = {
|
||||
|
||||
export const KNOWN_LANGUAGES = {
|
||||
English: 'en',
|
||||
German: 'de',
|
||||
} as const;
|
||||
export const KNOWN_LANGUAGE_CODES = Object.values(KNOWN_LANGUAGES);
|
||||
|
||||
@ -38,25 +39,49 @@ export const ALGOLIA = {
|
||||
apiKey: 'd5044f9d21b80e7721e5b0067a8730b1',
|
||||
};
|
||||
|
||||
export type Sidebar = Record<(typeof KNOWN_LANGUAGE_CODES)[number], Record<string, { text: string; link: string }[]>>;
|
||||
export type SidebarLang = Record<string, { text: string; link: string }[]>;
|
||||
export type Sidebar = Record<(typeof KNOWN_LANGUAGE_CODES)[number], SidebarLang>;
|
||||
export const SIDEBAR: Sidebar = {
|
||||
de: {
|
||||
Workshop: [
|
||||
{ text: 'Intro', link: 'de/workshop/getting-started' },
|
||||
{ text: 'Erste Sounds', link: 'de/workshop/first-sounds' },
|
||||
{ text: 'Erste Töne', link: 'de/workshop/first-notes' },
|
||||
{ text: 'Erste Effekte', link: 'de/workshop/first-effects' },
|
||||
{ text: 'Pattern Effekte', link: 'de/workshop/pattern-effects' },
|
||||
{ text: 'Rückblick', link: 'de/workshop/recap' },
|
||||
{ text: 'Mehr Seiten auf Englisch', link: 'workshop/getting-started' },
|
||||
],
|
||||
},
|
||||
en: {
|
||||
Tutorial: [
|
||||
{ text: 'Getting Started', link: 'learn/getting-started' },
|
||||
{ text: 'Notes', link: 'learn/notes' },
|
||||
{ text: 'Sounds', link: 'learn/sounds' },
|
||||
{ text: 'Coding syntax', link: 'learn/code' },
|
||||
{ text: 'Mini-Notation', link: 'learn/mini-notation' },
|
||||
Workshop: [
|
||||
{ text: 'Getting Started', link: 'workshop/getting-started' },
|
||||
{ text: 'First Sounds', link: 'workshop/first-sounds' },
|
||||
{ text: 'First Notes', link: 'workshop/first-notes' },
|
||||
{ text: 'First Effects', link: 'workshop/first-effects' },
|
||||
{ text: 'Pattern Effects', link: 'workshop/pattern-effects' },
|
||||
{ text: 'Recap', link: 'workshop/recap' },
|
||||
{ text: 'Workshop in German', link: 'de/workshop/getting-started' },
|
||||
],
|
||||
'Making Sound': [
|
||||
{ text: 'Samples', link: 'learn/samples' },
|
||||
{ text: 'Synths', link: 'learn/synths' },
|
||||
{ text: 'Audio Effects', link: 'learn/effects' },
|
||||
{ text: 'MIDI & OSC', link: 'learn/input-output' },
|
||||
],
|
||||
More: [
|
||||
{ text: 'Mini-Notation', link: 'learn/mini-notation' },
|
||||
{ text: 'Coding syntax', link: 'learn/code' },
|
||||
{ text: 'Offline', link: 'learn/pwa' },
|
||||
{ text: 'Patterns', link: 'technical-manual/patterns' },
|
||||
{ text: 'Pattern Alignment', link: 'technical-manual/alignment' },
|
||||
{ text: 'Strudel vs Tidal', link: 'learn/strudel-vs-tidal' },
|
||||
{ text: 'Music metadata', link: 'learn/metadata' },
|
||||
{ text: 'CSound', link: 'learn/csound' },
|
||||
],
|
||||
'Pattern Functions': [
|
||||
{ text: 'Introduction', link: 'functions/intro' },
|
||||
{ text: 'Pattern Constructors', link: 'learn/factories' },
|
||||
{ text: 'Creating Patterns', link: 'learn/factories' },
|
||||
{ text: 'Time Modifiers', link: 'learn/time-modifiers' },
|
||||
{ text: 'Control Parameters', link: 'functions/value-modifiers' },
|
||||
{ text: 'Signals', link: 'learn/signals' },
|
||||
@ -64,13 +89,6 @@ export const SIDEBAR: Sidebar = {
|
||||
{ text: 'Accumulation', link: 'learn/accumulation' },
|
||||
{ text: 'Tonal Modifiers', link: 'learn/tonal' },
|
||||
],
|
||||
More: [
|
||||
{ text: 'MIDI & OSC', link: 'learn/input-output' },
|
||||
{ text: 'Offline', link: 'learn/pwa' },
|
||||
{ text: 'Patterns', link: 'technical-manual/patterns' },
|
||||
{ text: 'Pattern Alignment', link: 'technical-manual/alignment' },
|
||||
{ text: 'Strudel vs Tidal', link: 'learn/strudel-vs-tidal' },
|
||||
],
|
||||
Development: [
|
||||
{ text: 'REPL', link: 'technical-manual/repl' },
|
||||
{ text: 'Sounds', link: 'technical-manual/sounds' },
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
.cm-activeLine {
|
||||
.cm-activeLine,
|
||||
.cm-activeLineGutter {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
@ -7,3 +8,19 @@
|
||||
border: 1px solid var(--lineHighlight);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.cm-scroller {
|
||||
font-family: inherit !important;
|
||||
}
|
||||
|
||||
.cm-gutters {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.cm-cursorLayer {
|
||||
animation-name: inherit !important;
|
||||
}
|
||||
|
||||
.cm-cursor {
|
||||
border-left: 2px solid currentcolor !important;
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { evalScope, controls } from '@strudel.cycles/core';
|
||||
import { evalScope, controls, noteToMidi } from '@strudel.cycles/core';
|
||||
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { prebake } from '../repl/prebake';
|
||||
import { themes, settings } from '../repl/themes.mjs';
|
||||
import './MiniRepl.css';
|
||||
import { useSettings } from '../settings.mjs';
|
||||
import Claviature from '@components/Claviature';
|
||||
|
||||
let modules;
|
||||
if (typeof window !== 'undefined') {
|
||||
@ -27,9 +28,20 @@ if (typeof window !== 'undefined') {
|
||||
prebake();
|
||||
}
|
||||
|
||||
export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) {
|
||||
export function MiniRepl({
|
||||
tune,
|
||||
drawTime,
|
||||
punchcard,
|
||||
punchcardLabels = true,
|
||||
span = [0, 4],
|
||||
canvasHeight = 100,
|
||||
hideHeader,
|
||||
claviature,
|
||||
claviatureLabels,
|
||||
}) {
|
||||
const [Repl, setRepl] = useState();
|
||||
const { theme } = useSettings();
|
||||
const { theme, keybindings, fontSize, fontFamily, isLineNumbersDisplayed } = useSettings();
|
||||
const [activeNotes, setActiveNotes] = useState([]);
|
||||
useEffect(() => {
|
||||
// we have to load this package on the client
|
||||
// because codemirror throws an error on the server
|
||||
@ -42,11 +54,39 @@ export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) {
|
||||
<Repl
|
||||
tune={tune}
|
||||
hideOutsideView={true}
|
||||
drawTime={drawTime}
|
||||
drawTime={claviature ? [0, 0] : drawTime}
|
||||
punchcard={punchcard}
|
||||
punchcardLabels={punchcardLabels}
|
||||
span={span}
|
||||
canvasHeight={canvasHeight}
|
||||
theme={themes[theme]}
|
||||
hideHeader={hideHeader}
|
||||
keybindings={keybindings}
|
||||
fontFamily={fontFamily}
|
||||
fontSize={fontSize}
|
||||
isLineNumbersDisplayed={isLineNumbersDisplayed}
|
||||
onPaint={
|
||||
claviature
|
||||
? (ctx, time, haps, drawTime) => {
|
||||
const active = haps
|
||||
.map((hap) => hap.value.note)
|
||||
.filter(Boolean)
|
||||
.map((n) => (typeof n === 'string' ? noteToMidi(n) : n));
|
||||
setActiveNotes(active);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{claviature && (
|
||||
<Claviature
|
||||
options={{
|
||||
range: ['C2', 'C6'],
|
||||
scaleY: 0.75,
|
||||
colorize: [{ keys: activeNotes, color: 'steelblue' }],
|
||||
labels: claviatureLabels || {},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<pre>{tune}</pre>
|
||||
|
||||
336
website/src/pages/de/workshop/first-effects.mdx
Normal file
336
website/src/pages/de/workshop/first-effects.mdx
Normal file
@ -0,0 +1,336 @@
|
||||
---
|
||||
title: Erste Effekte
|
||||
layout: ../../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '../../../docs/MiniRepl';
|
||||
import QA from '@components/QA';
|
||||
|
||||
# Erste Effekte
|
||||
|
||||
import Box from '@components/Box.astro';
|
||||
|
||||
## Ein paar grundlegende Effekte
|
||||
|
||||
**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)`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
lpf = **l**ow **p**ass **f**ilter
|
||||
|
||||
- Ändere `lpf` in 200. Hörst du wie der Bass dumpfer klingt? Es klingt so ähnlich als würde die Musik hinter einer geschlossenen Tür laufen 🚪
|
||||
- Lass uns nun die Tür öffnen: Ändere `lpf` in 5000. Der Klang wird dadurch viel heller und schärfer ✨🪩
|
||||
|
||||
</Box>
|
||||
|
||||
**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")`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
- Füg noch mehr `lpf` Werte hinzu
|
||||
- Das pattern in `lpf` ändert nicht den Rhythmus der Bassline
|
||||
|
||||
Später sehen wir wie man mit Wellenformen Dinge automatisieren kann.
|
||||
|
||||
</Box>
|
||||
|
||||
**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")`}
|
||||
/>
|
||||
|
||||
**gain = Verstärkung**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`stack(
|
||||
sound("hh*8").gain("[.25 1]*2"),
|
||||
sound("bd*2,~ sd:1")
|
||||
) `}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Bei Rhythmen ist die Dynamik (= Veränderungen der Lautstärke) sehr wichtig.
|
||||
|
||||
- Entferne `.gain(...)` und achte darauf wie es viel flacher klingt.
|
||||
- Mach es rückgängig (strg+z dann strg+enter)
|
||||
|
||||
</Box>
|
||||
|
||||
**stacks in stacks**
|
||||
|
||||
Lass uns die obigen Beispiele kombinieren:
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`stack(
|
||||
stack(
|
||||
sound("hh*8").gain("[.25 1]*2"),
|
||||
sound("bd*2,~ sd:1")
|
||||
),
|
||||
note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
|
||||
.sound("sawtooth").lpf("200 1000"),
|
||||
note("<[c3,g3,e4] [bb2,f3,d4] [a2,f3,c4] [bb2,g3,eb4]>/2")
|
||||
.sound("sawtooth").vowel("<a e i o>/2")
|
||||
) `}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Versuche die einzelnen Teile innerhalb `stack` zu erkennen, schau dir an wie die Kommas gesetzt sind.
|
||||
|
||||
Die 3 Teile (Drums, Bass, Akkorde) sind genau wie vorher, nur in einem `stack`, getrennt durch Kommas
|
||||
|
||||
</Box>
|
||||
|
||||
**Den Sound formen mit ADSR Hüllkurve**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("<c3 bb2 f3 eb3>")
|
||||
.sound("sawtooth").lpf(600)
|
||||
.attack(.1)
|
||||
.decay(.1)
|
||||
.sustain(.25)
|
||||
.release(.2)`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Versuche herauszufinden was die Zahlen machen. Probier folgendes:
|
||||
|
||||
- attack: `.5` vs `0`
|
||||
- decay: `.5` vs `0`
|
||||
- sustain: `1` vs `.25` vs `0`
|
||||
- release: `0` vs `.5` vs `1`
|
||||
|
||||
Kannst du erraten was die einzelnen Werte machen?
|
||||
|
||||
</Box>
|
||||
|
||||
<QA q="Lösung anzeigen" client:visible>
|
||||
|
||||
- attack (anschlagen): Zeit des Aufbaus
|
||||
- decay (zerfallen): Zeit des Abfalls
|
||||
- sustain (erhalten): Lautstärke nach Abfall
|
||||
- release (loslassen): Zeit des Abfalls nach dem Ende
|
||||
|
||||

|
||||
|
||||
</QA>
|
||||
|
||||
**adsr Kurznotation**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("<c3 bb2 f3 eb3>")
|
||||
.sound("sawtooth").lpf(600)
|
||||
.adsr(".1:.1:.5:.2")
|
||||
`}
|
||||
/>
|
||||
|
||||
**delay = Verzögerung**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`stack(
|
||||
note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
|
||||
.sound("gm_electric_guitar_muted"),
|
||||
sound("<bd rim>").bank("RolandTR707")
|
||||
).delay(".5")`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Probier verschiedene `delay` Werte zwischen 0 und 1. Übrigens: `.5` ist kurz für `0.5`.
|
||||
|
||||
Was passiert wenn du `.delay(".8:.125")` schreibst? Kannst du erraten was die zweite Zahl macht?
|
||||
|
||||
Was passiert wenn du `.delay(".8:.06:.8")` schreibst? Kannst du erraten was die dritte Zahl macht?
|
||||
|
||||
</Box>
|
||||
|
||||
<QA q="Lösung anzeigen" client:visible>
|
||||
|
||||
`delay("a:b:c")`:
|
||||
|
||||
- a: Lautstärke des Delays
|
||||
- b: Verzögerungszeit
|
||||
- c: Feedback (je kleiner desto schneller verschwindet das Delay)
|
||||
|
||||
</QA>
|
||||
|
||||
**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")
|
||||
.room(2)`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Spiel mit verschiedenen Werten.
|
||||
|
||||
Füg auch ein Delay hinzu!
|
||||
|
||||
</Box>
|
||||
|
||||
**kleiner dub tune**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`stack(
|
||||
note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
|
||||
.sound("gm_electric_guitar_muted").delay(.5),
|
||||
sound("<bd rim>").bank("RolandTR707").delay(.5),
|
||||
n("<4 [3@3 4] [<2 0> ~@16] ~>/2")
|
||||
.scale("D4:minor").sound("gm_accordion:2")
|
||||
.room(2).gain(.5)
|
||||
)`}
|
||||
/>
|
||||
|
||||
Für echten Dub fehlt noch der Bass:
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`stack(
|
||||
note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
|
||||
.sound("gm_electric_guitar_muted").delay(.5),
|
||||
sound("<bd rim>").bank("RolandTR707").delay(.5),
|
||||
n("<4 [3@3 4] [<2 0> ~@16] ~>/2")
|
||||
.scale("D4:minor").sound("gm_accordion:2")
|
||||
.room(2).gain(.4),
|
||||
n("<0 [~ 0] 4 [3 2] [0 ~] [0 ~] <0 2> ~>*2")
|
||||
.scale("D2:minor")
|
||||
.sound("sawtooth,triangle").lpf(800)
|
||||
)`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Füg `.hush()` ans ende eines Patterns im stack...
|
||||
|
||||
</Box>
|
||||
|
||||
**pan = Panorama**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`sound("numbers:1 numbers:2 numbers:3 numbers:4")
|
||||
.pan("0 0.3 .6 1")
|
||||
.slow(2)`}
|
||||
/>
|
||||
|
||||
**speed = Geschwindigkeit**
|
||||
|
||||
<MiniRepl hideHeader 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)`} />
|
||||
|
||||
<Box>
|
||||
|
||||
Ändere den `slow` Wert. Tausche `slow` durch `fast`.
|
||||
|
||||
Was passiert wenn du den Wert automatisierst? z.b. `.fast("<1 [2 4]>")` ?
|
||||
|
||||
</Box>
|
||||
|
||||
Übrigens, innerhalb der Mini-Notation, `fast` ist `*` und `slow` ist `/`.
|
||||
|
||||
<MiniRepl hideHeader 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} />
|
||||
|
||||
<Box>
|
||||
|
||||
Die grundlegenden Wellenformen sind `sine`, `saw`, `square`, `tri` 🌊
|
||||
|
||||
Probiere auch die zufälligen Signale `rand` und `perlin`!
|
||||
|
||||
Der `gain`-Wert (Verstärkung) wird in der Visualisierung als Transparenz dargestellt.
|
||||
|
||||
</Box>
|
||||
|
||||
**Bereich ändern mit `range`**
|
||||
|
||||
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))`} />
|
||||
|
||||
`range` ist nützlich wenn wir Funktionen mit einem anderen Wertebereich als 0 und 1 automatisieren wollen (z.b. lpf)
|
||||
|
||||
<Box>
|
||||
|
||||
Was passiert wenn du die beiden Werte vertauschst?
|
||||
|
||||
</Box>
|
||||
|
||||
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")
|
||||
.lpf(sine.range(100, 2000).slow(8))`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Die ganze Automation braucht nun 8 cycle bis sie sich wiederholt.
|
||||
|
||||
</Box>
|
||||
|
||||
## Rückblick
|
||||
|
||||
| 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))`} /> |
|
||||
|
||||
Lass uns nun die für Tidal typischen [Pattern Effekte anschauen](/de/workshop/pattern-effects).
|
||||
408
website/src/pages/de/workshop/first-notes.mdx
Normal file
408
website/src/pages/de/workshop/first-notes.mdx
Normal file
@ -0,0 +1,408 @@
|
||||
---
|
||||
title: Erste Töne
|
||||
layout: ../../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '@src/docs/MiniRepl';
|
||||
import { midi2note } from '@strudel.cycles/core/';
|
||||
import Box from '@components/Box.astro';
|
||||
import QA from '@components/QA';
|
||||
|
||||
# Erste Töne
|
||||
|
||||
Jetzt schauen wir uns an wie man mit Tönen mit der `note` Funktion spielt.
|
||||
|
||||
## Zahlen und Noten
|
||||
|
||||
**Töne mit Zahlen**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("48 52 55 59").sound("piano")`}
|
||||
claviature
|
||||
claviatureLabels={Object.fromEntries(
|
||||
Array(49)
|
||||
.fill()
|
||||
.map((_, i) => [midi2note(i + 36), i + 36]),
|
||||
)}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Probiere verschiedene Zahlen aus!
|
||||
|
||||
Versuch auch mal Kommazahlen, z.B. 55.5 (beachte die englische Schreibweise von Kommazahlen mit "." anstatt ",")
|
||||
|
||||
</Box>
|
||||
|
||||
**Töne mit Buchstaben**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("c e g b").sound("piano")`}
|
||||
claviature
|
||||
claviatureLabels={Object.fromEntries(['c3', 'd3', 'e3', 'f3', 'g3', 'a3', 'b3'].map((n) => [n, n.split('')[0]]))}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Versuch verschiedene Buchstaben aus (a - g).
|
||||
|
||||
Findest du Melodien die auch gleichzeitig ein Wort sind? Tipp: ☕ 🙈 🧚
|
||||
|
||||
</Box>
|
||||
|
||||
**Vorzeichen**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("db eb gb ab bb").sound("piano")`}
|
||||
claviature
|
||||
claviatureLabels={Object.fromEntries(
|
||||
['db3', 'eb3', 'gb3', 'ab3', 'bb3'].map((n) => [n, n.split('').slice(0, 2).join('')]),
|
||||
)}
|
||||
/>
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("c# d# f# g# a#").sound("piano")`}
|
||||
claviature
|
||||
claviatureLabels={Object.fromEntries(
|
||||
['c#3', 'd#3', 'f#3', 'g#3', 'a#3'].map((n) => [n, n.split('').slice(0, 2).join('')]),
|
||||
)}
|
||||
/>
|
||||
|
||||
**Andere Oktaven**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("c2 e3 g4 b5").sound("piano")`}
|
||||
claviature
|
||||
claviatureLabels={Object.fromEntries(['c1', 'c2', 'c3', 'c4', 'c5'].map((n) => [n, n]))}
|
||||
claviatureLabels={Object.fromEntries(
|
||||
Array(49)
|
||||
.fill()
|
||||
.map((_, i) => [midi2note(i + 36), midi2note(i + 36)]),
|
||||
)}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Probiere verschiedene Oktaven aus (1-8)
|
||||
|
||||
</Box>
|
||||
|
||||
Normalerweise kommen Leute die keine Noten besser mit Zahlen anstatt mit Buchstaben zurecht.
|
||||
Daher benutzen die folgenden Beispiele meistens Zahlen.
|
||||
Später sehen wir auch noch ein paar Tricks die es uns erleichtern Töne zu spielen die zueinander passen.
|
||||
|
||||
## Den Sound verändern
|
||||
|
||||
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")`} />
|
||||
|
||||
<Box>
|
||||
|
||||
Probier ein paar sounds aus:
|
||||
|
||||
- gm_electric_guitar_muted - E-Gitarre
|
||||
- gm_acoustic_bass - Kontrabass
|
||||
- gm_voice_oohs - Chords
|
||||
- gm_blown_bottle - Flasche
|
||||
- sawtooth - Sägezahn-Welle
|
||||
- square - Rechteck-Welle
|
||||
- triangle - Dreieck-Welle
|
||||
- Was ist mit bd, sd oder hh?
|
||||
- Entferne `.sound('...')` komplett
|
||||
|
||||
</Box>
|
||||
|
||||
**Zwischen Sounds hin und her wechseln**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("48 67 63 [62, 58]")
|
||||
.sound("piano gm_electric_guitar_muted")`}
|
||||
/>
|
||||
|
||||
**Gleichzeitige Sounds**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("48 67 63 [62, 58]")
|
||||
.sound("piano, gm_electric_guitar_muted")`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Die patterns in `note` und `sound` werden kombiniert!
|
||||
|
||||
Wir schauen uns später noch mehr Möglichkeiten an wie man patterns kombiniert.
|
||||
|
||||
</Box>
|
||||
|
||||
## Längere Sequenzen
|
||||
|
||||
**Sequenzen verlangsamen mit `/`**
|
||||
|
||||
{/* [c2 bb1 f2 eb2] */}
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`note("[36 34 41 39]/4").sound("gm_acoustic_bass")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
Das `/4` spielt die Sequenz 4 mal so langsam, also insgesamt 4 cycles = 4s.
|
||||
|
||||
Jede Note ist nun also 1s lang.
|
||||
|
||||
Schreib noch mehr Töne in die Klammern und achte darauf dass es schneller wird.
|
||||
|
||||
</Box>
|
||||
|
||||
Wenn eine Sequenz unabhängig von ihrem Inhalt immer gleich schnell bleiben soll, gibt es noch eine andere Art Klammern:
|
||||
|
||||
**Eins pro Cycle per \< \>**
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`note("<36 34 41 39>").sound("gm_acoustic_bass")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
Füg noch mehr Töne hinzu und achte darauf wie das Tempo gleich bleibt!
|
||||
|
||||
Tatsächlich sind diese Klammern nur eine Abkürzung:
|
||||
|
||||
`<a b c>` = `[a b c]/3`
|
||||
|
||||
`<a b c d>` = `[a b c d]/4`
|
||||
|
||||
usw..
|
||||
|
||||
</Box>
|
||||
|
||||
**Eine Sequenz pro Cycle**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("<[36 48] [34 46] [41 53] [39 51]>")
|
||||
.sound("gm_acoustic_bass")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
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")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
**Alternativen**
|
||||
|
||||
Ä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")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
Das ist auch praktisch für atonale Sounds:
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`sound("bd*2, ~ <sd cp>, [~ hh]*2")
|
||||
.bank("RolandTR909")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
## Skalen
|
||||
|
||||
Es kann mühsam sein die richtigen Noten zu finden wenn man alle zur Verfügung hat.
|
||||
Mit Skalen ist das einfacher:
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`n("0 2 4 <[6,8] [7,9]>")
|
||||
.scale("C:minor").sound("piano")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Probier verschiedene Zahlen aus. Jede klingt gut!
|
||||
|
||||
Probier verschiedene Skalen:
|
||||
|
||||
- C:major
|
||||
- A2:minor
|
||||
- D:dorian
|
||||
- G:mixolydian
|
||||
- A2:minor:pentatonic
|
||||
- F:major:pentatonic
|
||||
|
||||
</Box>
|
||||
|
||||
**Automatisierte 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")
|
||||
.sound("piano")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Wenn du keine Ahnung hast was die Skalen bedeuten, keine Sorge.
|
||||
Es sind einfach nur Namen für verschiedene Gruppen von Tönen die gut zusammenpassen.
|
||||
|
||||
Nimm dir Zeit um herauszufinden welche Skalen du magst.
|
||||
|
||||
</Box>
|
||||
|
||||
## Wiederholen und Verlängern
|
||||
|
||||
**Verlängern mit @**
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`note("c@3 eb").sound("gm_acoustic_bass")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
Ein Element ohne `@` ist gleichbedeutend mit `@1`. Im Beispiel ist `c` drei Einheiten lang, `eb` nur eine.
|
||||
|
||||
Spiel mit der Länge!
|
||||
|
||||
</Box>
|
||||
|
||||
**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")
|
||||
.sound("gm_acoustic_bass")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Dieser Groove wird auch `shuffle` genannt.
|
||||
Jeder Schlag enthält 2 Töne, wobei der erste doppelt so lang wie der zweite ist.
|
||||
Das nennt man auch manchmal `triolen swing`. Es ist ein typischer Rhythmus im Blues und Jazz.
|
||||
|
||||
</Box>
|
||||
|
||||
**Wiederholen**
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`note("c!2 [eb,<g a bb a>]").sound("piano")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
Wechsel zwischen `!`, `*` und `@` hin und her.
|
||||
|
||||
Was ist der Unterschied?
|
||||
|
||||
</Box>
|
||||
|
||||
## Rückblick
|
||||
|
||||
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")`} /> |
|
||||
|
||||
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"))`} /> |
|
||||
|
||||
## 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")
|
||||
.lpf(800) // <-- we'll learn about this soon`}
|
||||
/>
|
||||
|
||||
**Melodie**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`n(\`<
|
||||
[~ 0] 2 [0 2] [~ 2]
|
||||
[~ 0] 1 [0 1] [~ 1]
|
||||
[~ 0] 3 [0 3] [~ 3]
|
||||
[~ 0] 2 [0 2] [~ 2]
|
||||
>*2\`).scale("C4:minor")
|
||||
.sound("gm_synth_strings_1")`}
|
||||
/>
|
||||
|
||||
**Drums**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`sound("bd*2, ~ <sd cp>, [~ hh]*2")
|
||||
.bank("RolandTR909")`}
|
||||
/>
|
||||
|
||||
**Wenn es doch nur einen Weg gäbe das alles gleichzeitig zu spielen.......**
|
||||
|
||||
<Box>
|
||||
|
||||
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")
|
||||
.sound("gm_synth_bass_1").lpf(800),
|
||||
n(\`<
|
||||
[~ 0] 2 [0 2] [~ 2]
|
||||
[~ 0] 1 [0 1] [~ 1]
|
||||
[~ 0] 3 [0 3] [~ 3]
|
||||
[~ 0] 2 [0 2] [~ 2]
|
||||
>*2\`).scale("C4:minor")
|
||||
.sound("gm_synth_strings_1"),
|
||||
sound("bd*2, ~ <sd cp>, [~ hh]*2")
|
||||
.bank("RolandTR909")
|
||||
)`}
|
||||
/>
|
||||
|
||||
Das hört sich doch langsam nach echter Musik an!
|
||||
Wir haben Sounds, wir haben Töne.. noch ein Puzzleteil fehlt: [Effekte](/de/workshop/first-effects)
|
||||
363
website/src/pages/de/workshop/first-sounds.mdx
Normal file
363
website/src/pages/de/workshop/first-sounds.mdx
Normal file
@ -0,0 +1,363 @@
|
||||
---
|
||||
title: Erste Sounds
|
||||
layout: ../../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '@src/docs/MiniRepl';
|
||||
import Box from '@components/Box.astro';
|
||||
import QA from '@components/QA';
|
||||
|
||||
# Erste Sounds
|
||||
|
||||
Dies ist das erste Kapitel im Strudel Workshop, schön dich an Bord zu haben!
|
||||
|
||||
## Textfelder
|
||||
|
||||
Der Workshop ist voller interaktiver Textfelder. Lass uns lernen wie sie funktionieren. Hier ist eins:
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`sound("casio")`} />
|
||||
|
||||
<Box>
|
||||
|
||||
1. ⬆️ Klicke in das obige Textfeld ⬆️
|
||||
2. Drücke `Strg`+`Enter` zum Abspielen
|
||||
3. Ändere `casio` in `metal`
|
||||
4. Drücke `Strg`+`Enter` zum Aktualisieren
|
||||
5. Drücke `Strg`+`Punkt` zum Stoppen
|
||||
|
||||
Mac: `Strg` = `control` oder auch `option`
|
||||
|
||||
</Box>
|
||||
|
||||
Glückwunsch, du kannst jetzt live coden!
|
||||
|
||||
## Sounds
|
||||
|
||||
Gerade haben wir schon den ersten sound mit `sound` abgespielt:
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`sound("casio")`} />
|
||||
|
||||
<Box>
|
||||
|
||||
`casio` ist einer von vielen Standard Sounds.
|
||||
|
||||
Probier ein paar andere Sounds aus:
|
||||
|
||||
```
|
||||
insect wind jazz metal east crow casio space numbers
|
||||
```
|
||||
|
||||
Es kann sein, dass du kurz nichts hörst während ein neuer Sound lädt.
|
||||
|
||||
</Box>
|
||||
|
||||
**Sample Nummer ändern mit :**
|
||||
|
||||
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 />
|
||||
|
||||
<Box>
|
||||
|
||||
Probiere verschiedene Sound / Zahlen Kombinationen.
|
||||
|
||||
Ohne Zahl ist gleichbedeutend mit `:0`
|
||||
|
||||
</Box>
|
||||
|
||||
Jetzt weißt du wie man Sounds abspielt und ihre Sample Nummer einstellt.
|
||||
Vorerst bleiben wir bei den voreingestellten Sounds, später erfahren wir noch wie man eigene benutzt.
|
||||
|
||||
## Drum Sounds
|
||||
|
||||
Strudel kommt von Haus aus mit einer breiten Auswahl an Drum Sounds:
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`sound("bd hh sd oh")`} hideHeader />
|
||||
|
||||
<Box>
|
||||
|
||||
Diese 2-Buchstaben Kombinationen stehen für verschiedene Teile eines Schlagzeugs:
|
||||
|
||||
- `bd` = **b**ass **d**rum - Basstrommel
|
||||
- `sd` = **s**nare **d**rum - Schnarrtrommel
|
||||
- `rim` = **rim**shot - Rahmenschlag
|
||||
- `hh` = **h**i**h**at - Hallo Hut
|
||||
- `oh` = **o**pen **h**ihat - Offener Hallo Hut
|
||||
|
||||
Probier verschiedene Sounds aus!
|
||||
|
||||
</Box>
|
||||
|
||||
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 />
|
||||
|
||||
In diesem Beispiel ist `RolandTR909` der Name der Drum Machine, die eine prägende Rolle für House und Techno Musik spielte.
|
||||
|
||||
<Box>
|
||||
|
||||
Ändere `RolandTR909` in
|
||||
|
||||
- `AkaiLinn`
|
||||
- `RhythmAce`
|
||||
- `RolandTR808`
|
||||
- `RolandTR707`
|
||||
- `ViscoSpaceDrum`
|
||||
|
||||
Es gibt noch viel mehr, aber das sollte fürs Erste reichen..
|
||||
|
||||
🦥 Tipp für faule: Mach Doppel-Klick auf einen Namen um ihn zu markieren.
|
||||
Dann kannst du ihn mit `Strg`+`C` kopieren und mit `Strg`+`V` einfügen.
|
||||
|
||||
</Box>
|
||||
|
||||
## Sequenzen / Sequences
|
||||
|
||||
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 />
|
||||
|
||||
Beachte wie der aktuell gespielte Sound im Code markiert und auch darunter visualisiert wird.
|
||||
|
||||
<Box>
|
||||
|
||||
Versuch noch mehr Sounds hinzuzfügen!
|
||||
|
||||
</Box>
|
||||
|
||||
**Je länger die Sequence, desto schneller**
|
||||
|
||||
<MiniRepl hideHeader 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 />
|
||||
|
||||
<Box>
|
||||
|
||||
cpm = **c**ycles per **m**inute = Cycles pro Minute
|
||||
|
||||
Das Tempo ist standardmäßig auf 60cpm eingestellt, also 1 Cycle pro Sekunde.
|
||||
|
||||
`cpm` ist angelehnt an `bpm` (=beats per minute).
|
||||
|
||||
</Box>
|
||||
|
||||
Wir werden später noch mehr Möglichkeiten kennen lernen das Tempo zu verändern.
|
||||
|
||||
**Pausen mit '~'**
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`sound("bd hh ~ rim")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
Tilde tippen:
|
||||
|
||||
- Windows / Linux: `Alt Gr` + `~`
|
||||
- Mac: `option` + `N`
|
||||
|
||||
</Box>
|
||||
|
||||
**Unter-Sequenzen mit [Klammern]**
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`sound("bd [hh hh] rim [hh hh]")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
Der Inhalt der Klammer wird ebenfalls zusammengequetscht!
|
||||
|
||||
Füge noch mehr Sounds in die Klammern ein!
|
||||
|
||||
</Box>
|
||||
|
||||
Genau wie bei der ganzen Sequence wird eine Unter-Sequence schneller je mehr drin ist.
|
||||
|
||||
**Multiplikation: Dinge schneller machen**
|
||||
|
||||
<MiniRepl hideHeader 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 />
|
||||
|
||||
<Box>
|
||||
|
||||
Tonhöhe = sehr schneller Rhythmus
|
||||
|
||||
</Box>
|
||||
|
||||
**Multiplikation: Ganze Unter-Sequences schneller machen**
|
||||
|
||||
<MiniRepl hideHeader 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)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
**Unter-Unter-Sequenzen mit [[Klammern]]**
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`sound("bd [[rim rim] hh]")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
Du kannst so tief verschachteln wie du willst!
|
||||
|
||||
</Box>
|
||||
|
||||
**Parallele Sequenzen mit Komma**
|
||||
|
||||
<MiniRepl hideHeader 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 />
|
||||
|
||||
Kommas können auch in Unter-Sequenzen verwendet werden:
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`sound("hh hh hh, bd [bd,casio]")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
Ist dir aufgefallen dass sich die letzten beiden Beispiele gleich anhören?
|
||||
|
||||
Es kommt öfter vor, dass man die gleiche Idee auf verschiedene Arten ausdrücken kann.
|
||||
|
||||
</Box>
|
||||
|
||||
**Mehrere Zeilen schreiben mit \` (backtick)**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`sound(\`bd*2, ~ cp,
|
||||
~ ~ ~ oh, hh*4,
|
||||
[~ casio]*2\`)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Ob man " oder \` benutzt ist nur eine Frage der Übersichtlichkeit.
|
||||
|
||||
</Box>
|
||||
|
||||
**Sample Nummer separat auswählen**
|
||||
|
||||
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 />
|
||||
|
||||
Das gleiche kann man auch so schreiben:
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`n("0 1 [4 2] 3*2").sound("jazz")`} punchcard />
|
||||
|
||||
## Rückblick
|
||||
|
||||
Wir haben jetzt die Grundlagen der sogenannten Mini-Notation gelernt, der Rhythmus-Sprache von Tidal.
|
||||
|
||||
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]")`} /> |
|
||||
|
||||
Die mit Apostrophen umgebene Mini-Notation benutzt man normalerweise in eine 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")`} /> |
|
||||
|
||||
## Beispiele
|
||||
|
||||
**Einfacher Rock Beat**
|
||||
|
||||
<MiniRepl hideHeader 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 />
|
||||
|
||||
<Box>
|
||||
|
||||
Ist die aufgefallen dass die letzten 2 Patterns extrem ähnlich sind?
|
||||
Bestimmte Drum Patterns werden oft genreübergreifend wiederverwendet.
|
||||
|
||||
</Box>
|
||||
|
||||
We Will Rock you
|
||||
|
||||
<MiniRepl hideHeader 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")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
**Nachahmung eines 16 step sequencers**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`sound(\`
|
||||
[~ ~ oh ~ ] [~ ~ ~ ~ ] [~ ~ ~ ~ ] [~ ~ ~ ~ ],
|
||||
[hh hh ~ ~ ] [hh ~ hh ~ ] [hh ~ hh ~ ] [hh ~ hh ~ ],
|
||||
[~ ~ ~ ~ ] [cp ~ ~ ~ ] [~ ~ ~ ~ ] [cp ~ ~ ~ ],
|
||||
[bd ~ ~ ~ ] [~ ~ ~ bd] [~ ~ bd ~ ] [~ ~ ~ bd]
|
||||
\`).cpm(90/4)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
**Noch eins**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`sound(\`
|
||||
[~ ~ ~ ~ ] [~ ~ ~ ~ ] [~ ~ ~ ~ ] [~ ~ oh:1 ~ ],
|
||||
[hh hh hh hh] [hh hh hh hh] [hh hh hh hh] [hh hh ~ ~ ],
|
||||
[~ ~ ~ ~ ] [cp ~ ~ ~ ] [~ ~ ~ ~ ] [~ cp ~ ~ ],
|
||||
[bd bd ~ ~ ] [~ ~ bd ~ ] [bd bd ~ bd ] [~ ~ ~ ~ ]
|
||||
\`).bank("RolandTR808").cpm(88/4)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
**Nicht so typische Drums**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`s(\`jazz*2,
|
||||
insect [crow metal] ~ ~,
|
||||
~ space:4 ~ space:1,
|
||||
~ wind\`)
|
||||
.cpm(100/2)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
Jetzt haben wir eine grundlegende Ahnung davon wie man mit Strudel Beats baut!
|
||||
Im nächsten Kapitel werden wir ein paar [Töne spielen](/de/workshop/first-notes).
|
||||
74
website/src/pages/de/workshop/getting-started.mdx
Normal file
74
website/src/pages/de/workshop/getting-started.mdx
Normal file
@ -0,0 +1,74 @@
|
||||
---
|
||||
title: Intro
|
||||
layout: ../../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '../../../docs/MiniRepl';
|
||||
|
||||
# Willkommen
|
||||
|
||||
<img src="/icons/strudel_icon.png" className="w-32 animate-pulse md:float-right ml-8" />
|
||||
|
||||
Willkommen zum Strudel Workshop!
|
||||
Du hast den richtigen Ort gefunden wenn du lernen möchtest wie man mit Code Musik macht.
|
||||
|
||||
## Was ist Strudel
|
||||
|
||||
Mit Strudel kann man dynamische Musikstücke in Echtzeit schreiben.
|
||||
Es ist eine in JavaScript entwickelte Version von [Tidal Cycles](https://tidalcycles.org/) und wurde 2022 von Alex McLean und Felix Roos initiiert.
|
||||
Tidal Cycles, auch bekannt unter dem Namen Tidal, ist eine Computersprache für algorithmische Muster.
|
||||
Obwohl sie meistens für die Erzeugung von Musik eingesetzt wird, kann sie für jede Art von Tätigkeit eingesetzt werden,
|
||||
in der Muster eine Rolle spielen.
|
||||
|
||||
Du brauchst keine Erfahrung in JavaScript oder Tidal Cycles um mit Strudel Musik zu machen.
|
||||
Dieser interaktive Workshop leitet dich spielerisch durch die Grundlagen von Strudel.
|
||||
Der beste Ort um mit Strudel Musik zu machen ist das [Strudel REPL](https://strudel.tidalcycles.org/).
|
||||
|
||||
## Was kann man mit Strudel machen?
|
||||
|
||||
- Musik Live Coding: In Echtzeit mit Code Musik machen
|
||||
- Algorithmische Komposition: Schreibe Musik mithilfe Tidals einzigartiger Sprache für Muster
|
||||
- Lehren: Strudel eignet sich gut für Lehrende, da keine Installation nötig ist und die Sprache kein theoretisches Vorwissen erfordert.
|
||||
- Mit anderen Musik-Anwendungen kombinieren: Per MIDI oder OSC kann Strudel als flexibler Sequencer mit jedem Setup kombiniert werden
|
||||
|
||||
## Beispiel
|
||||
|
||||
Hier ist ein Beispiel wie Strudel klingen kann:
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`stack(
|
||||
// drums
|
||||
s("bd,[~ <sd!3 sd(3,4,2)>],hh*8")
|
||||
.speed(perlin.range(.8,.9)), // random sample speed variation
|
||||
// bassline
|
||||
"<a1 b1\*2 a1(3,8) e2>"
|
||||
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
|
||||
.add(perlin.range(0,.5)) // random pitch variation
|
||||
.superimpose(add(.05)) // add second, slightly detuned voice
|
||||
.note() // wrap in "note"
|
||||
.decay(.15).sustain(0) // make each note of equal length
|
||||
.s('sawtooth') // waveform
|
||||
.gain(.4) // turn down
|
||||
.cutoff(sine.slow(7).range(300,5000)), // automate cutoff
|
||||
// chords
|
||||
"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand')
|
||||
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
||||
.add(perlin.range(0,.5)) // random pitch variation
|
||||
.note() // wrap in "note"
|
||||
.s('sawtooth') // waveform
|
||||
.gain(.16) // turn down
|
||||
.cutoff(500) // fixed cutoff
|
||||
.attack(1) // slowly fade in
|
||||
)
|
||||
.slow(3/2)`}
|
||||
/>
|
||||
|
||||
Mehr Beispiele gibt es [hier](/examples).
|
||||
|
||||
Du kannst auch im [Strudel REPL](https://strudel.tidalcycles.org/) auf `shuffle` klicken um ein zufälliges Beispiel zu hören.
|
||||
|
||||
## Workshop
|
||||
|
||||
Der beste Weg um Strudel zu lernen ist der nun folgende Workshop.
|
||||
Wenn du bereit bist, lass uns loslegen mit deinen [ersten Sounds](/de/workshop/first-sounds).
|
||||
3
website/src/pages/de/workshop/index.astro
Normal file
3
website/src/pages/de/workshop/index.astro
Normal file
@ -0,0 +1,3 @@
|
||||
<script is:inline>
|
||||
window.location.pathname = `/workshop/intro`;
|
||||
</script>
|
||||
183
website/src/pages/de/workshop/pattern-effects.mdx
Normal file
183
website/src/pages/de/workshop/pattern-effects.mdx
Normal file
@ -0,0 +1,183 @@
|
||||
---
|
||||
title: Pattern Effekte
|
||||
layout: ../../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '@src/docs/MiniRepl';
|
||||
import Box from '@components/Box.astro';
|
||||
import QA from '@components/QA';
|
||||
|
||||
# Pattern Effekte
|
||||
|
||||
Bis jetzt sind die meisten Funktionen die wir kennengelernt haben ähnlich wie Funktionen in anderen Musik Programmen: Sequencing von Sounds, Noten und Effekten.
|
||||
|
||||
In diesem Kapitel beschäftigen wir uns mit Funktionen die weniger herkömmlich oder auch enzigartig sind.
|
||||
|
||||
**rev = rückwärts abspielen**
|
||||
|
||||
<MiniRepl hideHeader 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)`} />
|
||||
|
||||
So würde man das ohne jux schreiben:
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`stack(
|
||||
n("0 1 [4 3] 2").sound("jazz").pan(0),
|
||||
n("0 1 [4 3] 2").sound("jazz").pan(1).rev()
|
||||
)`}
|
||||
/>
|
||||
|
||||
Lass uns visualisieren was hier passiert:
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`stack(
|
||||
n("0 1 [4 3] 2").sound("jazz").pan(0).color("cyan"),
|
||||
n("0 1 [4 3] 2").sound("jazz").pan(1).color("magenta").rev()
|
||||
)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Schreibe `//` vor eine der beiden Zeilen im `stack`!
|
||||
|
||||
</Box>
|
||||
|
||||
**mehrere tempos**
|
||||
|
||||
<MiniRepl hideHeader 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'),
|
||||
note("c2, eb3 g3 [bb3 c4]").s("piano").slow(2).color('magenta'),
|
||||
note("c2, eb3 g3 [bb3 c4]").s("piano").slow(3).color('yellow')
|
||||
)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Schreibe wieder `//` vor eine oder mehrere Zeilen im `stack`.
|
||||
|
||||
</Box>
|
||||
|
||||
**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]")
|
||||
.sound("gm_acoustic_bass").room(.5)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Addiert man eine Zahl zu einer Note, verhält sich diese wie eine Zahl.
|
||||
|
||||
z.B. c4 = 60, also ist c4 + 2 = 62
|
||||
|
||||
</Box>
|
||||
|
||||
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]")
|
||||
.sound("gm_acoustic_bass").room(.5)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
**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)
|
||||
.sound("gm_xylophone").room(.5)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
**Alles zusammen**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`stack(
|
||||
n("<0 [2 4] <3 5> [~ <4 1>]>*2".add("<0 [0,2,4]>/4"))
|
||||
.scale("C5:minor")
|
||||
.sound("gm_xylophone")
|
||||
.room(.4).delay(.125),
|
||||
note("c2 [eb3,g3]".add("<0 <1 -1>>"))
|
||||
.adsr("[.1 0]:.2:[1 0]")
|
||||
.sound("gm_acoustic_bass")
|
||||
.room(.5),
|
||||
n("0 1 [2 3] 2").sound("jazz").jux(rev).slow(2)
|
||||
)`}
|
||||
/>
|
||||
|
||||
**ply**
|
||||
|
||||
<MiniRepl hideHeader 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 />
|
||||
|
||||
<Box>
|
||||
|
||||
Probier `ply` mit einem pattern zu automatisieren, z.b. `"<1 2 1 3>"`
|
||||
|
||||
</Box>
|
||||
|
||||
**off**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`n("<0 [4 <3 2>] <2 3> [~ 1]>"
|
||||
.off(1/8, x=>x.add(4))
|
||||
//.off(1/4, x=>x.add(7))
|
||||
).scale("<C5:minor Db5:mixolydian>/4")
|
||||
.s("triangle").room(.5).ds(".1:0").delay(.5)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
In der notation `x=>x.`, das `x` ist das Pattern das wir bearbeiten.
|
||||
|
||||
</Box>
|
||||
|
||||
`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 | description | example |
|
||||
| ---- | --------------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| rev | rückwärts | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6").scale("C:minor").rev()`} /> |
|
||||
| jux | ein 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))`} /> |
|
||||
68
website/src/pages/de/workshop/recap.mdx
Normal file
68
website/src/pages/de/workshop/recap.mdx
Normal file
@ -0,0 +1,68 @@
|
||||
---
|
||||
title: Recap
|
||||
layout: ../../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '../../../docs/MiniRepl';
|
||||
|
||||
# Workshop Rückblick
|
||||
|
||||
Diese Seite ist eine Auflistung aller im Workshop enthaltenen Funktionen.
|
||||
|
||||
## Mini Notation
|
||||
|
||||
| Concept | Syntax | Example |
|
||||
| --------------------- | -------- | -------------------------------------------------------------------------------- |
|
||||
| Sequence | 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-Sequences | \[\] | <MiniRepl hideHeader client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
|
||||
| Unter-Unter-Sequences | \[\[\]\] | <MiniRepl hideHeader client:visible tune={`sound("bd [metal [jazz sn]]")`} /> |
|
||||
| Schneller | \* | <MiniRepl hideHeader client:visible tune={`sound("bd sn*2 cp*3")`} /> |
|
||||
| Slow down | \/ | <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")`} /> |
|
||||
|
||||
## Sounds
|
||||
|
||||
| Name | Description | Example |
|
||||
| ----- | -------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| 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")`} /> |
|
||||
|
||||
## Notes
|
||||
|
||||
| Name | Description | Example |
|
||||
| --------- | ---------------------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| 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"))`} /> |
|
||||
|
||||
## Audio Effekte
|
||||
|
||||
| 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))`} /> |
|
||||
|
||||
## Pattern Effects
|
||||
|
||||
| name | description | example |
|
||||
| ---- | --------------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| 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 | ein 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))`} /> |
|
||||
@ -1,6 +1,8 @@
|
||||
---
|
||||
import * as tunes from '../../../src/repl/tunes.mjs';
|
||||
import HeadCommon from '../../components/HeadCommon.astro';
|
||||
|
||||
import { getMetadata } from '../metadata_parser';
|
||||
---
|
||||
|
||||
<head>
|
||||
@ -12,7 +14,7 @@ import HeadCommon from '../../components/HeadCommon.astro';
|
||||
Object.entries(tunes).map(([name, tune]) => (
|
||||
<a class="rounded-md bg-slate-900 hover:bg-slate-700 cursor-pointer relative" href={`./#${btoa(tune)}`}>
|
||||
<div class="absolute w-full h-full flex justify-center items-center">
|
||||
<span class="bg-slate-800 p-2 rounded-md text-white">{name}</span>
|
||||
<span class="bg-slate-800 p-2 rounded-md text-white">{getMetadata(tune)['title'] || name}</span>
|
||||
</div>
|
||||
<img src={`./img/example-${name}.png`} />
|
||||
</a>
|
||||
|
||||
@ -6,23 +6,24 @@ layout: ../../layouts/MainLayout.astro
|
||||
import { MiniRepl } from '../../docs/MiniRepl';
|
||||
import { JsDoc } from '../../docs/JsDoc';
|
||||
|
||||
# Functional JavaScript API
|
||||
# Pattern Functions
|
||||
|
||||
While the mini notation is powerful on its own, there is much more to discover.
|
||||
Internally, the mini notation will expand to use the actual functional JavaScript API.
|
||||
Let's learn all about functions to create and modify patterns.
|
||||
At the core of Strudel, everything is made of functions.
|
||||
|
||||
For example, this Pattern in Mini Notation:
|
||||
For example, everything you can do with the Mini-Notation can also be done with a function.
|
||||
This Pattern in Mini Notation:
|
||||
|
||||
<MiniRepl client:only="react" tune={`note("c3 eb3 g3")`} />
|
||||
|
||||
is equivalent to this Pattern without Mini Notation:
|
||||
|
||||
<MiniRepl client:only="react" tune={`note(seq(c3, eb3, g3))`} />
|
||||
<MiniRepl client:only="react" tune={`note(seq("c3", "eb3", "g3"))`} />
|
||||
|
||||
Similarly, there is an equivalent function for every aspect of the mini notation.
|
||||
|
||||
Which representation to use is a matter of context. As a rule of thumb, you can think of the JavaScript API
|
||||
to fit better for the larger context, while mini notation is more practical for individiual rhythms.
|
||||
Which representation to use is a matter of context. As a rule of thumb, functions
|
||||
are better suited in a larger context, while mini notation is more practical for individiual rhythms.
|
||||
|
||||
## Limits of Mini Notation
|
||||
|
||||
@ -46,10 +47,10 @@ You can freely mix JS patterns, mini patterns and values! For example, this patt
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`cat(
|
||||
stack(g3,b3,e4),
|
||||
stack(a3,c3,e4),
|
||||
stack(b3,d3,fs4),
|
||||
stack(b3,e4,g4)
|
||||
stack("g3","b3","e4"),
|
||||
stack("a3","c3","e4"),
|
||||
stack("b3","d3","fs4"),
|
||||
stack("b3","e4","g4")
|
||||
).note()`}
|
||||
/>
|
||||
|
||||
@ -72,4 +73,4 @@ You can freely mix JS patterns, mini patterns and values! For example, this patt
|
||||
While mini notation is almost always shorter, it only has a handful of modifiers: \* / ! @.
|
||||
When using JS patterns, there is a lot more you can do.
|
||||
|
||||
What [Pattern Constructors](/learn/factories) does Strudel offer?
|
||||
Next, let's look at how you can [create patterns](/learn/factories)
|
||||
|
||||
@ -139,6 +139,10 @@ This group of functions allows to modify the value of events.
|
||||
|
||||
<JsDoc client:idle name="Pattern.range2" h={0} />
|
||||
|
||||
## ratio
|
||||
|
||||
<JsDoc client:idle name="Pattern.ratio" h={0} />
|
||||
|
||||
# Custom Parameters
|
||||
|
||||
You can also create your own parameters:
|
||||
|
||||
@ -6,31 +6,31 @@ layout: ../../layouts/MainLayout.astro
|
||||
import { MiniRepl } from '../../docs/MiniRepl';
|
||||
import { JsDoc } from '../../docs/JsDoc';
|
||||
|
||||
# Strudel Code
|
||||
# Coding Syntax
|
||||
|
||||
Now that we have played some notes using different sounds, let's take a step back and look how we actually achieved this using _code_.
|
||||
Let's take a step back and understand how the syntax in Strudel works.
|
||||
|
||||
Let's look at this simple example again. What do we notice?
|
||||
Take a look at this simple example:
|
||||
|
||||
<MiniRepl client:idle tune={`freq("220 275 330 440").s("sine")`} />
|
||||
<MiniRepl client:idle tune={`note("c a f e").s("piano")`} />
|
||||
|
||||
- We have a word `freq` which is followed by some brackets `()` with some words/letters/numbers inside, surrounded by quotes `"220 275 330 440"` (corresponding to the pitches a3, c#4, e4, a4).
|
||||
- Then we have a dot `.` followed by another similar piece of code `s("sine")`.
|
||||
- We can also see these texts are _highlighted_ using colours: word `freq` is purple, the brackets `()` are grey, and the content inside the `""` are green.
|
||||
- We have a word `note` which is followed by some brackets `()` with some words/letters/numbers inside, surrounded by quotes `"c a f e"`
|
||||
- Then we have a dot `.` followed by another similar piece of code `s("piano")`.
|
||||
- We can also see these texts are _highlighted_ using colours: word `note` is purple, the brackets `()` are grey, and the content inside the `""` are green. (The colors could be different if you've changed the default theme)
|
||||
|
||||
What happens if we try to 'break' this pattern in different ways?
|
||||
|
||||
<MiniRepl client:idle tune={`freq(220 275 330 440).s(sine)`} />
|
||||
<MiniRepl client:idle tune={`note(c a f e).s(piano)`} />
|
||||
|
||||
<MiniRepl client:idle tune={`freq("220 275 330 440")s("sine")`} />
|
||||
<MiniRepl client:idle tune={`note("c a f e")s("piano")`} />
|
||||
|
||||
<MiniRepl client:idle tune={`freq["220 275 330 440"].s{"sine"}`} />
|
||||
<MiniRepl client:idle tune={`note["c a f e"].s{"piano"}`} />
|
||||
|
||||
Ok, none of these seem to work...
|
||||
|
||||
<MiniRepl client:idle tune={`s("sine").freq("220 275 330 440")`} />
|
||||
<MiniRepl client:idle tune={`s("piano").note("c a f e")`} />
|
||||
|
||||
This one does work, but now we can't hear the four different events and frequencies anymore.
|
||||
This one does work, but now we only hear the first note...
|
||||
|
||||
So what is going on here?
|
||||
|
||||
@ -67,16 +67,17 @@ It is a handy way to quickly turn code on and off.
|
||||
Try uncommenting this line by deleting `//` and refreshing the pattern.
|
||||
You can also use the keyboard shortcut `cmd-/` to toggle comments on and off.
|
||||
|
||||
You might noticed that some comments in the REPL samples include some words starting with a "@", like `@by` or `@license`.
|
||||
Those are just a convention to define some information about the music. We will talk about it in the [Music metadata](/learn/metadata) section.
|
||||
|
||||
# Strings
|
||||
|
||||
Ok, so what about the content inside the quotes (e.g. `"a3 c#4 e4 a4"`)?
|
||||
Ok, so what about the content inside the quotes (e.g. `"c a f e"`)?
|
||||
In JavaScript, as in most programming languages, this content is referred to as being a [_string_](<https://en.wikipedia.org/wiki/String_(computer_science)>).
|
||||
A string is simply a sequence of individual characters.
|
||||
In TidalCycles, double quoted strings are used to write _patterns_ using the mini-notation, and you may hear the phrase _pattern string_ from time to time.
|
||||
If you want to create a regular string and not a pattern, you can use single quotes, e.g. `'C minor'` will not be parsed as Mini Notation.
|
||||
|
||||
The good news is, that this covers 99% of the JavaScript syntax needed for Strudel!
|
||||
|
||||
Let's now look at the way we can express [Rhythms](/learn/mini-notation)...
|
||||
The good news is, that this covers most of the JavaScript syntax needed for Strudel!
|
||||
|
||||
<br />
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: Pattern Constructors
|
||||
title: Creating Patterns
|
||||
layout: ../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '../../docs/MiniRepl';
|
||||
import { JsDoc } from '../../docs/JsDoc';
|
||||
|
||||
# Pattern Constructors
|
||||
# Creating Patterns
|
||||
|
||||
The following functions will return a pattern.
|
||||
These are the equivalents used by the Mini Notation:
|
||||
|
||||
94
website/src/pages/learn/metadata.mdx
Normal file
94
website/src/pages/learn/metadata.mdx
Normal file
@ -0,0 +1,94 @@
|
||||
---
|
||||
title: Music metadata
|
||||
layout: ../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '../../docs/MiniRepl';
|
||||
import { JsDoc } from '../../docs/JsDoc';
|
||||
|
||||
# Music metadata
|
||||
|
||||
You can optionally add some music metadata in your Strudel code, by using tags in code comments:
|
||||
|
||||
```js
|
||||
// @title Hey Hoo
|
||||
// @by Sam Tagada
|
||||
// @license CC BY-NC-SA
|
||||
```
|
||||
|
||||
Like other comments, those are ignored by Strudel, but it can be used by other tools to retrieve some information about the music.
|
||||
|
||||
It is for instance used by the [swatch tool](https://github.com/tidalcycles/strudel/tree/main/my-patterns) to display pattern titles in the [examples page](https://strudel.tidalcycles.org/examples/).
|
||||
|
||||
## Alternative syntax
|
||||
|
||||
You can also use comment blocks:
|
||||
|
||||
```js
|
||||
/*
|
||||
@title Hey Hoo
|
||||
@by Sam Tagada
|
||||
@license CC BY-NC-SA
|
||||
*/
|
||||
```
|
||||
|
||||
Or define multiple tags in one line:
|
||||
|
||||
```js
|
||||
// @title Hey Hoo @by Sam Tagada @license CC BY-NC-SA
|
||||
```
|
||||
|
||||
The `title` tag has an alternative syntax using quotes (must be defined at the very begining):
|
||||
|
||||
```js
|
||||
// "Hey Hoo" @by Sam Tagada
|
||||
```
|
||||
|
||||
## Tags list
|
||||
|
||||
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)
|
||||
- `@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)
|
||||
- `@album`: music album name
|
||||
|
||||
## Multiple values
|
||||
|
||||
Some of them accepts several values, using the comma or new line separator, or duplicating the tag:
|
||||
|
||||
```js
|
||||
/*
|
||||
@by Sam Tagada
|
||||
Jimmy
|
||||
@genre pop, jazz
|
||||
@url https://example.com
|
||||
@url https://example.org
|
||||
*/
|
||||
```
|
||||
|
||||
You can also add optional prefixes and use tags where you want:
|
||||
|
||||
```js
|
||||
/*
|
||||
song @by Sam Tagada
|
||||
samples @by Jimmy
|
||||
*/
|
||||
...
|
||||
note("a3 c#4 e4 a4") // @by Sandy
|
||||
```
|
||||
|
||||
## Multiline
|
||||
|
||||
If a tag doesn't accept a list, it can take multi-line values:
|
||||
|
||||
```js
|
||||
/*
|
||||
@details I wrote this song in February 19th, 2023.
|
||||
It was around midnight and I was lying on
|
||||
the sofa in the living room.
|
||||
*/
|
||||
```
|
||||
@ -8,11 +8,17 @@ import { JsDoc } from '../../docs/JsDoc';
|
||||
|
||||
# Mini-notation
|
||||
|
||||
Similar to [Haskell Tidal Cycles](https://tidalcycles.org/docs/), Strudel has an "embedded mini-notation" (also called a [domain-specific language, or DSL](https://en.wikipedia.org/wiki/Domain-specific_language)) that is designed for writing rhythmic patterns using little amounts of text.
|
||||
If you've seen any Tidal code before, you may have noticed the mini-notation and wondered what it's all about.
|
||||
It's one of the main features of Tidal, and although it might look like a strange way to notate music and other patterns, you will soon see how powerful it can be.
|
||||
Just like [Tidal Cycles](https://tidalcycles.org/), Strudel uses a so called "Mini-Notation", which is a custom language that is designed for writing rhythmic patterns using little amounts of text.
|
||||
|
||||
Before diving deeper into the details, here is a flavour of how the mini-notation looks like:
|
||||
## Note
|
||||
|
||||
This page just explains the entirety of the Mini-Notation syntax.
|
||||
If you are just getting started with Strudel, you can learn the basics of the Mini-Notation in a more practical manner in the [workshop](http://localhost:3000/workshop/first-sounds).
|
||||
After that, you can come back here if you want to understand every little detail.
|
||||
|
||||
## Example
|
||||
|
||||
Before diving deeper into the details, here is a flavour of how the Mini-Notation looks like:
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
|
||||
@ -24,7 +24,7 @@ Instead of numbers, scientific interval notation can be used as well:
|
||||
|
||||
### scale(name)
|
||||
|
||||
Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like scaleTranpose.
|
||||
Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like scaleTranspose.
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
|
||||
34
website/src/pages/metadata_parser.js
Normal file
34
website/src/pages/metadata_parser.js
Normal file
@ -0,0 +1,34 @@
|
||||
const ALLOW_MANY = ['by', 'url', 'genre', 'license'];
|
||||
|
||||
export function getMetadata(raw_code) {
|
||||
const comment_regexp = /\/\*([\s\S]*?)\*\/|\/\/(.*)$/gm;
|
||||
const comments = [...raw_code.matchAll(comment_regexp)].map((c) => (c[1] || c[2] || '').trim());
|
||||
const tags = {};
|
||||
|
||||
const [prefix, title] = (comments[0] || '').split('"');
|
||||
if (prefix.trim() === '' && title !== undefined) {
|
||||
tags['title'] = title;
|
||||
}
|
||||
|
||||
for (const comment of comments) {
|
||||
const tag_matches = comment.split('@').slice(1);
|
||||
for (const tag_match of tag_matches) {
|
||||
let [tag, tag_value] = tag_match.split(/ (.*)/s);
|
||||
tag = tag.trim();
|
||||
tag_value = (tag_value || '').replaceAll(/ +/g, ' ').trim();
|
||||
|
||||
if (ALLOW_MANY.includes(tag)) {
|
||||
const tag_list = tag_value
|
||||
.split(/[,\n]/)
|
||||
.map((t) => t.trim())
|
||||
.filter((t) => t !== '');
|
||||
tags[tag] = tag in tags ? tags[tag].concat(tag_list) : tag_list;
|
||||
} else {
|
||||
tag_value = tag_value.replaceAll(/\s+/g, ' ');
|
||||
tags[tag] = tag in tags ? tags[tag] + ' ' + tag_value : tag_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
@ -1,9 +1,11 @@
|
||||
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]) => [name.split('/').slice(-1), raw]), //
|
||||
Object.entries(my)
|
||||
.filter(([name]) => name.endsWith('.txt'))
|
||||
.map(([name, raw]) => [getMetadata(raw)['title'] || name.split('/').slice(-1), raw]),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
335
website/src/pages/workshop/first-effects.mdx
Normal file
335
website/src/pages/workshop/first-effects.mdx
Normal file
@ -0,0 +1,335 @@
|
||||
---
|
||||
title: First Effects
|
||||
layout: ../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '../../docs/MiniRepl';
|
||||
import QA from '@components/QA';
|
||||
|
||||
# First Effects
|
||||
|
||||
import Box from '@components/Box.astro';
|
||||
|
||||
We have sounds, we have notes, now let's look at effects!
|
||||
|
||||
## Some basic 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)`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
lpf = **l**ow **p**ass **f**ilter
|
||||
|
||||
- Change lpf to 200. Notice how it gets muffled. Think of it as standing in front of the club with the door closed 🚪.
|
||||
- Now let's open the door... change it to 5000. Notice how it gets brighter ✨🪩
|
||||
|
||||
</Box>
|
||||
|
||||
**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")`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
- Try adding more values
|
||||
- Notice how the pattern in lpf does not change the overall rhythm
|
||||
|
||||
We will learn how to automate with waves later...
|
||||
|
||||
</Box>
|
||||
|
||||
**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")`}
|
||||
/>
|
||||
|
||||
**gain**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`stack(
|
||||
sound("hh*8").gain("[.25 1]*2"),
|
||||
sound("bd*2,~ sd:1")
|
||||
) `}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Rhythm is all about dynamics!
|
||||
|
||||
- Remove `.gain(...)` and notice how flat it sounds.
|
||||
- Bring it back by undoing (ctrl+z)
|
||||
|
||||
</Box>
|
||||
|
||||
**stacks within stacks**
|
||||
|
||||
Let's combine all of the above into a little tune:
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`stack(
|
||||
stack(
|
||||
sound("hh*8").gain("[.25 1]*2"),
|
||||
sound("bd*2,~ sd:1")
|
||||
),
|
||||
note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2")
|
||||
.sound("sawtooth").lpf("200 1000"),
|
||||
note("<[c3,g3,e4] [bb2,f3,d4] [a2,f3,c4] [bb2,g3,eb4]>/2")
|
||||
.sound("sawtooth").vowel("<a e i o>/2")
|
||||
) `}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Try to identify the individual parts of the stacks, pay attention to where the commas are.
|
||||
The 3 parts (drums, bassline, chords) are exactly as earlier, just stacked together, separated by comma.
|
||||
|
||||
</Box>
|
||||
|
||||
**shape the sound with an adsr envelope**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("<c3 bb2 f3 eb3>")
|
||||
.sound("sawtooth").lpf(600)
|
||||
.attack(.1)
|
||||
.decay(.1)
|
||||
.sustain(.25)
|
||||
.release(.2)`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Try to find out what the numbers do.. Compare the following
|
||||
|
||||
- attack: `.5` vs `0`
|
||||
- decay: `.5` vs `0`
|
||||
- sustain: `1` vs `.25` vs `0`
|
||||
- release: `0` vs `.5` vs `1`
|
||||
|
||||
Can you guess what they do?
|
||||
|
||||
</Box>
|
||||
|
||||
<QA q="Click to see solution" client:visible>
|
||||
|
||||
- attack: time it takes to fade in
|
||||
- decay: time it takes to fade to sustain
|
||||
- sustain: level after decay
|
||||
- release: time it takes to fade out after note is finished
|
||||
|
||||

|
||||
|
||||
</QA>
|
||||
|
||||
**adsr short notation**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("<c3 bb2 f3 eb3>")
|
||||
.sound("sawtooth").lpf(600)
|
||||
.adsr(".1:.1:.5:.2")
|
||||
`}
|
||||
/>
|
||||
|
||||
**delay**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`stack(
|
||||
note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
|
||||
.sound("gm_electric_guitar_muted"),
|
||||
sound("<bd rim>").bank("RolandTR707")
|
||||
).delay(".5")`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Try some `delay` values between 0 and 1. Btw, `.5` is short for `0.5`
|
||||
|
||||
What happens if you use `.delay(".8:.125")` ? Can you guess what the second number does?
|
||||
|
||||
What happens if you use `.delay(".8:.06:.8")` ? Can you guess what the third number does?
|
||||
|
||||
</Box>
|
||||
|
||||
<QA q="Lösung anzeigen" client:visible>
|
||||
|
||||
`delay("a:b:c")`:
|
||||
|
||||
- a: delay volume
|
||||
- b: delay time
|
||||
- c: feedback (smaller number = quicker fade)
|
||||
|
||||
</QA>
|
||||
|
||||
**room aka reverb**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`n("<4 [3@3 4] [<2 0> ~@16] ~>/2")
|
||||
.scale("D4:minor").sound("gm_accordion:2")
|
||||
.room(2)`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Try different values!
|
||||
|
||||
Add a delay too!
|
||||
|
||||
</Box>
|
||||
|
||||
**little dub tune**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`stack(
|
||||
note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
|
||||
.sound("gm_electric_guitar_muted").delay(.5),
|
||||
sound("<bd rim>").bank("RolandTR707").delay(.5),
|
||||
n("<4 [3@3 4] [<2 0> ~@16] ~>/2")
|
||||
.scale("D4:minor").sound("gm_accordion:2")
|
||||
.room(2).gain(.5)
|
||||
)`}
|
||||
/>
|
||||
|
||||
Let's add a bass to make this complete:
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`stack(
|
||||
note("~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]")
|
||||
.sound("gm_electric_guitar_muted").delay(.5),
|
||||
sound("<bd rim>").bank("RolandTR707").delay(.5),
|
||||
n("<4 [3@3 4] [<2 0> ~@16] ~>/2")
|
||||
.scale("D4:minor").sound("gm_accordion:2")
|
||||
.room(2).gain(.4),
|
||||
n("<0 [~ 0] 4 [3 2] [0 ~] [0 ~] <0 2> ~>*2")
|
||||
.scale("D2:minor")
|
||||
.sound("sawtooth,triangle").lpf(800)
|
||||
)`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Try adding `.hush()` at the end of one of the patterns in the stack...
|
||||
|
||||
</Box>
|
||||
|
||||
**pan**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`sound("numbers:1 numbers:2 numbers:3 numbers:4")
|
||||
.pan("0 0.3 .6 1")
|
||||
.slow(2)`}
|
||||
/>
|
||||
|
||||
**speed**
|
||||
|
||||
<MiniRepl hideHeader 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)`} />
|
||||
|
||||
<Box>
|
||||
|
||||
Change the `slow` value. Try replacing it with `fast`.
|
||||
|
||||
What happens if you use a pattern like `.fast("<1 [2 4]>")`?
|
||||
|
||||
</Box>
|
||||
|
||||
By the way, inside Mini-Notation, `fast` is `*` and `slow` is `/`.
|
||||
|
||||
<MiniRepl hideHeader 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} />
|
||||
|
||||
<Box>
|
||||
|
||||
The basic waveforms for signals are `sine`, `saw`, `square`, `tri` 🌊
|
||||
|
||||
Try also random signals `rand` and `perlin`!
|
||||
|
||||
The gain is visualized as transparency in the pianoroll.
|
||||
|
||||
</Box>
|
||||
|
||||
**setting a range**
|
||||
|
||||
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))`} />
|
||||
|
||||
<Box>
|
||||
|
||||
What happens if you flip the range values?
|
||||
|
||||
</Box>
|
||||
|
||||
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")
|
||||
.lpf(sine.range(100, 2000).slow(8))`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
The whole automation will now take 8 cycles to repeat.
|
||||
|
||||
</Box>
|
||||
|
||||
## 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))`} /> |
|
||||
|
||||
Let us now take a look at some of Tidal's typical [pattern effects](/workshop/pattern-effects).
|
||||
390
website/src/pages/workshop/first-notes.mdx
Normal file
390
website/src/pages/workshop/first-notes.mdx
Normal file
@ -0,0 +1,390 @@
|
||||
---
|
||||
title: First Notes
|
||||
layout: ../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '@src/docs/MiniRepl';
|
||||
import { midi2note } from '@strudel.cycles/core/';
|
||||
import Box from '@components/Box.astro';
|
||||
import QA from '@components/QA';
|
||||
|
||||
# First Notes
|
||||
|
||||
Let's look at how we can play notes
|
||||
|
||||
## numbers and notes
|
||||
|
||||
**play notes with numbers**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("48 52 55 59").sound("piano")`}
|
||||
claviature
|
||||
claviatureLabels={Object.fromEntries(
|
||||
Array(49)
|
||||
.fill()
|
||||
.map((_, i) => [midi2note(i + 36), i + 36]),
|
||||
)}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Try out different numbers!
|
||||
|
||||
Try decimal numbers, like 55.5
|
||||
|
||||
</Box>
|
||||
|
||||
**play notes with letters**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("c e g b").sound("piano")`}
|
||||
claviature
|
||||
claviatureLabels={Object.fromEntries(['c3', 'd3', 'e3', 'f3', 'g3', 'a3', 'b3'].map((n) => [n, n.split('')[0]]))}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Try out different letters (a - g).
|
||||
|
||||
Can you find melodies that are actual words? Hint: ☕ 😉 ⚪
|
||||
|
||||
</Box>
|
||||
|
||||
**add flats or sharps to play the black keys**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("db eb gb ab bb").sound("piano")`}
|
||||
claviature
|
||||
claviatureLabels={Object.fromEntries(
|
||||
['db3', 'eb3', 'gb3', 'ab3', 'bb3'].map((n) => [n, n.split('').slice(0, 2).join('')]),
|
||||
)}
|
||||
/>
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("c# d# f# g# a#").sound("piano")`}
|
||||
claviature
|
||||
claviatureLabels={Object.fromEntries(
|
||||
['c#3', 'd#3', 'f#3', 'g#3', 'a#3'].map((n) => [n, n.split('').slice(0, 2).join('')]),
|
||||
)}
|
||||
/>
|
||||
|
||||
**play notes with letters in different octaves**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("c2 e3 g4 b5").sound("piano")`}
|
||||
claviature
|
||||
claviatureLabels={Object.fromEntries(['c1', 'c2', 'c3', 'c4', 'c5'].map((n) => [n, n]))}
|
||||
claviatureLabels={Object.fromEntries(
|
||||
Array(49)
|
||||
.fill()
|
||||
.map((_, i) => [midi2note(i + 36), midi2note(i + 36)]),
|
||||
)}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Try out different octaves (1-8)
|
||||
|
||||
</Box>
|
||||
|
||||
If you are not comfortable with the note letter system, it should be easier to use numbers instead.
|
||||
Most of the examples below will use numbers for that reason.
|
||||
We will also look at ways to make it easier to play the right notes later.
|
||||
|
||||
## changing the sound
|
||||
|
||||
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")`} />
|
||||
|
||||
{/* c2 g2, e3 b3 d4 e4 */}
|
||||
|
||||
<Box>
|
||||
|
||||
Try out different sounds:
|
||||
|
||||
- gm_electric_guitar_muted
|
||||
- gm_acoustic_bass
|
||||
- gm_voice_oohs
|
||||
- gm_blown_bottle
|
||||
- sawtooth
|
||||
- square
|
||||
- triangle
|
||||
- how about bd, sd or hh?
|
||||
- remove `.sound('...')` completely
|
||||
|
||||
</Box>
|
||||
|
||||
**switch between sounds**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("48 67 63 [62, 58]")
|
||||
.sound("piano gm_electric_guitar_muted")`}
|
||||
/>
|
||||
|
||||
**stack multiple sounds**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("48 67 63 [62, 58]")
|
||||
.sound("piano, gm_electric_guitar_muted")`}
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
The `note` and `sound` patterns are combined!
|
||||
|
||||
We will see more ways to combine patterns later..
|
||||
|
||||
</Box>
|
||||
|
||||
## Longer Sequences
|
||||
|
||||
**Divide sequences with `/` to slow them down**
|
||||
|
||||
{/* [c2 bb1 f2 eb2] */}
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`note("[36 34 41 39]/4").sound("gm_acoustic_bass")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
The `/4` plays the sequence in brackets over 4 cycles (=4s).
|
||||
|
||||
So each of the 4 notes is 1s long.
|
||||
|
||||
Try adding more notes inside the brackets and notice how it gets faster.
|
||||
|
||||
</Box>
|
||||
|
||||
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 />
|
||||
|
||||
<Box>
|
||||
|
||||
Try adding more notes inside the brackets and notice how it does **not** get faster.
|
||||
|
||||
</Box>
|
||||
|
||||
**Play one sequence per cycle**
|
||||
|
||||
{/* <[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")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
**Alternate between multiple things**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("60 <63 62 65 63>")
|
||||
.sound("gm_xylophone")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
This is also useful for unpitched sounds:
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`sound("bd*2, ~ <sd cp>, [~ hh]*2")
|
||||
.bank("RolandTR909")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
## Scales
|
||||
|
||||
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")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Try out different numbers. Any number should sound good!
|
||||
|
||||
Try out different scales:
|
||||
|
||||
- C:major
|
||||
- A2:minor
|
||||
- D:dorian
|
||||
- G:mixolydian
|
||||
- A2:minor:pentatonic
|
||||
- F:major:pentatonic
|
||||
|
||||
</Box>
|
||||
|
||||
**automate 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")
|
||||
.sound("piano")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
If you have no idea what these scale mean, don't worry.
|
||||
These are just labels for different sets of notes that go well together.
|
||||
|
||||
Take your time and you'll find scales you like!
|
||||
|
||||
</Box>
|
||||
|
||||
## Repeat & Elongate
|
||||
|
||||
**Elongate with @**
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`note("c@3 eb").sound("gm_acoustic_bass")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
Not using `@` is like using `@1`. In the above example, c is 3 units long and eb is 1 unit long.
|
||||
|
||||
Try changing that number!
|
||||
|
||||
</Box>
|
||||
|
||||
**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")
|
||||
.sound("gm_acoustic_bass")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
This groove is called a `shuffle`.
|
||||
Each beat has two notes, where the first is twice as long as the second.
|
||||
This is also sometimes called triplet swing. You'll often find it in blues and jazz.
|
||||
|
||||
</Box>
|
||||
|
||||
**Replicate**
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`note("c!2 [eb,<g a bb a>]").sound("piano")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
Try switching between `!`, `*` and `@`
|
||||
|
||||
What's the difference?
|
||||
|
||||
</Box>
|
||||
|
||||
## Recap
|
||||
|
||||
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")`} /> |
|
||||
|
||||
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"))`} /> |
|
||||
|
||||
## 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")
|
||||
.lpf(800) // <-- we'll learn about this soon`}
|
||||
/>
|
||||
|
||||
**Classy Melody**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`n(\`<
|
||||
[~ 0] 2 [0 2] [~ 2]
|
||||
[~ 0] 1 [0 1] [~ 1]
|
||||
[~ 0] 3 [0 3] [~ 3]
|
||||
[~ 0] 2 [0 2] [~ 2]
|
||||
>*2\`).scale("C4:minor")
|
||||
.sound("gm_synth_strings_1")`}
|
||||
/>
|
||||
|
||||
**Classy Drums**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`sound("bd*2, ~ <sd cp>, [~ hh]*2")
|
||||
.bank("RolandTR909")`}
|
||||
/>
|
||||
|
||||
**If there just was a way to play all the above at the same time.......**
|
||||
|
||||
<Box>
|
||||
|
||||
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")
|
||||
.sound("gm_synth_bass_1").lpf(800),
|
||||
n(\`<
|
||||
[~ 0] 2 [0 2] [~ 2]
|
||||
[~ 0] 1 [0 1] [~ 1]
|
||||
[~ 0] 3 [0 3] [~ 3]
|
||||
[~ 0] 2 [0 2] [~ 2]
|
||||
>*2\`).scale("C4:minor")
|
||||
.sound("gm_synth_strings_1"),
|
||||
sound("bd*2, ~ <sd cp>, [~ hh]*2")
|
||||
.bank("RolandTR909")
|
||||
)`}
|
||||
/>
|
||||
|
||||
This is starting to sound like actual music! We have sounds, we have notes, now the last piece of the puzzle is missing: [effects](/workshop/first-effects)
|
||||
330
website/src/pages/workshop/first-sounds.mdx
Normal file
330
website/src/pages/workshop/first-sounds.mdx
Normal file
@ -0,0 +1,330 @@
|
||||
---
|
||||
title: First Sounds
|
||||
layout: ../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '@src/docs/MiniRepl';
|
||||
import Box from '@components/Box.astro';
|
||||
import QA from '@components/QA';
|
||||
|
||||
# First Sounds
|
||||
|
||||
This is the first chapter of the Strudel Workshop, nice to have you on board!
|
||||
|
||||
## Code Fields
|
||||
|
||||
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 />
|
||||
|
||||
<Box>
|
||||
|
||||
1. ⬆️ click into the text field above ⬆️
|
||||
2. press `ctrl`+`enter` to play
|
||||
3. change `casio` to `metal`
|
||||
4. press `ctrl`+`enter` to update
|
||||
5. press `ctrl`+`.` to stop
|
||||
|
||||
</Box>
|
||||
|
||||
Congratulations, you are now live coding!
|
||||
|
||||
## Sounds
|
||||
|
||||
We have just played a sound with `sound` like this:
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`sound("casio")`} hideHeader />
|
||||
|
||||
<Box>
|
||||
|
||||
`casio` is one of many standard sounds.
|
||||
|
||||
Try out a few other sounds:
|
||||
|
||||
```
|
||||
insect wind jazz metal east crow casio space numbers
|
||||
```
|
||||
|
||||
You might hear a little pause while the sound is loading
|
||||
|
||||
</Box>
|
||||
|
||||
**Change Sample Number with :**
|
||||
|
||||
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 />
|
||||
|
||||
<Box>
|
||||
|
||||
Try different sound / sample number combinations.
|
||||
|
||||
Not adding a number is like doing `:0`
|
||||
|
||||
</Box>
|
||||
|
||||
Now you know how to use different sounds.
|
||||
For now we'll stick to this little selection of sounds, but we'll find out how to load your own sounds later.
|
||||
|
||||
## Drum Sounds
|
||||
|
||||
By default, Strudel comes with a wide selection of drum sounds:
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`sound("bd hh sd oh")`} hideHeader />
|
||||
|
||||
<Box>
|
||||
|
||||
These letter combinations stand for different parts of a drum set:
|
||||
|
||||
- `bd` = **b**ass **d**rum
|
||||
- `sd` = **s**nare **d**rum
|
||||
- `sd` = **sd**are
|
||||
- `rim` = **rim**shot
|
||||
- `hh` = **h**i**h**at
|
||||
- `oh` = **o**pen **h**ihat
|
||||
|
||||
Try out different drum sounds!
|
||||
|
||||
</Box>
|
||||
|
||||
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 />
|
||||
|
||||
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.
|
||||
|
||||
<Box>
|
||||
|
||||
Try changing `RolandTR909` to one of
|
||||
|
||||
- `AkaiLinn`
|
||||
- `RhythmAce`
|
||||
- `RolandTR808`
|
||||
- `RolandTR707`
|
||||
- `ViscoSpaceDrum`
|
||||
|
||||
There are a lot more, but let's keep it simple for now
|
||||
|
||||
🦥 Pro-Tip: Mark a name via double click. Then just copy and paste!
|
||||
|
||||
</Box>
|
||||
|
||||
## Sequences
|
||||
|
||||
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 />
|
||||
|
||||
Notice how the currently playing sound is highlighted in the code and also visualized below.
|
||||
|
||||
<Box>
|
||||
|
||||
Try adding more sounds to the sequence!
|
||||
|
||||
</Box>
|
||||
|
||||
**The longer the sequence, the faster it runs**
|
||||
|
||||
<MiniRepl hideHeader 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 />
|
||||
|
||||
<Box>
|
||||
|
||||
cpm = cycles per minute
|
||||
|
||||
By default, the tempo is 60 cycles per minute = 1 cycle per second.
|
||||
|
||||
</Box>
|
||||
|
||||
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 />
|
||||
|
||||
**Sub-Sequences with [brackets]**
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`sound("bd [hh hh] sd [hh bd]")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
Try adding more sounds inside a bracket!
|
||||
|
||||
</Box>
|
||||
|
||||
Similar to the whole sequence, the content of a sub-sequence will be squished to the its own length.
|
||||
|
||||
**Multiplication: Speed things up**
|
||||
|
||||
<MiniRepl hideHeader 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 />
|
||||
|
||||
**Multiplication: Speeeeeeeeed things up**
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`sound("bd hh*16 rim hh*8")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
Pitch = really fast rhythm
|
||||
|
||||
</Box>
|
||||
|
||||
**Sub-Sub-Sequences with [[brackets]]**
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`sound("bd [[rim rim] hh]")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
You can go as deep as you want!
|
||||
|
||||
</Box>
|
||||
|
||||
**Play sequences in parallel with comma**
|
||||
|
||||
<MiniRepl hideHeader 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 />
|
||||
|
||||
Commas can also be used inside sub-sequences:
|
||||
|
||||
<MiniRepl hideHeader client:visible tune={`sound("hh hh hh, bd [bd,casio]")`} punchcard />
|
||||
|
||||
<Box>
|
||||
|
||||
Notice how the 2 above are the same?
|
||||
|
||||
It is quite common that there are many ways to express the same idea.
|
||||
|
||||
</Box>
|
||||
|
||||
**Multiple Lines with backticks**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`sound(\`bd*2, ~ cp,
|
||||
~ ~ ~ oh, hh*4,
|
||||
[~ casio]*2\`)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
**selecting sample numbers separately**
|
||||
|
||||
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 />
|
||||
|
||||
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 />
|
||||
|
||||
## 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]")`} /> |
|
||||
|
||||
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")`} /> |
|
||||
|
||||
## Examples
|
||||
|
||||
**Basic rock beat**
|
||||
|
||||
<MiniRepl hideHeader 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 />
|
||||
|
||||
<Box>
|
||||
|
||||
Notice that the two patterns are extremely similar.
|
||||
Certain drum patterns are reused across genres.
|
||||
|
||||
</Box>
|
||||
|
||||
We Will Rock you
|
||||
|
||||
<MiniRepl hideHeader 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")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
**Imitation of a 16 step sequencer**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`sound(\`
|
||||
[~ ~ oh ~ ] [~ ~ ~ ~ ] [~ ~ ~ ~ ] [~ ~ ~ ~ ],
|
||||
[hh hh ~ ~ ] [hh ~ hh ~ ] [hh ~ hh ~ ] [hh ~ hh ~ ],
|
||||
[~ ~ ~ ~ ] [cp ~ ~ ~ ] [~ ~ ~ ~ ] [cp ~ ~ ~ ],
|
||||
[bd ~ ~ ~ ] [~ ~ ~ bd] [~ ~ bd ~ ] [~ ~ ~ bd]
|
||||
\`).cpm(90/4)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
**Another one**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`sound(\`
|
||||
[~ ~ ~ ~ ] [~ ~ ~ ~ ] [~ ~ ~ ~ ] [~ ~ oh:1 ~ ],
|
||||
[hh hh hh hh] [hh hh hh hh] [hh hh hh hh] [hh hh ~ ~ ],
|
||||
[~ ~ ~ ~ ] [cp ~ ~ ~ ] [~ ~ ~ ~ ] [~ cp ~ ~ ],
|
||||
[bd bd ~ ~ ] [~ ~ bd ~ ] [bd bd ~ bd ] [~ ~ ~ ~ ]
|
||||
\`).bank("RolandTR808").cpm(88/4)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
**Not your average drums**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`s(\`jazz*2,
|
||||
insect [crow metal] ~ ~,
|
||||
~ space:4 ~ space:1,
|
||||
~ wind\`)
|
||||
.cpm(100/2)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
Now that we know the basics of how to make beats, let's look at how we can play [notes](/workshop/first-notes)
|
||||
70
website/src/pages/workshop/getting-started.mdx
Normal file
70
website/src/pages/workshop/getting-started.mdx
Normal file
@ -0,0 +1,70 @@
|
||||
---
|
||||
title: Getting Started
|
||||
layout: ../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '../../docs/MiniRepl';
|
||||
|
||||
# Welcome
|
||||
|
||||
<img src="/icons/strudel_icon.png" className="w-32 animate-pulse md:float-right ml-8" />
|
||||
|
||||
Welcome to the Strudel documentation pages!
|
||||
You've come to the right place if you want to learn how to make music with code.
|
||||
|
||||
## What is Strudel?
|
||||
|
||||
With Strudel, you can expressively write dynamic music pieces.<br/>
|
||||
It is an official port of the [Tidal Cycles](https://tidalcycles.org/) pattern language to JavaScript.<br/>
|
||||
You don't need to know JavaScript or Tidal Cycles to make music with Strudel.
|
||||
This interactive tutorial will guide you through the basics of Strudel.<br/>
|
||||
The best place to actually make music with Strudel is the [Strudel REPL](https://strudel.tidalcycles.org/)
|
||||
|
||||
<div className="clear-both" />
|
||||
|
||||
## What can you do with Strudel?
|
||||
|
||||
- live code music: make music with code in real time
|
||||
- algorithmic composition: compose music using tidal's unique approach to pattern manipulation
|
||||
- teaching: focussing on a low barrier of entry, Strudel is a good fit for teaching music and code at the same time.
|
||||
- integrate into your existing music setup: either via MIDI or OSC, you can use Strudel as a really flexible sequencer
|
||||
|
||||
## Example
|
||||
|
||||
Here is an example of how strudel can sound:
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`stack(
|
||||
// drums
|
||||
s("bd,[~ <sd!3 sd(3,4,2)>],hh*8")
|
||||
.speed(perlin.range(.8,.9)), // random sample speed variation
|
||||
// bassline
|
||||
"<a1 b1\*2 a1(3,8) e2>"
|
||||
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
|
||||
.add(perlin.range(0,.5)) // random pitch variation
|
||||
.superimpose(add(.05)) // add second, slightly detuned voice
|
||||
.note() // wrap in "note"
|
||||
.decay(.15).sustain(0) // make each note of equal length
|
||||
.s('sawtooth') // waveform
|
||||
.gain(.4) // turn down
|
||||
.cutoff(sine.slow(7).range(300,5000)), // automate cutoff
|
||||
// chords
|
||||
"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand')
|
||||
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
||||
.add(perlin.range(0,.5)) // random pitch variation
|
||||
.note() // wrap in "note"
|
||||
.s('sawtooth') // waveform
|
||||
.gain(.16) // turn down
|
||||
.cutoff(500) // fixed cutoff
|
||||
.attack(1) // slowly fade in
|
||||
)
|
||||
.slow(3/2)`}
|
||||
/>
|
||||
|
||||
To hear more, go to the [Strudel REPL](https://strudel.tidalcycles.org/) and press shuffle to hear a random example pattern.
|
||||
|
||||
## Getting Started
|
||||
|
||||
The best way to start learning Strudel is the workshop.
|
||||
If you're ready to dive in, let's start with your [first sounds](/workshop/first-sounds)
|
||||
3
website/src/pages/workshop/index.astro
Normal file
3
website/src/pages/workshop/index.astro
Normal file
@ -0,0 +1,3 @@
|
||||
<script is:inline>
|
||||
window.location.pathname = `/workshop/intro`;
|
||||
</script>
|
||||
181
website/src/pages/workshop/pattern-effects.mdx
Normal file
181
website/src/pages/workshop/pattern-effects.mdx
Normal file
@ -0,0 +1,181 @@
|
||||
---
|
||||
title: Pattern Effects
|
||||
layout: ../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '@src/docs/MiniRepl';
|
||||
import Box from '@components/Box.astro';
|
||||
import QA from '@components/QA';
|
||||
|
||||
# Pattern Effects
|
||||
|
||||
Up until now, most of the functions we've seen are what other music programs are typically capable of: sequencing sounds, playing notes, controlling effects.
|
||||
|
||||
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()`} />
|
||||
|
||||
**play pattern left and modify it right with jux**
|
||||
|
||||
<MiniRepl hideHeader 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),
|
||||
n("0 1 [4 3] 2").sound("jazz").pan(1).rev()
|
||||
)`}
|
||||
/>
|
||||
|
||||
Let's visualize what happens here:
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`stack(
|
||||
n("0 1 [4 3] 2").sound("jazz").pan(0).color("cyan"),
|
||||
n("0 1 [4 3] 2").sound("jazz").pan(1).color("magenta").rev()
|
||||
)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Try commenting out one of the two by adding `//` before a line
|
||||
|
||||
</Box>
|
||||
|
||||
**multiple tempos**
|
||||
|
||||
<MiniRepl hideHeader 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'),
|
||||
note("c2, eb3 g3 [bb3 c4]").s("piano").slow(2).color('magenta'),
|
||||
note("c2, eb3 g3 [bb3 c4]").s("piano").slow(3).color('yellow')
|
||||
)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
Try commenting out one or more by adding `//` before a line
|
||||
|
||||
</Box>
|
||||
|
||||
**add**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`note("c2 [eb3,g3]".add("<0 <1 -1>>"))
|
||||
.color("<cyan <magenta yellow>>").adsr("[.1 0]:.2:[1 0]")
|
||||
.sound("gm_acoustic_bass").room(.5)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
If you add a number to a note, the note will be treated as if it was a number
|
||||
|
||||
</Box>
|
||||
|
||||
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]")
|
||||
.sound("gm_acoustic_bass").room(.5)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
**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)
|
||||
.sound("gm_xylophone").room(.5)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
**time to stack**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`stack(
|
||||
n("<0 [2 4] <3 5> [~ <4 1>]>*2".add("<0 [0,2,4]>/4"))
|
||||
.scale("C5:minor")
|
||||
.sound("gm_xylophone")
|
||||
.room(.4).delay(.125),
|
||||
note("c2 [eb3,g3]".add("<0 <1 -1>>"))
|
||||
.adsr("[.1 0]:.2:[1 0]")
|
||||
.sound("gm_acoustic_bass")
|
||||
.room(.5),
|
||||
n("0 1 [2 3] 2").sound("jazz").jux(rev).slow(2)
|
||||
)`}
|
||||
/>
|
||||
|
||||
**ply**
|
||||
|
||||
<MiniRepl hideHeader 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 />
|
||||
|
||||
<Box>
|
||||
|
||||
Try patterning the `ply` function, for example using `"<1 2 1 3>"`
|
||||
|
||||
</Box>
|
||||
|
||||
**off**
|
||||
|
||||
<MiniRepl
|
||||
hideHeader
|
||||
client:visible
|
||||
tune={`n("<0 [4 <3 2>] <2 3> [~ 1]>"
|
||||
.off(1/8, x=>x.add(4))
|
||||
//.off(1/4, x=>x.add(7))
|
||||
).scale("<C5:minor Db5:mixolydian>/4")
|
||||
.s("triangle").room(.5).ds(".1:0").delay(.5)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
<Box>
|
||||
|
||||
In the notation `x=>x.`, the `x` is the shifted pattern, which where modifying.
|
||||
|
||||
</Box>
|
||||
|
||||
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))`} /> |
|
||||
68
website/src/pages/workshop/recap.mdx
Normal file
68
website/src/pages/workshop/recap.mdx
Normal file
@ -0,0 +1,68 @@
|
||||
---
|
||||
title: Recap
|
||||
layout: ../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '../../docs/MiniRepl';
|
||||
|
||||
# Workshop Recap
|
||||
|
||||
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")`} /> |
|
||||
|
||||
## 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")`} /> |
|
||||
|
||||
## 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"))`} /> |
|
||||
|
||||
## 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))`} /> |
|
||||
|
||||
## 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))`} /> |
|
||||
@ -273,6 +273,15 @@ function SoundsTab() {
|
||||
);
|
||||
}
|
||||
|
||||
function Checkbox({ label, value, onChange }) {
|
||||
return (
|
||||
<label>
|
||||
<input type="checkbox" checked={value} onChange={onChange} />
|
||||
{' ' + label}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
function ButtonGroup({ value, onChange, items }) {
|
||||
return (
|
||||
<div className="flex max-w-lg">
|
||||
@ -355,7 +364,8 @@ const fontFamilyOptions = {
|
||||
};
|
||||
|
||||
function SettingsTab({ scheduler }) {
|
||||
const { theme, keybindings, fontSize, fontFamily } = useSettings();
|
||||
const { theme, keybindings, isLineNumbersDisplayed, isAutoCompletionEnabled, fontSize, fontFamily } = useSettings();
|
||||
|
||||
return (
|
||||
<div className="text-foreground p-4 space-y-4">
|
||||
{/* <FormItem label="Tempo">
|
||||
@ -397,13 +407,25 @@ function SettingsTab({ scheduler }) {
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
<FormItem label="Keybindings">
|
||||
<ButtonGroup
|
||||
value={keybindings}
|
||||
onChange={(keybindings) => settingsMap.setKey('keybindings', keybindings)}
|
||||
items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs' }}
|
||||
></ButtonGroup>
|
||||
</FormItem>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<FormItem label="Keybindings">
|
||||
<ButtonGroup
|
||||
value={keybindings}
|
||||
onChange={(keybindings) => settingsMap.setKey('keybindings', keybindings)}
|
||||
items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs' }}
|
||||
></ButtonGroup>
|
||||
</FormItem>
|
||||
<Checkbox
|
||||
label="Display line numbers"
|
||||
onChange={(cbEvent) => settingsMap.setKey('isLineNumbersDisplayed', cbEvent.target.checked)}
|
||||
value={isLineNumbersDisplayed}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Enable auto-completion"
|
||||
onChange={(cbEvent) => settingsMap.setKey('isAutoCompletionEnabled', cbEvent.target.checked)}
|
||||
value={isAutoCompletionEnabled}
|
||||
/>
|
||||
</div>
|
||||
<FormItem label="Reset Settings">
|
||||
<button
|
||||
className="bg-background p-2 max-w-[300px] rounded-md hover:opacity-50"
|
||||
|
||||
@ -122,7 +122,7 @@ export function Header({ context }) {
|
||||
{!isEmbedded && (
|
||||
<a
|
||||
title="learn"
|
||||
href="./learn/getting-started"
|
||||
href="./workshop/getting-started"
|
||||
className={cx('hover:opacity-50 flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
|
||||
>
|
||||
<AcademicCapIcon className="w-6 h-6" />
|
||||
|
||||
@ -22,12 +22,8 @@
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#code .cm-content {
|
||||
padding-top: 12px !important;
|
||||
padding-left: 8px !important;
|
||||
}
|
||||
|
||||
#code .cm-scroller {
|
||||
padding-top: 10px !important;
|
||||
padding-bottom: 50vh;
|
||||
font-family: inherit;
|
||||
}
|
||||
@ -49,3 +45,11 @@
|
||||
#code .cm-theme-light {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#code .cm-cursorLayer {
|
||||
animation-name: inherit !important;
|
||||
}
|
||||
|
||||
#code .cm-cursor {
|
||||
border-left: 2px solid currentcolor !important;
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ export function Repl({ embedded = false }) {
|
||||
const [lastShared, setLastShared] = useState();
|
||||
const [pending, setPending] = useState(true);
|
||||
|
||||
const { theme, keybindings, fontSize, fontFamily } = useSettings();
|
||||
const { theme, keybindings, fontSize, fontFamily, isLineNumbersDisplayed, isAutoCompletionEnabled } = useSettings();
|
||||
|
||||
const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } =
|
||||
useStrudel({
|
||||
@ -157,7 +157,7 @@ export function Repl({ embedded = false }) {
|
||||
e.preventDefault();
|
||||
flash(view);
|
||||
await activateCode();
|
||||
} else if (e.key === '.') {
|
||||
} else if (e.key === '.' || e.code === 'Period') {
|
||||
stop();
|
||||
e.preventDefault();
|
||||
}
|
||||
@ -271,6 +271,8 @@ export function Repl({ embedded = false }) {
|
||||
theme={themes[theme] || themes.strudelTheme}
|
||||
value={code}
|
||||
keybindings={keybindings}
|
||||
isLineNumbersDisplayed={isLineNumbersDisplayed}
|
||||
isAutoCompletionEnabled={isAutoCompletionEnabled}
|
||||
fontSize={fontSize}
|
||||
fontFamily={fontFamily}
|
||||
onChange={handleChangeCode}
|
||||
|
||||
@ -22,8 +22,96 @@ export async function prebake() {
|
||||
tag: 'drum-machines',
|
||||
}),
|
||||
samples(`./EmuSP12.json`, `./EmuSP12/`, { prebake: true, tag: 'drum-machines' }),
|
||||
// samples('github:tidalcycles/Dirt-Samples/master'),
|
||||
samples(
|
||||
{
|
||||
casio: ['casio/high.wav', 'casio/low.wav', 'casio/noise.wav'],
|
||||
crow: ['crow/000_crow.wav', 'crow/001_crow2.wav', 'crow/002_crow3.wav', 'crow/003_crow4.wav'],
|
||||
insect: [
|
||||
'insect/000_everglades_conehead.wav',
|
||||
'insect/001_robust_shieldback.wav',
|
||||
'insect/002_seashore_meadow_katydid.wav',
|
||||
],
|
||||
wind: [
|
||||
'wind/000_wind1.wav',
|
||||
'wind/001_wind10.wav',
|
||||
'wind/002_wind2.wav',
|
||||
'wind/003_wind3.wav',
|
||||
'wind/004_wind4.wav',
|
||||
'wind/005_wind5.wav',
|
||||
'wind/006_wind6.wav',
|
||||
'wind/007_wind7.wav',
|
||||
'wind/008_wind8.wav',
|
||||
'wind/009_wind9.wav',
|
||||
],
|
||||
jazz: [
|
||||
'jazz/000_BD.wav',
|
||||
'jazz/001_CB.wav',
|
||||
'jazz/002_FX.wav',
|
||||
'jazz/003_HH.wav',
|
||||
'jazz/004_OH.wav',
|
||||
'jazz/005_P1.wav',
|
||||
'jazz/006_P2.wav',
|
||||
'jazz/007_SN.wav',
|
||||
],
|
||||
metal: [
|
||||
'metal/000_0.wav',
|
||||
'metal/001_1.wav',
|
||||
'metal/002_2.wav',
|
||||
'metal/003_3.wav',
|
||||
'metal/004_4.wav',
|
||||
'metal/005_5.wav',
|
||||
'metal/006_6.wav',
|
||||
'metal/007_7.wav',
|
||||
'metal/008_8.wav',
|
||||
'metal/009_9.wav',
|
||||
],
|
||||
east: [
|
||||
'east/000_nipon_wood_block.wav',
|
||||
'east/001_ohkawa_mute.wav',
|
||||
'east/002_ohkawa_open.wav',
|
||||
'east/003_shime_hi.wav',
|
||||
'east/004_shime_hi_2.wav',
|
||||
'east/005_shime_mute.wav',
|
||||
'east/006_taiko_1.wav',
|
||||
'east/007_taiko_2.wav',
|
||||
'east/008_taiko_3.wav',
|
||||
],
|
||||
space: [
|
||||
'space/000_0.wav',
|
||||
'space/001_1.wav',
|
||||
'space/002_11.wav',
|
||||
'space/003_12.wav',
|
||||
'space/004_13.wav',
|
||||
'space/005_14.wav',
|
||||
'space/006_15.wav',
|
||||
'space/007_16.wav',
|
||||
'space/008_17.wav',
|
||||
'space/009_18.wav',
|
||||
'space/010_2.wav',
|
||||
'space/011_3.wav',
|
||||
'space/012_4.wav',
|
||||
'space/013_5.wav',
|
||||
'space/014_6.wav',
|
||||
'space/015_7.wav',
|
||||
'space/016_8.wav',
|
||||
'space/017_9.wav',
|
||||
],
|
||||
numbers: [
|
||||
'numbers/0.wav',
|
||||
'numbers/1.wav',
|
||||
'numbers/2.wav',
|
||||
'numbers/3.wav',
|
||||
'numbers/4.wav',
|
||||
'numbers/5.wav',
|
||||
'numbers/6.wav',
|
||||
'numbers/7.wav',
|
||||
'numbers/8.wav',
|
||||
],
|
||||
},
|
||||
'github:tidalcycles/Dirt-Samples/master/',
|
||||
),
|
||||
]);
|
||||
// await samples('github:tidalcycles/Dirt-Samples/master');
|
||||
}
|
||||
|
||||
const maxPan = noteToMidi('C8');
|
||||
|
||||
@ -121,8 +121,10 @@ stack(
|
||||
.room(1)
|
||||
//.pianoroll({fold:1})`;
|
||||
|
||||
export const caverave = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const caverave = `// "Caverave"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
const keys = x => x.s('sawtooth').cutoff(1200).gain(.5)
|
||||
.attack(0).decay(.16).sustain(.3).release(.1);
|
||||
|
||||
@ -184,8 +186,10 @@ export const barryHarris = `// adapted from a Barry Harris excercise
|
||||
.color('#00B8D4')
|
||||
`;
|
||||
|
||||
export const blippyRhodes = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const blippyRhodes = `// "Blippy Rhodes"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
samples({
|
||||
bd: 'samples/tidal/bd/BT0A0D0.wav',
|
||||
sn: 'samples/tidal/sn/ST0T0S3.wav',
|
||||
@ -226,8 +230,10 @@ stack(
|
||||
).fast(3/2)
|
||||
//.pianoroll({fold:1})`;
|
||||
|
||||
export const wavyKalimba = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const wavyKalimba = `// "Wavy kalimba"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
samples({
|
||||
'kalimba': { c5:'https://freesound.org/data/previews/536/536549_11935698-lq.mp3' }
|
||||
})
|
||||
@ -256,8 +262,10 @@ stack(
|
||||
.delay(.2)
|
||||
`;
|
||||
|
||||
export const festivalOfFingers = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const festivalOfFingers = `// "Festival of fingers"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
const chords = "<Cm7 Fm7 G7 F#7>";
|
||||
stack(
|
||||
chords.voicings('lefthand').struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)),
|
||||
@ -271,8 +279,10 @@ stack(
|
||||
.note().piano()`;
|
||||
|
||||
// iter, echo, echoWith
|
||||
export const undergroundPlumber = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos, inspired by Friendship - Let's not talk about it (1979) :)
|
||||
export const undergroundPlumber = `// "Underground plumber"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
// @details inspired by Friendship - Let's not talk about it (1979) :)
|
||||
|
||||
samples({ bd: 'bd/BT0A0D0.wav', sn: 'sn/ST0T0S3.wav', hh: 'hh/000_hh3closedhh.wav', cp: 'cp/HANDCLP0.wav',
|
||||
}, 'https://loophole-letters.vercel.app/samples/tidal/')
|
||||
@ -297,8 +307,10 @@ stack(
|
||||
.fast(2/3)
|
||||
.pianoroll({})`;
|
||||
|
||||
export const bridgeIsOver = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos, bassline by BDP - The Bridge Is Over
|
||||
export const bridgeIsOver = `// "Bridge is over"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos, bassline by BDP - The Bridge Is Over
|
||||
|
||||
samples({mad:'https://freesound.org/data/previews/22/22274_109943-lq.mp3'})
|
||||
stack(
|
||||
stack(
|
||||
@ -318,8 +330,10 @@ stack(
|
||||
.pianoroll()
|
||||
`;
|
||||
|
||||
export const goodTimes = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const goodTimes = `// "Good times"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
const scale = cat('C3 dorian','Bb2 major').slow(4);
|
||||
stack(
|
||||
"2*4".add(12).scale(scale)
|
||||
@ -361,8 +375,10 @@ stack(
|
||||
.pianoroll({maxMidi:100,minMidi:20})`;
|
||||
*/
|
||||
|
||||
export const echoPiano = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const echoPiano = `// "Echo piano"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
"<0 2 [4 6](3,4,2) 3*2>"
|
||||
.scale('D minor')
|
||||
.color('salmon')
|
||||
@ -408,8 +424,10 @@ stack(
|
||||
.legato(.5)
|
||||
).fast(2).note()`;
|
||||
|
||||
export const randomBells = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const randomBells = `// "Random bells"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
samples({
|
||||
bell: { c6: 'https://freesound.org/data/previews/411/411089_5121236-lq.mp3' },
|
||||
bass: { d2: 'https://freesound.org/data/previews/608/608286_13074022-lq.mp3' }
|
||||
@ -431,8 +449,10 @@ stack(
|
||||
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})
|
||||
`;
|
||||
|
||||
export const waa2 = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const waa2 = `// "Waa2"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
n(
|
||||
"a4 [a3 c3] a3 c3"
|
||||
.sub("<7 12 5 12>".slow(2))
|
||||
@ -447,8 +467,10 @@ n(
|
||||
.room(.5)
|
||||
`;
|
||||
|
||||
export const hyperpop = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const hyperpop = `// "Hyperpop"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
const lfo = cosine.slow(15);
|
||||
const lfo2 = sine.slow(16);
|
||||
const filter1 = x=>x.cutoff(lfo2.range(300,3000));
|
||||
@ -498,8 +520,10 @@ stack(
|
||||
).slow(2)
|
||||
// strudel disable-highlighting`;
|
||||
|
||||
export const festivalOfFingers3 = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const festivalOfFingers3 = `// "Festival of fingers 3"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
"[-7*3],0,2,6,[8 7]"
|
||||
.echoWith(4,1/4, (x,n)=>x
|
||||
.add(n*7)
|
||||
@ -516,8 +540,10 @@ export const festivalOfFingers3 = `// licensed with CC BY-NC-SA 4.0 https://crea
|
||||
.note().piano()
|
||||
//.pianoroll({maxMidi:160})`;
|
||||
|
||||
export const meltingsubmarine = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const meltingsubmarine = `// "Melting submarine"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
await samples('github:tidalcycles/Dirt-Samples/master/')
|
||||
stack(
|
||||
s("bd:5,[~ <sd:1!3 sd:1(3,4,3)>],hh27(3,4,1)") // drums
|
||||
@ -554,8 +580,10 @@ stack(
|
||||
)
|
||||
.slow(3/2)`;
|
||||
|
||||
export const outroMusic = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const outroMusic = `// "Outro music"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
samples({
|
||||
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
|
||||
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||
@ -583,8 +611,10 @@ samples({
|
||||
//.pianoroll({autorange:1,vertical:1,fold:0})
|
||||
`;
|
||||
|
||||
export const bassFuge = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const bassFuge = `// "Bass fuge"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
samples({ flbass: ['00_c2_finger_long_neck.wav','01_c2_finger_short_neck.wav','02_c2_finger_long_bridge.wav','03_c2_finger_short_bridge.wav','04_c2_pick_long.wav','05_c2_pick_short.wav','06_c2_palm_mute.wav'] },
|
||||
'github:cleary/samples-flbass/main/')
|
||||
samples({
|
||||
@ -609,8 +639,10 @@ x=>x.add(7).color('steelblue')
|
||||
.stack(s("bd:1*2,~ sd:0,[~ hh:0]*2"))
|
||||
.pianoroll({vertical:1})`;
|
||||
|
||||
export const chop = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const chop = `// "Chop"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
samples({ p: 'https://cdn.freesound.org/previews/648/648433_11943129-lq.mp3' })
|
||||
|
||||
s("p")
|
||||
@ -622,8 +654,10 @@ s("p")
|
||||
.sustain(.6)
|
||||
`;
|
||||
|
||||
export const delay = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const delay = `// "Delay"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
stack(
|
||||
s("bd <sd cp>")
|
||||
.delay("<0 .5>")
|
||||
@ -631,8 +665,10 @@ stack(
|
||||
.delayfeedback(".6 | .8")
|
||||
).sometimes(x=>x.speed("-1"))`;
|
||||
|
||||
export const orbit = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const orbit = `// "Orbit"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
stack(
|
||||
s("bd <sd cp>")
|
||||
.delay(.5)
|
||||
@ -645,8 +681,10 @@ stack(
|
||||
.orbit(2)
|
||||
).sometimes(x=>x.speed("-1"))`;
|
||||
|
||||
export const belldub = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const belldub = `// "Belldub"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
samples({ bell: {b4:'https://cdn.freesound.org/previews/339/339809_5121236-lq.mp3'}})
|
||||
// "Hand Bells, B, Single.wav" by InspectorJ (www.jshaw.co.uk) of Freesound.org
|
||||
stack(
|
||||
@ -678,8 +716,10 @@ stack(
|
||||
.mask("<1 0>/8")
|
||||
).slow(5)`;
|
||||
|
||||
export const dinofunk = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const dinofunk = `// "Dinofunk"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
samples({bass:'https://cdn.freesound.org/previews/614/614637_2434927-hq.mp3',
|
||||
dino:{b4:'https://cdn.freesound.org/previews/316/316403_5123851-hq.mp3'}})
|
||||
setVoicingRange('lefthand', ['c3','a4'])
|
||||
@ -699,8 +739,10 @@ note("Abm7".voicings('lefthand').struct("x(3,8,1)".slow(2))),
|
||||
note("<b4 eb4>").s('dino').delay(.8).slow(8).room(.5)
|
||||
)`;
|
||||
|
||||
export const sampleDemo = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const sampleDemo = `// "Sample demo"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
stack(
|
||||
// percussion
|
||||
s("[woodblock:1 woodblock:2*2] snare_rim:0,gong/8,brakedrum:1(3,8),~@3 cowbell:3")
|
||||
@ -715,8 +757,10 @@ stack(
|
||||
.release(.1).room(.5)
|
||||
)`;
|
||||
|
||||
export const holyflute = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const holyflute = `// "Holy flute"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
"c3 eb3(3,8) c4/2 g3*2"
|
||||
.superimpose(
|
||||
x=>x.slow(2).add(12),
|
||||
@ -728,8 +772,10 @@ export const holyflute = `// licensed with CC BY-NC-SA 4.0 https://creativecommo
|
||||
.pianoroll({fold:0,autorange:0,vertical:0,cycles:12,smear:0,minMidi:40})
|
||||
`;
|
||||
|
||||
export const flatrave = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const flatrave = `// "Flatrave"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
stack(
|
||||
s("bd*2,~ [cp,sd]").bank('RolandTR909'),
|
||||
|
||||
@ -751,8 +797,10 @@ stack(
|
||||
.decay(.05).sustain(0).delay(.2).degradeBy(.5).mask("<0 1>/16")
|
||||
)`;
|
||||
|
||||
export const amensister = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const amensister = `// "Amensister"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
|
||||
stack(
|
||||
@ -785,8 +833,10 @@ stack(
|
||||
n("0 1").s("east").delay(.5).degradeBy(.8).speed(rand.range(.5,1.5))
|
||||
).reset("<x@7 x(5,8,-1)>")`;
|
||||
|
||||
export const juxUndTollerei = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const juxUndTollerei = `// "Jux und tollerei"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
note("c3 eb3 g3 bb3").palindrome()
|
||||
.s('sawtooth')
|
||||
.jux(x=>x.rev().color('green').s('sawtooth'))
|
||||
@ -798,8 +848,10 @@ note("c3 eb3 g3 bb3").palindrome()
|
||||
.delay(.5).delaytime(.1).delayfeedback(.4)
|
||||
.pianoroll()`;
|
||||
|
||||
export const csoundDemo = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
export const csoundDemo = `// "CSound demo"
|
||||
// @license with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
await loadCsound\`
|
||||
instr CoolSynth
|
||||
iduration = p3
|
||||
@ -832,10 +884,10 @@ endin\`
|
||||
//.pianoroll()
|
||||
.csound('CoolSynth')`;
|
||||
|
||||
export const loungeSponge = `
|
||||
// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
// livecode.orc by Steven Yi
|
||||
export const loungeSponge = `// "Lounge sponge"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos, livecode.orc by Steven Yi
|
||||
|
||||
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
|
||||
|
||||
stack(
|
||||
@ -852,8 +904,10 @@ stack(
|
||||
s("bd*2,[~ hh]*2,~ cp").bank('RolandTR909')
|
||||
)`;
|
||||
|
||||
export const arpoon = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// "Arpoon" by Felix Roos
|
||||
export const arpoon = `// "Arpoon"
|
||||
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// @by Felix Roos
|
||||
|
||||
await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
|
||||
"<<Am7 C^7> C7 F^7 [Fm7 E7b9]>".voicings('lefthand')
|
||||
|
||||
@ -4,6 +4,8 @@ import { useStore } from '@nanostores/react';
|
||||
export const defaultSettings = {
|
||||
activeFooter: 'intro',
|
||||
keybindings: 'codemirror',
|
||||
isLineNumbersDisplayed: true,
|
||||
isAutoCompletionEnabled: false,
|
||||
theme: 'strudelTheme',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 18,
|
||||
@ -19,6 +21,8 @@ export function useSettings() {
|
||||
return {
|
||||
...state,
|
||||
isZen: [true, 'true'].includes(state.isZen) ? true : false,
|
||||
isLineNumbersDisplayed: [true, 'true'].includes(state.isLineNumbersDisplayed) ? true : false,
|
||||
isAutoCompletionEnabled: [true, 'true'].includes(state.isAutoCompletionEnabled) ? true : false,
|
||||
fontSize: Number(state.fontSize),
|
||||
};
|
||||
}
|
||||
|
||||
@ -27,10 +27,6 @@
|
||||
src: url('/fonts/FiraCode/FiraCode-SemiBold.ttf');
|
||||
}
|
||||
|
||||
.cm-gutters {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.prose > h1:not(:first-child) {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
@ -7,6 +7,11 @@
|
||||
"noImplicitAny": false,
|
||||
"types": [
|
||||
"vite-plugin-pwa/client"
|
||||
]
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@components/*": ["src/components/*"],
|
||||
"@src/*": ["src/*"],
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user