diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index ef5bd33f9..c4044d4a4 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see . */ -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; diff --git a/packages/core/pianoroll.mjs b/packages/core/pianoroll.mjs index 594760155..66b56dc88 100644 --- a/packages/core/pianoroll.mjs +++ b/packages/core/pianoroll.mjs @@ -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); diff --git a/packages/core/util.mjs b/packages/core/util.mjs index 2b43cf0b6..5dbf65fcb 100644 --- a/packages/core/util.mjs +++ b/packages/core/util.mjs @@ -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 * @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,5 @@ 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); diff --git a/packages/react/src/components/MiniRepl.jsx b/packages/react/src/components/MiniRepl.jsx index a8de79788..1a6cce944 100644 --- a/packages/react/src/components/MiniRepl.jsx +++ b/packages/react/src/components/MiniRepl.jsx @@ -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 (
-
-
- - + {!hideHeader && ( +
+
+ + +
- {error &&
{error.message}
} -
+ )}
- {show && } + {show && ( + + )} + {error &&
{error.message}
}
- {drawTime && ( + {punchcard && ( !!(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( diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index 6b069a859..c59223031 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -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); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a135d893..6542a7072 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 @@ -4502,7 +4505,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 @@ -5413,6 +5416,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'} diff --git a/website/package.json b/website/package.json index 6b8b7a6cf..c595f5a6d 100644 --- a/website/package.json +++ b/website/package.json @@ -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", diff --git a/website/public/icons/strudel_icon.png b/website/public/icons/strudel_icon.png new file mode 100644 index 000000000..ec9ad8e17 Binary files /dev/null and b/website/public/icons/strudel_icon.png differ diff --git a/website/src/components/Box.astro b/website/src/components/Box.astro new file mode 100644 index 000000000..d27671eaf --- /dev/null +++ b/website/src/components/Box.astro @@ -0,0 +1,10 @@ +--- +import LightBulbIcon from '@heroicons/react/20/solid/LightBulbIcon'; +//import MusicalNoteIcon from '@heroicons/react/20/solid/MusicalNoteIcon'; +--- + +
+
+ + +
diff --git a/website/src/components/Claviature.jsx b/website/src/components/Claviature.jsx new file mode 100644 index 000000000..e97facbc4 --- /dev/null +++ b/website/src/components/Claviature.jsx @@ -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.children.map((el, i) => { + const TagName = el.name; + return ( + + {el.value} + + ); + })} + + ); +} diff --git a/website/src/components/LeftSidebar/LeftSidebar.astro b/website/src/components/LeftSidebar/LeftSidebar.astro index 18327ebed..ce4dbb800 100644 --- a/website/src/components/LeftSidebar/LeftSidebar.astro +++ b/website/src/components/LeftSidebar/LeftSidebar.astro @@ -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]; --- diff --git a/website/src/components/QA.tsx b/website/src/components/QA.tsx new file mode 100644 index 000000000..7d2ac53d9 --- /dev/null +++ b/website/src/components/QA.tsx @@ -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 ( +
+
setVisible((v) => !v)}> +
{q}
+ + {visible ? : } + +
+ {visible &&
{children}
} +
+ ); +} diff --git a/website/src/config.ts b/website/src/config.ts index f4dba71bb..e32e243d5 100644 --- a/website/src/config.ts +++ b/website/src/config.ts @@ -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>; +export type SidebarLang = Record; +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,14 +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' }, - { text: 'Music metadata', link: 'learn/metadata' }, - ], Development: [ { text: 'REPL', link: 'technical-manual/repl' }, { text: 'Sounds', link: 'technical-manual/sounds' }, diff --git a/website/src/docs/MiniRepl.css b/website/src/docs/MiniRepl.css index e9b49af84..8ec75d4d8 100644 --- a/website/src/docs/MiniRepl.css +++ b/website/src/docs/MiniRepl.css @@ -1,4 +1,5 @@ -.cm-activeLine { +.cm-activeLine, +.cm-activeLineGutter { background-color: transparent !important; } @@ -7,3 +8,11 @@ border: 1px solid var(--lineHighlight); padding: 2px; } + +.cm-scroller { + font-family: inherit !important; +} + +.cm-gutters { + display: none !important; +} diff --git a/website/src/docs/MiniRepl.jsx b/website/src/docs/MiniRepl.jsx index cce24623f..7c5079a0b 100644 --- a/website/src/docs/MiniRepl.jsx +++ b/website/src/docs/MiniRepl.jsx @@ -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 }) { { + const active = haps + .map((hap) => hap.value.note) + .filter(Boolean) + .map((n) => (typeof n === 'string' ? noteToMidi(n) : n)); + setActiveNotes(active); + } + : undefined + } /> + {claviature && ( + + )}
) : (
{tune}
diff --git a/website/src/pages/de/workshop/first-effects.mdx b/website/src/pages/de/workshop/first-effects.mdx new file mode 100644 index 000000000..7719249aa --- /dev/null +++ b/website/src/pages/de/workshop/first-effects.mdx @@ -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** + +/2") +.sound("sawtooth").lpf(800)`} +/> + + + +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 ✨🪩 + + + +**filter automatisieren** + +/2") +.sound("sawtooth").lpf("200 1000")`} +/> + + + +- 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. + + + +**vowel = Vokal** + +/2") +.sound("sawtooth").vowel("/2")`} +/> + +**gain = Verstärkung** + + + + + +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) + + + +**stacks in stacks** + +Lass uns die obigen Beispiele kombinieren: + +/2") + .sound("sawtooth").lpf("200 1000"), + note("<[c3,g3,e4] [bb2,f3,d4] [a2,f3,c4] [bb2,g3,eb4]>/2") + .sound("sawtooth").vowel("/2") +) `} +/> + + + +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 + + + +**Den Sound formen mit ADSR Hüllkurve** + +") +.sound("sawtooth").lpf(600) +.attack(.1) +.decay(.1) +.sustain(.25) +.release(.2)`} +/> + + + +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? + + + + + +- attack (anschlagen): Zeit des Aufbaus +- decay (zerfallen): Zeit des Abfalls +- sustain (erhalten): Lautstärke nach Abfall +- release (loslassen): Zeit des Abfalls nach dem Ende + +![ADSR](https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/ADSR_parameter.svg/1920px-ADSR_parameter.svg.png) + + + +**adsr Kurznotation** + +") +.sound("sawtooth").lpf(600) +.adsr(".1:.1:.5:.2") +`} +/> + +**delay = Verzögerung** + + ~]") + .sound("gm_electric_guitar_muted"), + sound("").bank("RolandTR707") +).delay(".5")`} +/> + + + +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? + + + + + +`delay("a:b:c")`: + +- a: Lautstärke des Delays +- b: Verzögerungszeit +- c: Feedback (je kleiner desto schneller verschwindet das Delay) + + + +**room aka reverb = Hall** + + ~@16] ~>/2") +.scale("D4:minor").sound("gm_accordion:2") +.room(2)`} +/> + + + +Spiel mit verschiedenen Werten. + +Füg auch ein Delay hinzu! + + + +**kleiner dub tune** + + ~]") + .sound("gm_electric_guitar_muted").delay(.5), + sound("").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: + + ~]") + .sound("gm_electric_guitar_muted").delay(.5), + sound("").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) +)`} +/> + + + +Füg `.hush()` ans ende eines Patterns im stack... + + + +**pan = Panorama** + + + +**speed = Geschwindigkeit** + +").room(.2)`} /> + +**fast and slow = schnell und langsam** + +Mit `fast` und `slow` kann man das tempo eines patterns außerhalb der Mini-Notation ändern: + + + + + +Ändere den `slow` Wert. Tausche `slow` durch `fast`. + +Was passiert wenn du den Wert automatisierst? z.b. `.fast("<1 [2 4]>")` ? + + + +Übrigens, innerhalb der Mini-Notation, `fast` ist `*` und `slow` ist `/`. + +")`} /> + +## Automation mit Signalen + +Anstatt Werte schrittweise zu automatisieren können wir auch sogenannte Signale benutzen: + + + + + +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. + + + +**Bereich ändern mit `range`** + +Signale bewegen sich standardmäßig zwischen 0 und 1. Wir können das mit `range` ändern: + + + +`range` ist nützlich wenn wir Funktionen mit einem anderen Wertebereich als 0 und 1 automatisieren wollen (z.b. lpf) + + + +Was passiert wenn du die beiden Werte vertauschst? + + + +Wir können die Geschwindigkeit der Automation mit slow / fast ändern: + +/2") +.sound("sawtooth") +.lpf(sine.range(100, 2000).slow(8))`} +/> + + + +Die ganze Automation braucht nun 8 cycle bis sie sich wiederholt. + + + +## Rückblick + +| name | example | +| ----- | -------------------------------------------------------------------------------------------------- | +| lpf | ")`} /> | +| vowel | ")`} /> | +| gain | | +| delay | | +| room | | +| pan | | +| speed | ")`} /> | +| range | | + +Lass uns nun die für Tidal typischen [Pattern Effekte anschauen](/de/workshop/pattern-effects). diff --git a/website/src/pages/de/workshop/first-notes.mdx b/website/src/pages/de/workshop/first-notes.mdx new file mode 100644 index 000000000..c44969dfb --- /dev/null +++ b/website/src/pages/de/workshop/first-notes.mdx @@ -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** + + [midi2note(i + 36), i + 36]), + )} +/> + + + +Probiere verschiedene Zahlen aus! + +Versuch auch mal Kommazahlen, z.B. 55.5 (beachte die englische Schreibweise von Kommazahlen mit "." anstatt ",") + + + +**Töne mit Buchstaben** + + [n, n.split('')[0]]))} +/> + + + +Versuch verschiedene Buchstaben aus (a - g). + +Findest du Melodien die auch gleichzeitig ein Wort sind? Tipp: ☕ 🙈 🧚 + + + +**Vorzeichen** + + [n, n.split('').slice(0, 2).join('')]), + )} +/> + + [n, n.split('').slice(0, 2).join('')]), + )} +/> + +**Andere Oktaven** + + [n, n]))} + claviatureLabels={Object.fromEntries( + Array(49) + .fill() + .map((_, i) => [midi2note(i + 36), midi2note(i + 36)]), + )} +/> + + + +Probiere verschiedene Oktaven aus (1-8) + + + +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: + + + + + +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 + + + +**Zwischen Sounds hin und her wechseln** + + + +**Gleichzeitige Sounds** + + + + + +Die patterns in `note` und `sound` werden kombiniert! + +Wir schauen uns später noch mehr Möglichkeiten an wie man patterns kombiniert. + + + +## Längere Sequenzen + +**Sequenzen verlangsamen mit `/`** + +{/* [c2 bb1 f2 eb2] */} + + + + + +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. + + + +Wenn eine Sequenz unabhängig von ihrem Inhalt immer gleich schnell bleiben soll, gibt es noch eine andere Art Klammern: + +**Eins pro Cycle per \< \>** + +").sound("gm_acoustic_bass")`} punchcard /> + + + +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]/3` + +`` = `[a b c d]/4` + +usw.. + + + +**Eine Sequenz pro Cycle** + +") +.sound("gm_acoustic_bass")`} + punchcard +/> + +oder auch... + +/2") +.sound("gm_acoustic_bass")`} + punchcard +/> + +**Alternativen** + +Ähnlich wie Unter-Sequenzen, kann auch `<...>` innerhalb einer Sequenz verwendet werden: + +") +.sound("gm_xylophone")`} + punchcard +/> + +Das ist auch praktisch für atonale Sounds: + +, [~ 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: + +") +.scale("C:minor").sound("piano")`} + punchcard +/> + + + +Probier verschiedene Zahlen aus. Jede klingt gut! + +Probier verschiedene Skalen: + +- C:major +- A2:minor +- D:dorian +- G:mixolydian +- A2:minor:pentatonic +- F:major:pentatonic + + + +**Automatisierte Skalen** + +Wie alle Funktionen können auch Skalen mit einem Pattern automatisiert werden: + +, 2 4 <[6,8] [7,9]>") +.scale("/4") +.sound("piano")`} + punchcard +/> + + + +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. + + + +## Wiederholen und Verlängern + +**Verlängern mit @** + + + + + +Ein Element ohne `@` ist gleichbedeutend mit `@1`. Im Beispiel ist `c` drei Einheiten lang, `eb` nur eine. + +Spiel mit der Länge! + + + +**Unter-Sequenzen verlängern** + +*2") +.scale("/4") +.sound("gm_acoustic_bass")`} + punchcard +/> + + + +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. + + + +**Wiederholen** + +]").sound("piano")`} punchcard /> + + + +Wechsel zwischen `!`, `*` und `@` hin und her. + +Was ist der Unterschied? + + + +## Rückblick + +Das haben wir in diesem Kapitel gelernt: + +| Concept | Syntax | Example | +| ------------ | ------ | ------------------------------------------------------------------- | +| Verlangsamen | \/ | | +| Alternativen | \<\> | ")`} /> | +| Verlängern | @ | | +| Wiederholen | ! | | + +Neue Funktionen: + +| Name | Description | Example | +| ----- | --------------------------------------- | -------------------------------------------------------------------------------------------- | +| note | Tonhöhe als Buchstabe oder Zahl | | +| scale | Interpretiert `n` als Skalenstufe | | +| stack | Spiele mehrere Patterns parallel (s.u.) | | + +## Beispiele + +**Bassline** + +/2") +.sound("gm_synth_bass_1") +.lpf(800) // <-- we'll learn about this soon`} +/> + +**Melodie** + +*2\`).scale("C4:minor") +.sound("gm_synth_strings_1")`} +/> + +**Drums** + +, [~ hh]*2") +.bank("RolandTR909")`} +/> + +**Wenn es doch nur einen Weg gäbe das alles gleichzeitig zu spielen.......** + + + +Das geht mit `stack` 😙 + + + +/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, ~ , [~ 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) diff --git a/website/src/pages/de/workshop/first-sounds.mdx b/website/src/pages/de/workshop/first-sounds.mdx new file mode 100644 index 000000000..7654a2200 --- /dev/null +++ b/website/src/pages/de/workshop/first-sounds.mdx @@ -0,0 +1,371 @@ +--- +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: + + + + + +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` + + + +Glückwunsch, du kannst jetzt live coden! + +## Sounds + +Gerade haben wir schon den ersten sound mit `sound` abgespielt: + + + + + +`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. + + + +**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: + + + + + +Probiere verschiedene Sound / Zahlen Kombinationen. + +Ohne Zahl ist gleichbedeutend mit `:0` + + + +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: + + + + + +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! + + + +Wir können den Charakter des Drum Sounds verändern, indem wir mit `bank` die Drum Machine auswählen: + + + +In diesem Beispiel ist `RolandTR909` der Name der Drum Machine, die eine prägende Rolle für House und Techno Musik spielte. + + + +Ä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. + + + +## Sequenzen / Sequences + +Im letzten Beispiel haben wir schon gesehen dass man mehrere Sounds hintereinander abspielen kann wenn man sie durch Leerzeichen trennt: + + + +Beachte wie der aktuell gespielte Sound im Code markiert und auch darunter visualisiert wird. + + + +Versuch noch mehr Sounds hinzuzfügen! + + + +**Je länger die Sequence, desto schneller** + + + +Der Inhalt einer Sequence wird in einen sogenannten Cycle (=Zyklus) zusammengequetscht. + +**Tempo ändern mit `cpm`** + + + + + +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). + + + +Wir werden später noch mehr Möglichkeiten kennen lernen das Tempo zu verändern. + +**Pausen mit '~'** + + + + + +Tilde tippen: + +- Windows / Linux: `Alt Gr` + `~` +- Mac: `option` + `N` + + + +**Unter-Sequenzen mit [Klammern]** + + + + + +Der Inhalt der Klammer wird ebenfalls zusammengequetscht! + +Füge noch mehr Sounds in die Klammern ein! + + + +Genau wie bei der ganzen Sequence wird eine Unter-Sequence schneller je mehr drin ist. + +**Multiplikation: Dinge schneller machen** + + + +**Multiplikation: Vieeeeeeel schneller** + + + + + +Tonhöhe = sehr schneller Rhythmus + + + +**Multiplikation: Ganze Unter-Sequences schneller machen** + + + +Bolero: + + + +**Unter-Unter-Sequenzen mit [[Klammern]]** + + + + + +Du kannst so tief verschachteln wie du willst! + + + +**Parallele Sequenzen mit Komma** + + + +Du kannst so viele Kommas benutzen wie du möchtest: + + + +Kommas können auch in Unter-Sequenzen verwendet werden: + + + + + +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. + + + +**Mehrere Zeilen schreiben mit \` (backtick)** + + + + + +Ob man " oder \` benutzt ist nur eine Frage der Übersichtlichkeit. + + + +**Sample Nummer separat auswählen** + +Benutzt man nur einen Sound mit unterschiedlichen Sample Nummer sieht das so aus: + + + +Das gleiche kann man auch so schreiben: + + + +## 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 | | +| Sound Nummer | :x | | +| Pausen | ~ | | +| Unter-Sequenzen | \[\] | | +| Unter-Unter-Sequenzen | \[\[\]\] | | +| Schneller | \* | | +| Parallel | , | | + +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 | | +| bank | Wählt die Soundbank / Drum Machine | | +| cpm | Tempo in **C**ycles **p**ro **M**inute | | +| n | Sample Nummer | | + +## Beispiele + +**Einfacher Rock Beat** + + + +**Klassischer House** + + + + + +Ist die aufgefallen dass die letzten 2 Patterns extrem ähnlich sind? +Bestimmte Drum Patterns werden oft genreübergreifend wiederverwendet. + + + +We Will Rock you + + + +**Yellow Magic Orchestra - Firecracker** + + + +**Nachahmung eines 16 step sequencers** + + + +**Noch eins** + + + +**Nicht so typische Drums** + + + +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). + +--- + +- vllt mal das wörtchen `sound` behandeln +- was ist überhaupt casio?? +- Strg = ctrl on mac "+" ist verwirrend +- cursor ist sehr undeutlich wenn nicht in emacs mode +- wieviele zahlen gibt es diff --git a/website/src/pages/de/workshop/getting-started.mdx b/website/src/pages/de/workshop/getting-started.mdx new file mode 100644 index 000000000..c341b73fb --- /dev/null +++ b/website/src/pages/de/workshop/getting-started.mdx @@ -0,0 +1,74 @@ +--- +title: Intro +layout: ../../../layouts/MainLayout.astro +--- + +import { MiniRepl } from '../../../docs/MiniRepl'; + +# Willkommen + + + +Willkommen zum Strudel Workshop! +Du hast den richtigen Ort gefunden wenn du lernen möchstest 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: + +],hh*8") + .speed(perlin.range(.8,.9)), // random sample speed variation + // bassline + "" + .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 + ">".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). diff --git a/website/src/pages/de/workshop/index.astro b/website/src/pages/de/workshop/index.astro new file mode 100644 index 000000000..9f79e4c22 --- /dev/null +++ b/website/src/pages/de/workshop/index.astro @@ -0,0 +1,3 @@ + diff --git a/website/src/pages/de/workshop/pattern-effects.mdx b/website/src/pages/de/workshop/pattern-effects.mdx new file mode 100644 index 000000000..b701958a8 --- /dev/null +++ b/website/src/pages/de/workshop/pattern-effects.mdx @@ -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** + + + +**jux = einen stereo kanal modifizieren** + + + +So würde man das ohne jux schreiben: + + + +Lass uns visualisieren was hier passiert: + + + + + +Schreibe `//` vor eine der beiden Zeilen im `stack`! + + + +**mehrere tempos** + + + +Das hat den gleichen Effekt wie: + + + + + +Schreibe wieder `//` vor eine oder mehrere Zeilen im `stack`. + + + +**add = addieren** + +>")) +.color(">").adsr("[.1 0]:.2:[1 0]") +.sound("gm_acoustic_bass").room(.5)`} + punchcard +/> + + + +Addiert man eine Zahl zu einer Note, verhält sich diese wie eine Zahl. + +z.B. c4 = 60, also ist c4 + 2 = 62 + + + +Man kann auch mehrmals addieren: + +>").add("0,7")) +.color(">").adsr("[.1 0]:.2:[1 0]") +.sound("gm_acoustic_bass").room(.5)`} + punchcard +/> + +**add + scale** + + [~ <4 1>]>*2".add("<0 [0,2,4]>/4")) +.scale("C5:minor").release(.5) +.sound("gm_xylophone").room(.5)`} + punchcard +/> + +**Alles zusammen** + + [~ <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** + + + +das ist wie: + + + + + +Probier `ply` mit einem pattern zu automatisieren, z.b. `"<1 2 1 3>"` + + + +**off** + +] <2 3> [~ 1]>" + .off(1/8, x=>x.add(4)) + //.off(1/4, x=>x.add(7)) +).scale("/4") +.s("triangle").room(.5).ds(".1:0").delay(.5)`} + punchcard +/> + + + +In der notation `x=>x.`, das `x` ist das Pattern das wir bearbeiten. + + + +`off` ist auch nützlich für sounds: + +x.speed(1.5).gain(.25))`} +/> + +| name | description | example | +| ---- | --------------------------------- | ---------------------------------------------------------------------------------------------- | +| rev | rückwärts | | +| jux | ein stereo-kanal modifizieren | | +| add | addiert zahlen oder noten | ")).scale("C:minor")`} /> | +| ply | multipliziert jedes element x mal | ")`} /> | +| off | verzögert eine modifizierte kopie | x.speed(2))`} /> | diff --git a/website/src/pages/de/workshop/recap.mdx b/website/src/pages/de/workshop/recap.mdx new file mode 100644 index 000000000..db392b8b0 --- /dev/null +++ b/website/src/pages/de/workshop/recap.mdx @@ -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 | | +| Sample Nummer | :x | | +| Pausen | ~ | | +| Unter-Sequences | \[\] | | +| Unter-Unter-Sequences | \[\[\]\] | | +| Schneller | \* | | +| Slow down | \/ | | +| Parallel | , | | +| Alternieren | \<\> | ")`} /> | +| Verlängern | @ | | +| Wiederholen | ! | | + +## Sounds + +| Name | Description | Example | +| ----- | -------------------------- | ---------------------------------------------------------------------------------- | +| sound | spielt den sound mit namen | | +| bank | wählt die soundbank | | +| n | wählt sample mit nummer | | + +## Notes + +| Name | Description | Example | +| --------- | ---------------------------------- | -------------------------------------------------------------------------------------------- | +| note | wählt note per zahl oder buchstabe | | +| n + scale | wählt note n in skala | | +| stack | spielt mehrere patterns parallel | | + +## Audio Effekte + +| name | example | +| ----- | -------------------------------------------------------------------------------------------------- | +| lpf | ")`} /> | +| vowel | ")`} /> | +| gain | | +| delay | | +| room | | +| pan | | +| speed | ")`} /> | +| range | | + +## Pattern Effects + +| name | description | example | +| ---- | --------------------------------- | ---------------------------------------------------------------------------------------------- | +| cpm | tempo in cycles pro minute | | +| fast | schneller | | +| slow | langsamer | | +| rev | rückwärts | | +| jux | ein stereo-kanal modifizieren | | +| add | addiert zahlen oder noten | ")).scale("C:minor")`} /> | +| ply | jedes element schneller machen | ")`} /> | +| off | verzögert eine modifizierte kopie | x.speed(2))`} /> | diff --git a/website/src/pages/functions/intro.mdx b/website/src/pages/functions/intro.mdx index d396543c8..2e1bb7eb9 100644 --- a/website/src/pages/functions/intro.mdx +++ b/website/src/pages/functions/intro.mdx @@ -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: is equivalent to this Pattern without Mini Notation: - + 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 @@ -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) diff --git a/website/src/pages/learn/code.mdx b/website/src/pages/learn/code.mdx index b74002aa7..ca2564dec 100644 --- a/website/src/pages/learn/code.mdx +++ b/website/src/pages/learn/code.mdx @@ -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: - + -- 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? - + - + - + Ok, none of these seem to work... - + -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? @@ -72,14 +72,12 @@ Those are just a convention to define some information about the music. We will # 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_](). 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!
diff --git a/website/src/pages/learn/factories.mdx b/website/src/pages/learn/factories.mdx index 46567e347..0d93fb21a 100644 --- a/website/src/pages/learn/factories.mdx +++ b/website/src/pages/learn/factories.mdx @@ -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: diff --git a/website/src/pages/learn/mini-notation.mdx b/website/src/pages/learn/mini-notation.mdx index 79e81fc4e..d0844f457 100644 --- a/website/src/pages/learn/mini-notation.mdx +++ b/website/src/pages/learn/mini-notation.mdx @@ -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: /2") +.sound("sawtooth").lpf(800)`} +/> + + + +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 ✨🪩 + + + +**pattern the filter** + +/2") +.sound("sawtooth").lpf("200 1000")`} +/> + + + +- 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... + + + +**vowel** + +/2") +.sound("sawtooth").vowel("
/2")`} +/> + +**gain** + + + + + +Rhythm is all about dynamics! + +- Remove `.gain(...)` and notice how flat it sounds. +- Bring it back by undoing (ctrl+z) + + + +**stacks within stacks** + +Let's combine all of the above into a little tune: + +/2") + .sound("sawtooth").lpf("200 1000"), + note("<[c3,g3,e4] [bb2,f3,d4] [a2,f3,c4] [bb2,g3,eb4]>/2") + .sound("sawtooth").vowel("/2") +) `} +/> + + + +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. + + + +**shape the sound with an adsr envelope** + +") +.sound("sawtooth").lpf(600) +.attack(.1) +.decay(.1) +.sustain(.25) +.release(.2)`} +/> + + + +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? + + + + + +- 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 + +![ADSR](https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/ADSR_parameter.svg/1920px-ADSR_parameter.svg.png) + + + +**adsr short notation** + +") +.sound("sawtooth").lpf(600) +.adsr(".1:.1:.5:.2") +`} +/> + +**delay** + + ~]") + .sound("gm_electric_guitar_muted"), + sound("").bank("RolandTR707") +).delay(".5")`} +/> + + + +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? + + + + + +`delay("a:b:c")`: + +- a: delay volume +- b: delay time +- c: feedback (smaller number = quicker fade) + + + +**room aka reverb** + + ~@16] ~>/2") +.scale("D4:minor").sound("gm_accordion:2") +.room(2)`} +/> + + + +Try different values! + +Add a delay too! + + + +**little dub tune** + + ~]") + .sound("gm_electric_guitar_muted").delay(.5), + sound("").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: + + ~]") + .sound("gm_electric_guitar_muted").delay(.5), + sound("").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) +)`} +/> + + + +Try adding `.hush()` at the end of one of the patterns in the stack... + + + +**pan** + + + +**speed** + +").room(.2)`} /> + +**fast and slow** + +We can use `fast` and `slow` to change the tempo of a pattern outside of Mini-Notation: + + + + + +Change the `slow` value. Try replacing it with `fast`. + +What happens if you use a pattern like `.fast("<1 [2 4]>")`? + + + +By the way, inside Mini-Notation, `fast` is `*` and `slow` is `/`. + +")`} /> + +## automation with signals + +Instead of changing values stepwise, we can also control them with signals: + + + + + +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. + + + +**setting a range** + +By default, waves oscillate between 0 to 1. We can change that with `range`: + + + + + +What happens if you flip the range values? + + + +We can change the automation speed with slow / fast: + +/2") +.sound("sawtooth") +.lpf(sine.range(100, 2000).slow(8))`} +/> + + + +The whole automation will now take 8 cycles to repeat. + + + +## Recap + +| name | example | +| ----- | -------------------------------------------------------------------------------------------------- | +| lpf | ")`} /> | +| vowel | ")`} /> | +| gain | | +| delay | | +| room | | +| pan | | +| speed | ")`} /> | +| range | | + +Let us now take a look at some of Tidal's typical [pattern effects](/de/workshop/pattern-effects). diff --git a/website/src/pages/workshop/first-notes.mdx b/website/src/pages/workshop/first-notes.mdx new file mode 100644 index 000000000..330dd0721 --- /dev/null +++ b/website/src/pages/workshop/first-notes.mdx @@ -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** + + [midi2note(i + 36), i + 36]), + )} +/> + + + +Try out different numbers! + +Try decimal numbers, like 55.5 + + + +**play notes with letters** + + [n, n.split('')[0]]))} +/> + + + +Try out different letters (a - g). + +Can you find melodies that are actual words? Hint: ☕ 😉 ⚪ + + + +**add flats or sharps to play the black keys** + + [n, n.split('').slice(0, 2).join('')]), + )} +/> + + [n, n.split('').slice(0, 2).join('')]), + )} +/> + +**play notes with letters in different octaves** + + [n, n]))} + claviatureLabels={Object.fromEntries( + Array(49) + .fill() + .map((_, i) => [midi2note(i + 36), midi2note(i + 36)]), + )} +/> + + + +Try out different octaves (1-8) + + + +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`: + + + +{/* c2 g2, e3 b3 d4 e4 */} + + + +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 + + + +**switch between sounds** + + + +**stack multiple sounds** + + + + + +The `note` and `sound` patterns are combined! + +We will see more ways to combine patterns later.. + + + +## Longer Sequences + +**Divide sequences with `/` to slow them down** + +{/* [c2 bb1 f2 eb2] */} + + + + + +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. + + + +Because it is so common to just play one thing per cycle, you can.. + +**Play one per cycle with \< \>** + +").sound("gm_acoustic_bass")`} punchcard /> + + + +Try adding more notes inside the brackets and notice how it does **not** get faster. + + + +**Play one sequence per cycle** + +{/* <[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>/2 */} + +/2") +.sound("gm_acoustic_bass")`} + punchcard +/> + +**Alternate between multiple things** + +") +.sound("gm_xylophone")`} + punchcard +/> + +This is also useful for unpitched sounds: + +, [~ hh]*2") +.bank("RolandTR909")`} + punchcard +/> + +## Scales + +Finding the right notes can be difficult.. Scales are here to help: + +") +.scale("C:minor").sound("piano")`} + punchcard +/> + + + +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 + + + +**automate scales** + +Just like anything, we can automate the scale with a pattern: + +, 2 4 <[6,8] [7,9]>") +.scale("/4") +.sound("piano")`} + punchcard +/> + + + +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! + + + +## Repeat & Elongate + +**Elongate with @** + + + + + +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! + + + +**Elongate within sub-sequences** + +*2") +.scale("/4") +.sound("gm_acoustic_bass")`} + punchcard +/> + + + +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. + + + +**Replicate** + +]").sound("piano")`} punchcard /> + + + +Try switching between `!`, `*` and `@` + +What's the difference? + + + +## Recap + +Let's recap what we've learned in this chapter: + +| Concept | Syntax | Example | +| --------- | ------ | ------------------------------------------------------------------- | +| Slow down | \/ | | +| Alternate | \<\> | ")`} /> | +| Elongate | @ | | +| Replicate | ! | | + +New functions: + +| Name | Description | Example | +| ----- | ----------------------------------- | -------------------------------------------------------------------------------------------- | +| note | set pitch as number or letter | | +| scale | interpret `n` as scale degree | | +| stack | play patterns in parallel (read on) | | + +## Examples + +**Classy Bassline** + +/2") +.sound("gm_synth_bass_1") +.lpf(800) // <-- we'll learn about this soon`} +/> + +**Classy Melody** + +*2\`).scale("C4:minor") +.sound("gm_synth_strings_1")`} +/> + +**Classy Drums** + +, [~ hh]*2") +.bank("RolandTR909")`} +/> + +**If there just was a way to play all the above at the same time.......** + + + +It's called `stack` 😙 + + + +/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, ~ , [~ 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) diff --git a/website/src/pages/workshop/first-sounds.mdx b/website/src/pages/workshop/first-sounds.mdx new file mode 100644 index 000000000..893a563dd --- /dev/null +++ b/website/src/pages/workshop/first-sounds.mdx @@ -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: + + + + + +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 + + + +Congratulations, you are now live coding! + +## Sounds + +We have just played a sound with `sound` like this: + + + + + +`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 + + + +**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: + + + + + +Try different sound / sample number combinations. + +Not adding a number is like doing `:0` + + + +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: + + + + + +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! + + + +To change the sound character of our drums, we can use `bank` to change the drum machine: + + + +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. + + + +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! + + + +## Sequences + +In the last example, we already saw that you can play multiple sounds in a sequence by separating them with a space: + + + +Notice how the currently playing sound is highlighted in the code and also visualized below. + + + +Try adding more sounds to the sequence! + + + +**The longer the sequence, the faster it runs** + + + +The content of a sequence will be squished into what's called a cycle. + +**One way to change the tempo is using `cpm`** + + + + + +cpm = cycles per minute + +By default, the tempo is 60 cycles per minute = 1 cycle per second. + + + +We will look at other ways to change the tempo later! + +**Add a rests in a sequence with '~'** + + + +**Sub-Sequences with [brackets]** + + + + + +Try adding more sounds inside a bracket! + + + +Similar to the whole sequence, the content of a sub-sequence will be squished to the its own length. + +**Multiplication: Speed things up** + + + +**Multiplication: Speed up sequences** + + + +**Multiplication: Speeeeeeeeed things up** + + + + + +Pitch = really fast rhythm + + + +**Sub-Sub-Sequences with [[brackets]]** + + + + + +You can go as deep as you want! + + + +**Play sequences in parallel with comma** + + + +You can use as many commas as you want: + + + +Commas can also be used inside sub-sequences: + + + + + +Notice how the 2 above are the same? + +It is quite common that there are many ways to express the same idea. + + + +**Multiple Lines with backticks** + + + +**selecting sample numbers separately** + +Instead of using ":", we can also use the `n` function to select sample numbers: + + + +This is shorter and more readable than: + + + +## 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 | | +| Sample Number | :x | | +| Rests | ~ | | +| Sub-Sequences | \[\] | | +| Sub-Sub-Sequences | \[\[\]\] | | +| Speed up | \* | | +| Parallel | , | | + +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 | | +| bank | selects the sound bank | | +| cpm | sets the tempo in cycles per minute | | +| n | select sample number | | + +## Examples + +**Basic rock beat** + + + +**Classic house** + + + + + +Notice that the two patterns are extremely similar. +Certain drum patterns are reused across genres. + + + +We Will Rock you + + + +**Yellow Magic Orchestra - Firecracker** + + + +**Imitation of a 16 step sequencer** + + + +**Another one** + + + +**Not your average drums** + + + +Now that we know the basics of how to make beats, let's look at how we can play [notes](/workshop/first-notes) diff --git a/website/src/pages/workshop/getting-started.mdx b/website/src/pages/workshop/getting-started.mdx new file mode 100644 index 000000000..66eecdce5 --- /dev/null +++ b/website/src/pages/workshop/getting-started.mdx @@ -0,0 +1,70 @@ +--- +title: Getting Started +layout: ../../layouts/MainLayout.astro +--- + +import { MiniRepl } from '../../docs/MiniRepl'; + +# Welcome + + + +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.
+It is an official port of the [Tidal Cycles](https://tidalcycles.org/) pattern language to JavaScript.
+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.
+The best place to actually make music with Strudel is the [Strudel REPL](https://strudel.tidalcycles.org/) + +
+ +## 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: + +],hh*8") + .speed(perlin.range(.8,.9)), // random sample speed variation + // bassline + "" + .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 + ">".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) diff --git a/website/src/pages/workshop/index.astro b/website/src/pages/workshop/index.astro new file mode 100644 index 000000000..9f79e4c22 --- /dev/null +++ b/website/src/pages/workshop/index.astro @@ -0,0 +1,3 @@ + diff --git a/website/src/pages/workshop/pattern-effects.mdx b/website/src/pages/workshop/pattern-effects.mdx new file mode 100644 index 000000000..23346ccb7 --- /dev/null +++ b/website/src/pages/workshop/pattern-effects.mdx @@ -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** + + + +**play pattern left and modify it right with jux** + + + +This is the same as: + + + +Let's visualize what happens here: + + + + + +Try commenting out one of the two by adding `//` before a line + + + +**multiple tempos** + + + +This is like doing + + + + + +Try commenting out one or more by adding `//` before a line + + + +**add** + +>")) +.color(">").adsr("[.1 0]:.2:[1 0]") +.sound("gm_acoustic_bass").room(.5)`} + punchcard +/> + + + +If you add a number to a note, the note will be treated as if it was a number + + + +We can add as often as we like: + +>").add("0,7")) +.color(">").adsr("[.1 0]:.2:[1 0]") +.sound("gm_acoustic_bass").room(.5)`} + punchcard +/> + +**add with scale** + + [~ <4 1>]>*2".add("<0 [0,2,4]>/4")) +.scale("C5:minor").release(.5) +.sound("gm_xylophone").room(.5)`} + punchcard +/> + +**time to stack** + + [~ <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** + + + +this is like writing: + + + + + +Try patterning the `ply` function, for example using `"<1 2 1 3>"` + + + +**off** + +] <2 3> [~ 1]>" + .off(1/8, x=>x.add(4)) + //.off(1/4, x=>x.add(7)) +).scale("/4") +.s("triangle").room(.5).ds(".1:0").delay(.5)`} + punchcard +/> + + + +In the notation `x=>x.`, the `x` is the shifted pattern, which where modifying. + + + +off is also useful for sounds: + +x.speed(1.5).gain(.25))`} +/> + +| name | description | example | +| ---- | ------------------------------ | ---------------------------------------------------------------------------------------------- | +| rev | reverse | | +| jux | split left/right, modify right | | +| add | add numbers / notes | ")).scale("C:minor")`} /> | +| ply | speed up each event n times | ")`} /> | +| off | copy, shift time & modify | x.speed(2))`} /> | diff --git a/website/src/pages/workshop/recap.mdx b/website/src/pages/workshop/recap.mdx new file mode 100644 index 000000000..fad14fb4f --- /dev/null +++ b/website/src/pages/workshop/recap.mdx @@ -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 | | +| Sample Number | :x | | +| Rests | ~ | | +| Sub-Sequences | \[\] | | +| Sub-Sub-Sequences | \[\[\]\] | | +| Speed up | \* | | +| Parallel | , | | +| Slow down | \/ | | +| Alternate | \<\> | ")`} /> | +| Elongate | @ | | +| Replicate | ! | | + +## Sounds + +| Name | Description | Example | +| ----- | --------------------------------- | ---------------------------------------------------------------------------------- | +| sound | plays the sound of the given name | | +| bank | selects the sound bank | | +| n | select sample number | | + +## Notes + +| Name | Description | Example | +| --------- | ----------------------------- | -------------------------------------------------------------------------------------------- | +| note | set pitch as number or letter | | +| n + scale | set note in scale | | +| stack | play patterns in parallel | | + +## Audio Effects + +| name | example | +| ----- | -------------------------------------------------------------------------------------------------- | +| lpf | ")`} /> | +| vowel | ")`} /> | +| gain | | +| delay | | +| room | | +| pan | | +| speed | ")`} /> | +| range | | + +## Pattern Effects + +| name | description | example | +| ---- | ----------------------------------- | ---------------------------------------------------------------------------------------------- | +| cpm | sets the tempo in cycles per minute | | +| fast | speed up | | +| slow | slow down | | +| rev | reverse | | +| jux | split left/right, modify right | | +| add | add numbers / notes | ")).scale("C:minor")`} /> | +| ply | speed up each event n times | ")`} /> | +| off | copy, shift time & modify | x.speed(2))`} /> | diff --git a/website/src/repl/Header.jsx b/website/src/repl/Header.jsx index 213e336e9..53623466a 100644 --- a/website/src/repl/Header.jsx +++ b/website/src/repl/Header.jsx @@ -122,7 +122,7 @@ export function Header({ context }) { {!isEmbedded && ( diff --git a/website/src/repl/Repl.css b/website/src/repl/Repl.css index ddedad6f6..6815966be 100644 --- a/website/src/repl/Repl.css +++ b/website/src/repl/Repl.css @@ -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; } diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 4027b0754..ce4807010 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -157,7 +157,7 @@ export function Repl({ embedded = false }) { e.preventDefault(); flash(view); await activateCode(); - } else if (e.key === '.') { + } else if (e.key === '.' || e.keyCode === 'Period') { stop(); e.preventDefault(); } diff --git a/website/src/repl/prebake.mjs b/website/src/repl/prebake.mjs index edf63f546..68bbd8c7a 100644 --- a/website/src/repl/prebake.mjs +++ b/website/src/repl/prebake.mjs @@ -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'); diff --git a/website/tsconfig.json b/website/tsconfig.json index 78017eafe..90aa524fd 100644 --- a/website/tsconfig.json +++ b/website/tsconfig.json @@ -7,6 +7,11 @@ "noImplicitAny": false, "types": [ "vite-plugin-pwa/client" - ] + ], + "baseUrl": ".", + "paths": { + "@components/*": ["src/components/*"], + "@src/*": ["src/*"], + } } } \ No newline at end of file