mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-25 20:48:27 +00:00
Merge remote-tracking branch 'upstream/main' into branch-daria
This commit is contained in:
commit
623661f8d2
3
packages/codemirror/README.md
Normal file
3
packages/codemirror/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# @strudel/codemirror
|
||||||
|
|
||||||
|
This package contains helpers and extensions to use codemirror6. See [vite-vanilla-repl-cm6](../core/examples/vite-vanilla-repl-cm6/main.js) as an example of using it.
|
||||||
199
packages/codemirror/codemirror.mjs
Normal file
199
packages/codemirror/codemirror.mjs
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import { EditorState } from '@codemirror/state';
|
||||||
|
import { EditorView, keymap, Decoration, lineNumbers, highlightActiveLineGutter } from '@codemirror/view';
|
||||||
|
import { defaultKeymap } from '@codemirror/commands';
|
||||||
|
import { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language';
|
||||||
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
|
import { StateField, StateEffect } from '@codemirror/state';
|
||||||
|
import { oneDark } from './themes/one-dark';
|
||||||
|
import { repl, Drawer } from '@strudel.cycles/core';
|
||||||
|
|
||||||
|
// https://codemirror.net/docs/guide/
|
||||||
|
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, theme = oneDark, root }) {
|
||||||
|
let state = EditorState.create({
|
||||||
|
doc: initialCode,
|
||||||
|
extensions: [
|
||||||
|
theme,
|
||||||
|
javascript(),
|
||||||
|
lineNumbers(),
|
||||||
|
highlightField,
|
||||||
|
highlightActiveLineGutter(),
|
||||||
|
syntaxHighlighting(defaultHighlightStyle),
|
||||||
|
keymap.of(defaultKeymap),
|
||||||
|
flashField,
|
||||||
|
EditorView.updateListener.of((v) => onChange(v)),
|
||||||
|
keymap.of([
|
||||||
|
{
|
||||||
|
key: 'Ctrl-Enter',
|
||||||
|
run: () => onEvaluate(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Ctrl-.',
|
||||||
|
run: () => onStop(),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return new EditorView({
|
||||||
|
state,
|
||||||
|
parent: root,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// highlighting
|
||||||
|
//
|
||||||
|
|
||||||
|
export const setHighlights = StateEffect.define();
|
||||||
|
export const highlightField = StateField.define({
|
||||||
|
create() {
|
||||||
|
return Decoration.none;
|
||||||
|
},
|
||||||
|
update(highlights, tr) {
|
||||||
|
try {
|
||||||
|
for (let e of tr.effects) {
|
||||||
|
if (e.is(setHighlights)) {
|
||||||
|
const { haps } = e.value;
|
||||||
|
const marks =
|
||||||
|
haps
|
||||||
|
.map((hap) =>
|
||||||
|
(hap.context.locations || []).map(({ start, end }) => {
|
||||||
|
// const color = hap.context.color || e.value.color || '#FFCA28';
|
||||||
|
let from = tr.newDoc.line(start.line).from + start.column;
|
||||||
|
let to = tr.newDoc.line(end.line).from + end.column;
|
||||||
|
const l = tr.newDoc.length;
|
||||||
|
if (from > l || to > l) {
|
||||||
|
return; // dont mark outside of range, as it will throw an error
|
||||||
|
}
|
||||||
|
const mark = Decoration.mark({
|
||||||
|
attributes: { style: `outline: 2px solid #FFCA28;` },
|
||||||
|
});
|
||||||
|
return mark.range(from, to);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.flat()
|
||||||
|
.filter(Boolean) || [];
|
||||||
|
highlights = Decoration.set(marks, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return highlights;
|
||||||
|
} catch (err) {
|
||||||
|
// console.warn('highlighting error', err);
|
||||||
|
return Decoration.set([]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
provide: (f) => EditorView.decorations.from(f),
|
||||||
|
});
|
||||||
|
|
||||||
|
// helper to simply trigger highlighting for given haps
|
||||||
|
export function highlightHaps(view, haps) {
|
||||||
|
view.dispatch({ effects: setHighlights.of({ haps }) });
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// flash
|
||||||
|
//
|
||||||
|
|
||||||
|
export const setFlash = StateEffect.define();
|
||||||
|
const flashField = StateField.define({
|
||||||
|
create() {
|
||||||
|
return Decoration.none;
|
||||||
|
},
|
||||||
|
update(flash, tr) {
|
||||||
|
try {
|
||||||
|
for (let e of tr.effects) {
|
||||||
|
if (e.is(setFlash)) {
|
||||||
|
if (e.value) {
|
||||||
|
const mark = Decoration.mark({ attributes: { style: `background-color: #FFCA2880` } });
|
||||||
|
flash = Decoration.set([mark.range(0, tr.newDoc.length)]);
|
||||||
|
} else {
|
||||||
|
flash = Decoration.set([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flash;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('flash error', err);
|
||||||
|
return flash;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
provide: (f) => EditorView.decorations.from(f),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const flash = (view, ms = 200) => {
|
||||||
|
view.dispatch({ effects: setFlash.of(true) });
|
||||||
|
setTimeout(() => {
|
||||||
|
view.dispatch({ effects: setFlash.of(false) });
|
||||||
|
}, ms);
|
||||||
|
};
|
||||||
|
|
||||||
|
export class StrudelMirror {
|
||||||
|
constructor(options) {
|
||||||
|
const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, ...replOptions } = options;
|
||||||
|
this.code = initialCode;
|
||||||
|
|
||||||
|
this.drawer = new Drawer((haps, time) => {
|
||||||
|
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.whole.end);
|
||||||
|
this.highlight(currentFrame);
|
||||||
|
onDraw?.(haps, time, currentFrame);
|
||||||
|
}, drawTime);
|
||||||
|
|
||||||
|
const prebaked = prebake();
|
||||||
|
prebaked.then(async () => {
|
||||||
|
if (!onDraw) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { scheduler, evaluate } = await this.repl;
|
||||||
|
// draw first frame instantly
|
||||||
|
prebaked.then(async () => {
|
||||||
|
await evaluate(this.code, false);
|
||||||
|
this.drawer.invalidate(scheduler);
|
||||||
|
onDraw?.(this.drawer.visibleHaps, 0, []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.repl = repl({
|
||||||
|
...replOptions,
|
||||||
|
onToggle: async (started) => {
|
||||||
|
replOptions?.onToggle?.(started);
|
||||||
|
const { scheduler } = await this.repl;
|
||||||
|
if (started) {
|
||||||
|
this.drawer.start(scheduler);
|
||||||
|
} else {
|
||||||
|
this.drawer.stop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeEval: async () => {
|
||||||
|
await prebaked;
|
||||||
|
},
|
||||||
|
afterEval: (options) => {
|
||||||
|
replOptions?.afterEval?.(options);
|
||||||
|
this.drawer.invalidate();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.editor = initEditor({
|
||||||
|
root,
|
||||||
|
initialCode,
|
||||||
|
onChange: (v) => {
|
||||||
|
this.code = v.state.doc.toString();
|
||||||
|
},
|
||||||
|
onEvaluate: () => this.evaluate(),
|
||||||
|
onStop: () => this.stop(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async evaluate() {
|
||||||
|
const { evaluate } = await this.repl;
|
||||||
|
this.flash();
|
||||||
|
await evaluate(this.code);
|
||||||
|
}
|
||||||
|
async stop() {
|
||||||
|
const { scheduler } = await this.repl;
|
||||||
|
scheduler.stop();
|
||||||
|
}
|
||||||
|
flash(ms) {
|
||||||
|
flash(this.editor, ms);
|
||||||
|
}
|
||||||
|
highlight(haps) {
|
||||||
|
highlightHaps(this.editor, haps);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
packages/codemirror/package.json
Normal file
47
packages/codemirror/package.json
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "@strudel/codemirror",
|
||||||
|
"version": "0.8.3",
|
||||||
|
"description": "Codemirror Extensions for Strudel",
|
||||||
|
"main": "codemirror.mjs",
|
||||||
|
"publishConfig": {
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"module": "dist/index.mjs"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"tidalcycles",
|
||||||
|
"strudel",
|
||||||
|
"pattern",
|
||||||
|
"livecoding",
|
||||||
|
"algorave"
|
||||||
|
],
|
||||||
|
"author": "Felix Roos <flix91@gmail.com>",
|
||||||
|
"contributors": [
|
||||||
|
"Alex McLean <alex@slab.org>"
|
||||||
|
],
|
||||||
|
"license": "AGPL-3.0-or-later",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/commands": "^6.2.4",
|
||||||
|
"@codemirror/lang-javascript": "^6.1.7",
|
||||||
|
"@codemirror/language": "^6.6.0",
|
||||||
|
"@codemirror/state": "^6.2.0",
|
||||||
|
"@codemirror/view": "^6.10.0",
|
||||||
|
"@lezer/highlight": "^1.1.4",
|
||||||
|
"@strudel.cycles/core": "workspace:*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^4.3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
139
packages/codemirror/themes/one-dark.mjs
vendored
Normal file
139
packages/codemirror/themes/one-dark.mjs
vendored
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { EditorView } from '@codemirror/view';
|
||||||
|
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
|
|
||||||
|
// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors
|
||||||
|
|
||||||
|
const chalky = '#e5c07b',
|
||||||
|
coral = '#e06c75',
|
||||||
|
cyan = '#56b6c2',
|
||||||
|
invalid = '#ffffff',
|
||||||
|
ivory = '#abb2bf',
|
||||||
|
stone = '#7d8799', // Brightened compared to original to increase contrast
|
||||||
|
malibu = '#61afef',
|
||||||
|
sage = '#98c379',
|
||||||
|
whiskey = '#d19a66',
|
||||||
|
violet = '#c678dd',
|
||||||
|
darkBackground = '#21252b',
|
||||||
|
highlightBackground = '#2c313a',
|
||||||
|
background = '#282c34',
|
||||||
|
tooltipBackground = '#353a42',
|
||||||
|
selection = '#3E4451',
|
||||||
|
cursor = '#528bff';
|
||||||
|
|
||||||
|
/// The colors used in the theme, as CSS color strings.
|
||||||
|
export const color = {
|
||||||
|
chalky,
|
||||||
|
coral,
|
||||||
|
cyan,
|
||||||
|
invalid,
|
||||||
|
ivory,
|
||||||
|
stone,
|
||||||
|
malibu,
|
||||||
|
sage,
|
||||||
|
whiskey,
|
||||||
|
violet,
|
||||||
|
darkBackground,
|
||||||
|
highlightBackground,
|
||||||
|
background,
|
||||||
|
tooltipBackground,
|
||||||
|
selection,
|
||||||
|
cursor,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The editor theme styles for One Dark.
|
||||||
|
export const oneDarkTheme = EditorView.theme(
|
||||||
|
{
|
||||||
|
'&': {
|
||||||
|
color: ivory,
|
||||||
|
backgroundColor: background,
|
||||||
|
},
|
||||||
|
|
||||||
|
'.cm-content': {
|
||||||
|
caretColor: cursor,
|
||||||
|
},
|
||||||
|
|
||||||
|
'.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
|
||||||
|
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':
|
||||||
|
{ backgroundColor: selection },
|
||||||
|
|
||||||
|
'.cm-panels': { backgroundColor: darkBackground, color: ivory },
|
||||||
|
'.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
|
||||||
|
'.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
|
||||||
|
|
||||||
|
'.cm-searchMatch': {
|
||||||
|
backgroundColor: '#72a1ff59',
|
||||||
|
outline: '1px solid #457dff',
|
||||||
|
},
|
||||||
|
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||||
|
backgroundColor: '#6199ff2f',
|
||||||
|
},
|
||||||
|
|
||||||
|
'.cm-activeLine': { backgroundColor: '#6699ff0b' },
|
||||||
|
'.cm-selectionMatch': { backgroundColor: '#aafe661a' },
|
||||||
|
|
||||||
|
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||||
|
backgroundColor: '#bad0f847',
|
||||||
|
},
|
||||||
|
|
||||||
|
'.cm-gutters': {
|
||||||
|
backgroundColor: background,
|
||||||
|
color: stone,
|
||||||
|
border: 'none',
|
||||||
|
},
|
||||||
|
|
||||||
|
'.cm-activeLineGutter': {
|
||||||
|
backgroundColor: highlightBackground,
|
||||||
|
},
|
||||||
|
|
||||||
|
'.cm-foldPlaceholder': {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
color: '#ddd',
|
||||||
|
},
|
||||||
|
|
||||||
|
'.cm-tooltip': {
|
||||||
|
border: 'none',
|
||||||
|
backgroundColor: tooltipBackground,
|
||||||
|
},
|
||||||
|
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||||
|
borderTopColor: 'transparent',
|
||||||
|
borderBottomColor: 'transparent',
|
||||||
|
},
|
||||||
|
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||||
|
borderTopColor: tooltipBackground,
|
||||||
|
borderBottomColor: tooltipBackground,
|
||||||
|
},
|
||||||
|
'.cm-tooltip-autocomplete': {
|
||||||
|
'& > ul > li[aria-selected]': {
|
||||||
|
backgroundColor: highlightBackground,
|
||||||
|
color: ivory,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ dark: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The highlighting style for code in the One Dark theme.
|
||||||
|
export const oneDarkHighlightStyle = HighlightStyle.define([
|
||||||
|
{ tag: t.keyword, color: violet },
|
||||||
|
{ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: coral },
|
||||||
|
{ tag: [t.function(t.variableName), t.labelName], color: malibu },
|
||||||
|
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey },
|
||||||
|
{ tag: [t.definition(t.name), t.separator], color: ivory },
|
||||||
|
{ tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: chalky },
|
||||||
|
{ tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], color: cyan },
|
||||||
|
{ tag: [t.meta, t.comment], color: stone },
|
||||||
|
{ tag: t.strong, fontWeight: 'bold' },
|
||||||
|
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||||
|
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||||
|
{ tag: t.link, color: stone, textDecoration: 'underline' },
|
||||||
|
{ tag: t.heading, fontWeight: 'bold', color: coral },
|
||||||
|
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey },
|
||||||
|
{ tag: [t.processingInstruction, t.string, t.inserted], color: sage },
|
||||||
|
{ tag: t.invalid, color: invalid },
|
||||||
|
]);
|
||||||
|
|
||||||
|
/// Extension to enable the One Dark theme (both the editor theme and
|
||||||
|
/// the highlight style).
|
||||||
|
export const oneDark = [oneDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)];
|
||||||
19
packages/codemirror/vite.config.js
Normal file
19
packages/codemirror/vite.config.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import { dependencies } from './package.json';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [],
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'codemirror.mjs'),
|
||||||
|
formats: ['es', 'cjs'],
|
||||||
|
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: [...Object.keys(dependencies)],
|
||||||
|
},
|
||||||
|
target: 'esnext',
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -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/>.
|
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';
|
import { zipWith } from './util.mjs';
|
||||||
|
|
||||||
const controls = {};
|
const controls = {};
|
||||||
@ -810,4 +810,15 @@ generic_params.forEach(([names, ...aliases]) => {
|
|||||||
controls.createParams = (...names) =>
|
controls.createParams = (...names) =>
|
||||||
names.reduce((acc, name) => Object.assign(acc, { [name]: controls.createParam(name) }), {});
|
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;
|
export default controls;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
draw.mjs - <short description TODO>
|
draw.mjs - <short description TODO>
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/tone/draw.mjs>
|
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/draw.mjs>
|
||||||
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/>.
|
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/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ export const getDrawContext = (id = 'test-canvas') => {
|
|||||||
canvas.id = id;
|
canvas.id = id;
|
||||||
canvas.width = window.innerWidth;
|
canvas.width = window.innerWidth;
|
||||||
canvas.height = window.innerHeight;
|
canvas.height = window.innerHeight;
|
||||||
canvas.style = 'pointer-events:none;width:100%;height:100%;position:fixed;top:0;left:0;z-index:5';
|
canvas.style = 'pointer-events:none;width:100%;height:100%;position:fixed;top:0;left:0';
|
||||||
document.body.prepend(canvas);
|
document.body.prepend(canvas);
|
||||||
}
|
}
|
||||||
return canvas.getContext('2d');
|
return canvas.getContext('2d');
|
||||||
@ -65,3 +65,97 @@ Pattern.prototype.onPaint = function (onPaint) {
|
|||||||
this.context = { onPaint };
|
this.context = { onPaint };
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const round = (x) => Math.round(x * 1000) / 1000;
|
||||||
|
|
||||||
|
// encapsulates starting and stopping animation frames
|
||||||
|
export class Framer {
|
||||||
|
constructor(onFrame, onError) {
|
||||||
|
this.onFrame = onFrame;
|
||||||
|
this.onError = onError;
|
||||||
|
}
|
||||||
|
start() {
|
||||||
|
const self = this;
|
||||||
|
let frame = requestAnimationFrame(function updateHighlights(time) {
|
||||||
|
try {
|
||||||
|
self.onFrame(time);
|
||||||
|
} catch (err) {
|
||||||
|
self.onError(err);
|
||||||
|
}
|
||||||
|
frame = requestAnimationFrame(updateHighlights);
|
||||||
|
});
|
||||||
|
self.cancel = () => {
|
||||||
|
cancelAnimationFrame(frame);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
if (this.cancel) {
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncs animation frames to a cyclist scheduler
|
||||||
|
// see vite-vanilla-repl-cm6 for an example
|
||||||
|
export class Drawer {
|
||||||
|
constructor(onDraw, drawTime) {
|
||||||
|
let [lookbehind, lookahead] = drawTime; // e.g. [-2, 2]
|
||||||
|
lookbehind = Math.abs(lookbehind);
|
||||||
|
this.visibleHaps = [];
|
||||||
|
this.lastFrame = null;
|
||||||
|
this.drawTime = drawTime;
|
||||||
|
this.framer = new Framer(
|
||||||
|
() => {
|
||||||
|
if (!this.scheduler) {
|
||||||
|
console.warn('Drawer: no scheduler');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// calculate current frame time (think right side of screen for pianoroll)
|
||||||
|
const phase = this.scheduler.now() + lookahead;
|
||||||
|
// first frame just captures the phase
|
||||||
|
if (this.lastFrame === null) {
|
||||||
|
this.lastFrame = phase;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// query haps from last frame till now. take last 100ms max
|
||||||
|
const haps = this.scheduler.pattern.queryArc(Math.max(this.lastFrame, phase - 1 / 10), phase);
|
||||||
|
this.lastFrame = phase;
|
||||||
|
this.visibleHaps = (this.visibleHaps || [])
|
||||||
|
// filter out haps that are too far in the past (think left edge of screen for pianoroll)
|
||||||
|
.filter((h) => h.whole.end >= phase - lookbehind - lookahead)
|
||||||
|
// add new haps with onset (think right edge bars scrolling in)
|
||||||
|
.concat(haps.filter((h) => h.hasOnset()));
|
||||||
|
const time = phase - lookahead;
|
||||||
|
onDraw(this.visibleHaps, time, this);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
console.warn('draw error', err);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
invalidate(scheduler = this.scheduler) {
|
||||||
|
if (!scheduler) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.scheduler = scheduler;
|
||||||
|
const t = scheduler.now();
|
||||||
|
let [_, lookahead] = this.drawTime;
|
||||||
|
const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1];
|
||||||
|
// remove all future haps
|
||||||
|
this.visibleHaps = this.visibleHaps.filter((h) => h.whole.begin < t);
|
||||||
|
// query future haps
|
||||||
|
const futureHaps = scheduler.pattern.queryArc(begin, end); // +0.1 = workaround for weird holes in query..
|
||||||
|
// append future haps
|
||||||
|
this.visibleHaps = this.visibleHaps.concat(futureHaps);
|
||||||
|
}
|
||||||
|
start(scheduler) {
|
||||||
|
this.scheduler = scheduler;
|
||||||
|
this.invalidate();
|
||||||
|
this.framer.start();
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
if (this.framer) {
|
||||||
|
this.framer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
24
packages/core/examples/vite-vanilla-repl-cm6/.gitignore
vendored
Normal file
24
packages/core/examples/vite-vanilla-repl-cm6/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
8
packages/core/examples/vite-vanilla-repl-cm6/README.md
Normal file
8
packages/core/examples/vite-vanilla-repl-cm6/README.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# vite-vanilla-repl-cm6
|
||||||
|
|
||||||
|
This folder demonstrates how to set up a strudel repl using vite and vanilla JS + codemirror. Run it using:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm i
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
22
packages/core/examples/vite-vanilla-repl-cm6/index.html
Normal file
22
packages/core/examples/vite-vanilla-repl-cm6/index.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite Vanilla Strudel REPL</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<nav>
|
||||||
|
<button id="play">eval</button>
|
||||||
|
<button id="stop">stop</button>
|
||||||
|
</nav>
|
||||||
|
<div class="container">
|
||||||
|
<div id="editor"></div>
|
||||||
|
<div id="output"></div>
|
||||||
|
</div>
|
||||||
|
<canvas id="roll"></canvas>
|
||||||
|
</main>
|
||||||
|
<script type="module" src="./main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
39
packages/core/examples/vite-vanilla-repl-cm6/main.js
Normal file
39
packages/core/examples/vite-vanilla-repl-cm6/main.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { StrudelMirror } from '@strudel/codemirror';
|
||||||
|
import { funk42 } from './tunes';
|
||||||
|
import { drawPianoroll, evalScope, controls } from '@strudel.cycles/core';
|
||||||
|
import './style.css';
|
||||||
|
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
||||||
|
import { transpiler } from '@strudel.cycles/transpiler';
|
||||||
|
import { getAudioContext, webaudioOutput, registerSynthSounds } from '@strudel.cycles/webaudio';
|
||||||
|
import { registerSoundfonts } from '@strudel.cycles/soundfonts';
|
||||||
|
|
||||||
|
// init canvas
|
||||||
|
const canvas = document.getElementById('roll');
|
||||||
|
canvas.width = canvas.width * 2;
|
||||||
|
canvas.height = canvas.height * 2;
|
||||||
|
const drawContext = canvas.getContext('2d');
|
||||||
|
const drawTime = [-2, 2]; // time window of drawn haps
|
||||||
|
|
||||||
|
const editor = new StrudelMirror({
|
||||||
|
defaultOutput: webaudioOutput,
|
||||||
|
getTime: () => getAudioContext().currentTime,
|
||||||
|
transpiler,
|
||||||
|
root: document.getElementById('editor'),
|
||||||
|
initialCode: funk42,
|
||||||
|
drawTime,
|
||||||
|
onDraw: (haps, time) => drawPianoroll({ haps, time, ctx: drawContext, drawTime, fold: 0 }),
|
||||||
|
prebake: async () => {
|
||||||
|
initAudioOnFirstClick(); // needed to make the browser happy (don't await this here..)
|
||||||
|
const loadModules = evalScope(
|
||||||
|
controls,
|
||||||
|
import('@strudel.cycles/core'),
|
||||||
|
import('@strudel.cycles/mini'),
|
||||||
|
import('@strudel.cycles/tonal'),
|
||||||
|
import('@strudel.cycles/webaudio'),
|
||||||
|
);
|
||||||
|
await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('play').addEventListener('click', () => editor.evaluate());
|
||||||
|
document.getElementById('stop').addEventListener('click', () => editor.stop());
|
||||||
23
packages/core/examples/vite-vanilla-repl-cm6/package.json
Normal file
23
packages/core/examples/vite-vanilla-repl-cm6/package.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "vite-vanilla-repl-cm6",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^4.3.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@strudel/codemirror": "workspace:*",
|
||||||
|
"@strudel.cycles/core": "workspace:*",
|
||||||
|
"@strudel.cycles/mini": "workspace:*",
|
||||||
|
"@strudel.cycles/soundfonts": "workspace:*",
|
||||||
|
"@strudel.cycles/tonal": "workspace:*",
|
||||||
|
"@strudel.cycles/transpiler": "workspace:*",
|
||||||
|
"@strudel.cycles/webaudio": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
||||||
31
packages/core/examples/vite-vanilla-repl-cm6/style.css
Normal file
31
packages/core/examples/vite-vanilla-repl-cm6/style.css
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
body,
|
||||||
|
html {
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: #282c34;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
flex-grow: 1;
|
||||||
|
max-height: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-editor {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#roll {
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
112
packages/core/examples/vite-vanilla-repl-cm6/tunes.mjs
Normal file
112
packages/core/examples/vite-vanilla-repl-cm6/tunes.mjs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
export const bumpStreet = `// froos - "22 bump street", licensed with CC BY-NC-SA 4.0
|
||||||
|
await samples('github:felixroos/samples/main')
|
||||||
|
await samples('https://strudel.tidalcycles.org/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
|
||||||
|
|
||||||
|
"<[0,<6 7 9>,13,<17 20 22 26>]!2>/2"
|
||||||
|
// make it 22 edo
|
||||||
|
.fmap(v => Math.pow(2,v/22))
|
||||||
|
// mess with the base frequency
|
||||||
|
.mul("<300 [300@3 200]>/8").freq()
|
||||||
|
.layer(
|
||||||
|
// chords
|
||||||
|
x=>x.div(freq(2)).s("flute").euclidLegato("<3 2>",8)
|
||||||
|
.shape(.4).lpf(sine.range(800,4000).slow(8)),
|
||||||
|
// adlibs
|
||||||
|
x=>x.arp("{0 3 2 [1 3]}%1.5")
|
||||||
|
.s('xylo').mul(freq(2))
|
||||||
|
.delay(.5).delayfeedback(.4).juxBy(.5, rev)
|
||||||
|
.hpf(sine.range(200,3000).slow(8)),
|
||||||
|
// bass
|
||||||
|
x=>x.arp("[0 [2 1?]](5,8)").s('sawtooth').div(freq(4))
|
||||||
|
.lpf(sine.range(400,2000).slow(8)).lpq(8).shape(.4)
|
||||||
|
.off(1/8, x=>x.mul(freq(2)).degradeBy(.5)).gain(.3)
|
||||||
|
).clip(1).release(.2)
|
||||||
|
.stack(
|
||||||
|
// drums
|
||||||
|
s("bd sd:<2 1>, [~ hh]*2, [~ rim]").bank('RolandTR909')
|
||||||
|
.off(1/8, x=>x.speed(2).gain(.4)).sometimes(ply(2)).gain(.8)
|
||||||
|
.mask("<0@4 1@12>/4")
|
||||||
|
.reset("<x@15 [x(3,8) x*[4 8]]>")
|
||||||
|
// wait for it...
|
||||||
|
).fast(2/3)
|
||||||
|
//.crush(6) // remove "//" if you dare`;
|
||||||
|
|
||||||
|
export const trafficFlam = `// froos - "traffic flam", licensed with CC BY-NC-SA 4.0
|
||||||
|
|
||||||
|
await samples('github:felixroos/samples/main')
|
||||||
|
await samples('https://strudel.tidalcycles.org/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
|
||||||
|
|
||||||
|
addVoicings('hip', {
|
||||||
|
m11: ['2M 3m 4P 7m'],
|
||||||
|
'^7#11': ['3M 4A 5P 7M'],
|
||||||
|
}, ['C4', 'C6'])
|
||||||
|
|
||||||
|
stack(
|
||||||
|
stack(
|
||||||
|
"<Bbm11 A^7#11>/2".voicings('hip').note()
|
||||||
|
.s("gm_epiano1:2")
|
||||||
|
.arp("[<[0 1 2 3] [3 2 1 0]> ~@5]/2")
|
||||||
|
.release(2).late(.25).lpf(2000),
|
||||||
|
"<Bb1 A1>/2".note().s('gm_acoustic_bass'),
|
||||||
|
n("<0 2 3>(3,8)".off(1/8, add(4)))
|
||||||
|
.scale("<Bb4:minor A4:lydian>/2")
|
||||||
|
.s('gm_electric_guitar_jazz')
|
||||||
|
.decay(sine.range(.05, .2).slow(32)).sustain(0)
|
||||||
|
.delay(.5).lpf(sine.range(100,5000).slow(64))
|
||||||
|
.gain(.7).room(.5).pan(sine.range(0,1).slow(11))
|
||||||
|
).add(perlin.range(0,.25).note()),
|
||||||
|
stack(
|
||||||
|
s("bd:1(3,8) rim").bank('RolandTR707').slow(2).room("<0 <.1 .6>>")
|
||||||
|
.when("<0@7 1>",x=>x.echoWith(3, .0625, (x,i) => x.speed(1+i*.24))),
|
||||||
|
s("rim*4").end(.05).bank('RolandTR808').speed(.8).room(.2)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.late("[0 .05]*2").late(12)
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const funk42 = `// froos - how to funk in 42 lines of code
|
||||||
|
// adapted from "how to funk in two minutes" by marc rebillet https://www.youtube.com/watch?v=3vBwRfQbXkg
|
||||||
|
// thanks to peach for the transcription: https://www.youtube.com/watch?v=8eiPXvIgda4
|
||||||
|
|
||||||
|
await samples('github:felixroos/samples/main')
|
||||||
|
await samples('https://strudel.tidalcycles.org/tidal-drum-machines.json', 'github:ritchse/tidal-drum-machines/main/machines/')
|
||||||
|
|
||||||
|
setcps(.5)
|
||||||
|
|
||||||
|
let drums = stack(
|
||||||
|
s("bd*2, ~ sd").bank('RolandTR707').room("0 .1"),
|
||||||
|
s("hh*4").begin(.2).release(.02).end(.25).release(.02)
|
||||||
|
.gain(.3).bank('RolandTR707').late(.02).room(.5),
|
||||||
|
//s("shaker_small").struct("[x x*2]*2").speed(".8,.9").release(.02)
|
||||||
|
).fast(2)
|
||||||
|
|
||||||
|
let wurli = note(\`<
|
||||||
|
[[a2,g3,[b3 c4],e4] ~ [g3,c4,e4](3,8)@4 ~@2]!3
|
||||||
|
[[e2,e3,a3,b3,e4]@3 [e2,e3,ab3,b3,e4]@5]>\`)
|
||||||
|
.s("gm_epiano1:5").decay(.2).sustain("<[1 0@7]!3 1>")
|
||||||
|
.gain("<[.8@2 .4@14]!3 .7>").room(.3)
|
||||||
|
|
||||||
|
let organ = note("<[~@3 [a3,d4,f#4]@2 [[a3,c4,e4]@2 ~] ~@2]!3 ~>".add(12))
|
||||||
|
.s("gm_percussive_organ:2").gain(.6).lpf(1800).pan(.2).room(.3);
|
||||||
|
|
||||||
|
let clav = note(\`<
|
||||||
|
[~@3 a2 [g3,[b3 c4],e4]@2 ~ a2 [g3,b3,e4] ~@2 [g3,c4,e4] ~@4]!3
|
||||||
|
[~@3 e3 [[a3 b3],c3,e3]@2 ~ e2 [e3,a3]@3 [b3,e3] ~@2 [b3,e3]@2]>\`)
|
||||||
|
.s("gm_clavinet:1").decay("<.25!3 [.25 .4]>").sustain(0)
|
||||||
|
.gain(.7).pan(.8).room(.2);
|
||||||
|
|
||||||
|
let bass = note(\`<
|
||||||
|
[a1 [~ [g2 a2]] [g1 g#1] [a1 [g2 a2]]]
|
||||||
|
[a1 [~ [g2 a2]] [e3 d3] [c3 [g3 a3]]]
|
||||||
|
[a1 [~ [g2 a2]] [g1 g#1] [a1 [g2 a2]]]
|
||||||
|
[e2@6 e1@5 e1 [[d2 e3] g1]@4]
|
||||||
|
>\`).s("gm_electric_bass_pick:1").release(.1)
|
||||||
|
|
||||||
|
stack(
|
||||||
|
drums
|
||||||
|
,wurli
|
||||||
|
,organ
|
||||||
|
,clav
|
||||||
|
,bass
|
||||||
|
)`;
|
||||||
@ -6,5 +6,3 @@ This folder demonstrates how to set up a strudel repl using vite and vanilla JS.
|
|||||||
npm i
|
npm i
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
or view it [live on githack](https://rawcdn.githack.com/tidalcycles/strudel/5fb36acb046ead7cd6ad3cd10f532e7f585f536a/packages/core/examples/vite-vanilla-repl/dist/index.html)
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ export * from './util.mjs';
|
|||||||
export * from './speak.mjs';
|
export * from './speak.mjs';
|
||||||
export * from './evaluate.mjs';
|
export * from './evaluate.mjs';
|
||||||
export * from './repl.mjs';
|
export * from './repl.mjs';
|
||||||
|
export * from './cyclist.mjs';
|
||||||
export * from './logger.mjs';
|
export * from './logger.mjs';
|
||||||
export * from './time.mjs';
|
export * from './time.mjs';
|
||||||
export * from './draw.mjs';
|
export * from './draw.mjs';
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/core",
|
"name": "@strudel.cycles/core",
|
||||||
"version": "0.7.2",
|
"version": "0.8.1",
|
||||||
"description": "Port of Tidal Cycles to JavaScript",
|
"description": "Port of Tidal Cycles to JavaScript",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@ -1677,6 +1677,9 @@ export const ply = register('ply', function (factor, pat) {
|
|||||||
* s("<bd sd> hh").fast(2) // s("[<bd sd> hh]*2")
|
* s("<bd sd> hh").fast(2) // s("[<bd sd> hh]*2")
|
||||||
*/
|
*/
|
||||||
export const { fast, density } = register(['fast', 'density'], function (factor, pat) {
|
export const { fast, density } = register(['fast', 'density'], function (factor, pat) {
|
||||||
|
if (factor === 0) {
|
||||||
|
return silence;
|
||||||
|
}
|
||||||
factor = Fraction(factor);
|
factor = Fraction(factor);
|
||||||
const fastQuery = pat.withQueryTime((t) => t.mul(factor));
|
const fastQuery = pat.withQueryTime((t) => t.mul(factor));
|
||||||
return fastQuery.withHapTime((t) => t.div(factor));
|
return fastQuery.withHapTime((t) => t.div(factor));
|
||||||
@ -1703,6 +1706,9 @@ export const hurry = register('hurry', function (r, pat) {
|
|||||||
* s("<bd sd> hh").slow(2) // s("[<bd sd> hh]/2")
|
* s("<bd sd> hh").slow(2) // s("[<bd sd> hh]/2")
|
||||||
*/
|
*/
|
||||||
export const { slow, sparsity } = register(['slow', 'sparsity'], function (factor, pat) {
|
export const { slow, sparsity } = register(['slow', 'sparsity'], function (factor, pat) {
|
||||||
|
if (factor === 0) {
|
||||||
|
return silence;
|
||||||
|
}
|
||||||
return pat._fast(Fraction(1).div(factor));
|
return pat._fast(Fraction(1).div(factor));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -50,6 +50,7 @@ Pattern.prototype.pianoroll = function ({
|
|||||||
timeframe: timeframeProp,
|
timeframe: timeframeProp,
|
||||||
fold = 0,
|
fold = 0,
|
||||||
vertical = 0,
|
vertical = 0,
|
||||||
|
labels = 0,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const ctx = getDrawContext();
|
const ctx = getDrawContext();
|
||||||
const w = ctx.canvas.width;
|
const w = ctx.canvas.width;
|
||||||
@ -87,7 +88,7 @@ Pattern.prototype.pianoroll = function ({
|
|||||||
const isActive = event.whole.begin <= t && event.whole.end > t;
|
const isActive = event.whole.begin <= t && event.whole.end > t;
|
||||||
ctx.fillStyle = event.context?.color || inactive;
|
ctx.fillStyle = event.context?.color || inactive;
|
||||||
ctx.strokeStyle = event.context?.color || active;
|
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);
|
const timePx = scale((event.whole.begin - (flipTime ? to : from)) / timeExtent, ...timeRange);
|
||||||
let durationPx = scale(event.duration / timeExtent, 0, timeAxis);
|
let durationPx = scale(event.duration / timeExtent, 0, timeAxis);
|
||||||
const value = getValue(event);
|
const value = getValue(event);
|
||||||
@ -114,6 +115,14 @@ Pattern.prototype.pianoroll = function ({
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
isActive ? ctx.strokeRect(...coords) : ctx.fillRect(...coords);
|
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!
|
ctx.globalAlpha = 1; // reset!
|
||||||
const playheadPosition = scale(-from / timeExtent, ...timeRange);
|
const playheadPosition = scale(-from / timeExtent, ...timeRange);
|
||||||
@ -181,6 +190,7 @@ export function pianoroll({
|
|||||||
timeframe: timeframeProp,
|
timeframe: timeframeProp,
|
||||||
fold = 0,
|
fold = 0,
|
||||||
vertical = 0,
|
vertical = 0,
|
||||||
|
labels = false,
|
||||||
ctx,
|
ctx,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const w = ctx.canvas.width;
|
const w = ctx.canvas.width;
|
||||||
@ -240,7 +250,7 @@ export function pianoroll({
|
|||||||
const color = event.value?.color || event.context?.color;
|
const color = event.value?.color || event.context?.color;
|
||||||
ctx.fillStyle = color || inactive;
|
ctx.fillStyle = color || inactive;
|
||||||
ctx.strokeStyle = color || active;
|
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);
|
const timePx = scale((event.whole.begin - (flipTime ? to : from)) / timeExtent, ...timeRange);
|
||||||
let durationPx = scale(event.duration / timeExtent, 0, timeAxis);
|
let durationPx = scale(event.duration / timeExtent, 0, timeAxis);
|
||||||
const value = getValue(event);
|
const value = getValue(event);
|
||||||
@ -267,6 +277,14 @@ export function pianoroll({
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
isActive ? ctx.strokeRect(...coords) : ctx.fillRect(...coords);
|
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!
|
ctx.globalAlpha = 1; // reset!
|
||||||
const playheadPosition = scale(-from / timeExtent, ...timeRange);
|
const playheadPosition = scale(-from / timeExtent, ...timeRange);
|
||||||
@ -284,7 +302,7 @@ export function pianoroll({
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOptions(drawTime, options = {}) {
|
export function getDrawOptions(drawTime, options = {}) {
|
||||||
let [lookbehind, lookahead] = drawTime;
|
let [lookbehind, lookahead] = drawTime;
|
||||||
lookbehind = Math.abs(lookbehind);
|
lookbehind = Math.abs(lookbehind);
|
||||||
const cycles = lookahead + lookbehind;
|
const cycles = lookahead + lookbehind;
|
||||||
@ -293,5 +311,18 @@ function getOptions(drawTime, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Pattern.prototype.punchcard = function (options) {
|
Pattern.prototype.punchcard = function (options) {
|
||||||
return this.onPaint((ctx, time, haps, drawTime) => pianoroll({ ctx, time, haps, ...getOptions(drawTime, options) }));
|
return this.onPaint((ctx, time, haps, drawTime) =>
|
||||||
|
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, options) }),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Pattern.prototype.pianoroll = function (options) {
|
||||||
|
return this.onPaint((ctx, time, haps, drawTime) =>
|
||||||
|
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, { fold: 0, ...options }) }),
|
||||||
|
);
|
||||||
|
}; */
|
||||||
|
|
||||||
|
export function drawPianoroll(options) {
|
||||||
|
const { drawTime, ...rest } = options;
|
||||||
|
pianoroll({ ...getDrawOptions(drawTime), ...rest });
|
||||||
|
}
|
||||||
|
|||||||
@ -18,23 +18,15 @@ export function repl({
|
|||||||
}) {
|
}) {
|
||||||
const scheduler = new Cyclist({
|
const scheduler = new Cyclist({
|
||||||
interval,
|
interval,
|
||||||
onTrigger: async (hap, deadline, duration, cps) => {
|
onTrigger: getTrigger({ defaultOutput, getTime }),
|
||||||
try {
|
|
||||||
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
|
|
||||||
await defaultOutput(hap, deadline, duration, cps);
|
|
||||||
}
|
|
||||||
if (hap.context.onTrigger) {
|
|
||||||
// call signature of output / onTrigger is different...
|
|
||||||
await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
logger(`[cyclist] error: ${err.message}`, 'error');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: onSchedulerError,
|
onError: onSchedulerError,
|
||||||
getTime,
|
getTime,
|
||||||
onToggle,
|
onToggle,
|
||||||
});
|
});
|
||||||
|
const setPattern = (pattern, autostart = true) => {
|
||||||
|
pattern = editPattern?.(pattern) || pattern;
|
||||||
|
scheduler.setPattern(pattern, autostart);
|
||||||
|
};
|
||||||
setTime(() => scheduler.now()); // TODO: refactor?
|
setTime(() => scheduler.now()); // TODO: refactor?
|
||||||
const evaluate = async (code, autostart = true) => {
|
const evaluate = async (code, autostart = true) => {
|
||||||
if (!code) {
|
if (!code) {
|
||||||
@ -45,8 +37,7 @@ export function repl({
|
|||||||
let { pattern } = await _evaluate(code, transpiler);
|
let { pattern } = await _evaluate(code, transpiler);
|
||||||
|
|
||||||
logger(`[eval] code updated`);
|
logger(`[eval] code updated`);
|
||||||
pattern = editPattern?.(pattern) || pattern;
|
setPattern(pattern, autostart);
|
||||||
scheduler.setPattern(pattern, autostart);
|
|
||||||
afterEval?.({ code, pattern });
|
afterEval?.({ code, pattern });
|
||||||
return pattern;
|
return pattern;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -63,5 +54,21 @@ export function repl({
|
|||||||
setCps,
|
setCps,
|
||||||
setcps: setCps,
|
setcps: setCps,
|
||||||
});
|
});
|
||||||
return { scheduler, evaluate, start, stop, pause, setCps };
|
return { scheduler, evaluate, start, stop, pause, setCps, setPattern };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getTrigger =
|
||||||
|
({ getTime, defaultOutput }) =>
|
||||||
|
async (hap, deadline, duration, cps) => {
|
||||||
|
try {
|
||||||
|
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
|
||||||
|
await defaultOutput(hap, deadline, duration, cps);
|
||||||
|
}
|
||||||
|
if (hap.context.onTrigger) {
|
||||||
|
// call signature of output / onTrigger is different...
|
||||||
|
await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger(`[cyclist] error: ${err.message}`, 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@ -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
|
// returns true if the given string is a note
|
||||||
export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name);
|
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) => {
|
export const tokenizeNote = (note) => {
|
||||||
if (typeof note !== 'string') {
|
if (typeof note !== 'string') {
|
||||||
return [];
|
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) {
|
if (!pc) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -25,7 +25,7 @@ export const noteToMidi = (note) => {
|
|||||||
throw new Error('not a note: "' + 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 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;
|
return (Number(oct) + 1) * 12 + chroma + offset;
|
||||||
};
|
};
|
||||||
export const midiToFreq = (n) => {
|
export const midiToFreq = (n) => {
|
||||||
@ -67,14 +67,9 @@ export const getFreq = (noteOrMidi) => {
|
|||||||
return midiToFreq(noteToMidi(noteOrMidi));
|
return midiToFreq(noteToMidi(noteOrMidi));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated does not appear to be referenced or invoked anywhere in the codebase
|
|
||||||
* @noAutocomplete
|
|
||||||
*/
|
|
||||||
/* added code from here to solve issue 302*/
|
|
||||||
|
|
||||||
export const midi2note = (n, notation = 'letters') => {
|
/* added code from here to solve issue 302*/
|
||||||
const solfeggio = ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si']; /*solffegio notes*/
|
const solfeggio = ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si']; /*solffegio notes*/
|
||||||
const indian = [
|
const indian = [
|
||||||
'Sa',
|
'Sa',
|
||||||
'Re',
|
'Re',
|
||||||
@ -108,6 +103,11 @@ export const midi2note = (n, notation = 'letters') => {
|
|||||||
'He',
|
'He',
|
||||||
'To',
|
'To',
|
||||||
]; /*traditional japanese musical notes, seems like they do not use falts or sharps*/
|
]; /*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 =
|
const pc =
|
||||||
notation === 'solfeggio'
|
notation === 'solfeggio'
|
||||||
? solfeggio /*check if its is any of the following*/
|
? solfeggio /*check if its is any of the following*/
|
||||||
@ -119,11 +119,24 @@ export const midi2note = (n, notation = 'letters') => {
|
|||||||
? byzantine
|
? byzantine
|
||||||
: notation === 'japanese'
|
: notation === 'japanese'
|
||||||
? japanese
|
? japanese
|
||||||
: ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B']; /*if not use standard version*/
|
: english; /*if not use standard version*/
|
||||||
const note = pc[n % 12]; /*calculating the midi value to the note*/
|
const note = pc[n % 12]; /*calculating the midi value to the note*/
|
||||||
const oct = Math.floor(n / 12) - 1;
|
const oct = Math.floor(n / 12) - 1;
|
||||||
return note + oct;
|
return note + oct;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const pcs = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
|
||||||
|
/**
|
||||||
|
* @deprecated only used in workshop atm
|
||||||
|
* @noAutocomplete
|
||||||
|
*/
|
||||||
|
export const midi2note = (n, notation = 'letters') => {
|
||||||
|
const oct = Math.floor(n / 12) - 1;
|
||||||
|
//return note + oct;
|
||||||
|
const pc = pcs[n % 12];
|
||||||
|
return pc + oct;
|
||||||
|
};
|
||||||
/*testing if the function works by using the file solmization.test.mjs
|
/*testing if the function works by using the file solmization.test.mjs
|
||||||
in the test folder*/
|
in the test folder*/
|
||||||
|
|
||||||
@ -262,3 +275,5 @@ export const splitAt = function (index, value) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i]));
|
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);
|
||||||
|
|||||||
@ -190,3 +190,8 @@ export function minify(thing) {
|
|||||||
}
|
}
|
||||||
return strudel.reify(thing);
|
return strudel.reify(thing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calling this function will cause patterns to parse strings as mini notation by default
|
||||||
|
export function miniAllStrings() {
|
||||||
|
strudel.setStringParser(mini);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/mini",
|
"name": "@strudel.cycles/mini",
|
||||||
"version": "0.7.2",
|
"version": "0.8.1",
|
||||||
"description": "Mini notation for strudel",
|
"description": "Mini notation for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
"@strudel.cycles/osc": "workspace:*",
|
"@strudel.cycles/osc": "workspace:*",
|
||||||
"@strudel.cycles/mini": "workspace:*",
|
"@strudel.cycles/mini": "workspace:*",
|
||||||
"@strudel.cycles/transpiler": "workspace:*",
|
"@strudel.cycles/transpiler": "workspace:*",
|
||||||
|
"@strudel.cycles/soundfonts": "workspace:*",
|
||||||
"@strudel.cycles/webaudio": "workspace:*",
|
"@strudel.cycles/webaudio": "workspace:*",
|
||||||
"@strudel.cycles/tonal": "workspace:*",
|
"@strudel.cycles/tonal": "workspace:*",
|
||||||
"@strudel.cycles/react": "workspace:*"
|
"@strudel.cycles/react": "workspace:*"
|
||||||
|
|||||||
@ -1,22 +1,34 @@
|
|||||||
import { controls, evalScope } from '@strudel.cycles/core';
|
import { controls, evalScope } from '@strudel.cycles/core';
|
||||||
import { CodeMirror, useHighlighting, useKeydown, useStrudel, flash } from '@strudel.cycles/react';
|
import { CodeMirror, useHighlighting, useKeydown, useStrudel, flash } from '@strudel.cycles/react';
|
||||||
import { getAudioContext, initAudioOnFirstClick, panic, webaudioOutput } from '@strudel.cycles/webaudio';
|
import {
|
||||||
|
getAudioContext,
|
||||||
|
initAudioOnFirstClick,
|
||||||
|
panic,
|
||||||
|
webaudioOutput,
|
||||||
|
registerSynthSounds,
|
||||||
|
} from '@strudel.cycles/webaudio';
|
||||||
|
import { registerSoundfonts } from '@strudel.cycles/soundfonts';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import './style.css';
|
import './style.css';
|
||||||
// import { prebake } from '../../../../../repl/src/prebake.mjs';
|
// import { prebake } from '../../../../../repl/src/prebake.mjs';
|
||||||
|
|
||||||
initAudioOnFirstClick();
|
initAudioOnFirstClick();
|
||||||
|
|
||||||
// TODO: only import stuff when play is pressed?
|
async function init() {
|
||||||
evalScope(
|
// TODO: only import stuff when play is pressed?
|
||||||
controls,
|
const loadModules = evalScope(
|
||||||
import('@strudel.cycles/core'),
|
controls,
|
||||||
import('@strudel.cycles/tonal'),
|
import('@strudel.cycles/core'),
|
||||||
import('@strudel.cycles/mini'),
|
import('@strudel.cycles/tonal'),
|
||||||
import('@strudel.cycles/xen'),
|
import('@strudel.cycles/mini'),
|
||||||
import('@strudel.cycles/webaudio'),
|
import('@strudel.cycles/xen'),
|
||||||
import('@strudel.cycles/osc'),
|
import('@strudel.cycles/webaudio'),
|
||||||
);
|
import('@strudel.cycles/osc'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]);
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
|
||||||
const defaultTune = `samples({
|
const defaultTune = `samples({
|
||||||
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
|
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
|
||||||
@ -31,7 +43,7 @@ stack(
|
|||||||
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
|
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
|
||||||
.add(perlin.range(0,.5)) // random pitch variation
|
.add(perlin.range(0,.5)) // random pitch variation
|
||||||
.superimpose(add(.05)) // add second, slightly detuned voice
|
.superimpose(add(.05)) // add second, slightly detuned voice
|
||||||
.n() // wrap in "n"
|
.note() // wrap in "note"
|
||||||
.decay(.15).sustain(0) // make each note of equal length
|
.decay(.15).sustain(0) // make each note of equal length
|
||||||
.s('sawtooth') // waveform
|
.s('sawtooth') // waveform
|
||||||
.gain(.4) // turn down
|
.gain(.4) // turn down
|
||||||
@ -40,7 +52,7 @@ stack(
|
|||||||
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand') // chords
|
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand') // chords
|
||||||
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
||||||
.add(perlin.range(0,.5)) // random pitch variation
|
.add(perlin.range(0,.5)) // random pitch variation
|
||||||
.n() // wrap in "n"
|
.note() // wrap in "n"
|
||||||
.s('square') // waveform
|
.s('square') // waveform
|
||||||
.gain(.16) // turn down
|
.gain(.16) // turn down
|
||||||
.cutoff(500) // fixed cutoff
|
.cutoff(500) // fixed cutoff
|
||||||
@ -49,7 +61,7 @@ stack(
|
|||||||
,"a4 c5 <e6 a6>".struct("x(5,8)")
|
,"a4 c5 <e6 a6>".struct("x(5,8)")
|
||||||
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
||||||
.add(perlin.range(0,.5)) // random pitch variation
|
.add(perlin.range(0,.5)) // random pitch variation
|
||||||
.n() // wrap in "n"
|
.note() // wrap in "note"
|
||||||
.decay(.1).sustain(0) // make notes short
|
.decay(.1).sustain(0) // make notes short
|
||||||
.s('triangle') // waveform
|
.s('triangle') // waveform
|
||||||
.degradeBy(perlin.range(0,.5)) // randomly controlled random removal :)
|
.degradeBy(perlin.range(0,.5)) // randomly controlled random removal :)
|
||||||
@ -103,7 +115,7 @@ function App() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[scheduler, evaluate, view],
|
[scheduler, evaluate, view, code],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -67,7 +67,7 @@ const highlightField = StateField.define({
|
|||||||
}
|
}
|
||||||
let mark;
|
let mark;
|
||||||
if (color) {
|
if (color) {
|
||||||
mark = Decoration.mark({ attributes: { style: `outline: 4px solid ${color};` } });
|
mark = Decoration.mark({ attributes: { style: `outline: 2px solid ${color};` } });
|
||||||
} else {
|
} else {
|
||||||
mark = Decoration.mark({ attributes: { class: `outline outline-2 outline-foreground` } });
|
mark = Decoration.mark({ attributes: { class: `outline outline-2 outline-foreground` } });
|
||||||
}
|
}
|
||||||
@ -104,6 +104,7 @@ export default function CodeMirror({
|
|||||||
onSelectionChange,
|
onSelectionChange,
|
||||||
theme,
|
theme,
|
||||||
keybindings,
|
keybindings,
|
||||||
|
isLineNumbersDisplayed,
|
||||||
fontSize = 18,
|
fontSize = 18,
|
||||||
fontFamily = 'monospace',
|
fontFamily = 'monospace',
|
||||||
options,
|
options,
|
||||||
@ -148,6 +149,7 @@ export default function CodeMirror({
|
|||||||
onCreateEditor={handleOnCreateEditor}
|
onCreateEditor={handleOnCreateEditor}
|
||||||
onUpdate={handleOnUpdate}
|
onUpdate={handleOnUpdate}
|
||||||
extensions={extensions}
|
extensions={extensions}
|
||||||
|
basicSetup={{ lineNumbers: isLineNumbersDisplayed }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -18,16 +18,24 @@ export function MiniRepl({
|
|||||||
tune,
|
tune,
|
||||||
hideOutsideView = false,
|
hideOutsideView = false,
|
||||||
enableKeyboard,
|
enableKeyboard,
|
||||||
|
onTrigger,
|
||||||
drawTime,
|
drawTime,
|
||||||
punchcard,
|
punchcard,
|
||||||
|
punchcardLabels,
|
||||||
|
onPaint,
|
||||||
canvasHeight = 200,
|
canvasHeight = 200,
|
||||||
|
fontSize = 18,
|
||||||
|
fontFamily,
|
||||||
|
hideHeader = false,
|
||||||
theme,
|
theme,
|
||||||
|
keybindings,
|
||||||
|
isLineNumbersDisplayed,
|
||||||
}) {
|
}) {
|
||||||
drawTime = drawTime || (punchcard ? [0, 4] : undefined);
|
drawTime = drawTime || (punchcard ? [0, 4] : undefined);
|
||||||
const evalOnMount = !!drawTime;
|
const evalOnMount = !!drawTime;
|
||||||
const drawContext = useCallback(
|
const drawContext = useCallback(
|
||||||
!!drawTime ? (canvasId) => document.querySelector('#' + canvasId)?.getContext('2d') : null,
|
punchcard ? (canvasId) => document.querySelector('#' + canvasId)?.getContext('2d') : null,
|
||||||
[drawTime],
|
[punchcard],
|
||||||
);
|
);
|
||||||
const {
|
const {
|
||||||
code,
|
code,
|
||||||
@ -47,7 +55,18 @@ export function MiniRepl({
|
|||||||
} = useStrudel({
|
} = useStrudel({
|
||||||
initialCode: tune,
|
initialCode: tune,
|
||||||
defaultOutput: webaudioOutput,
|
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,
|
getTime,
|
||||||
evalOnMount,
|
evalOnMount,
|
||||||
drawContext,
|
drawContext,
|
||||||
@ -82,7 +101,7 @@ export function MiniRepl({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
flash(view);
|
flash(view);
|
||||||
await activateCode();
|
await activateCode();
|
||||||
} else if (e.key === '.') {
|
} else if (e.key === '.' || e.code === 'Period') {
|
||||||
stop();
|
stop();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
@ -101,7 +120,7 @@ export function MiniRepl({
|
|||||||
// const logId = data?.pattern?.meta?.id;
|
// const logId = data?.pattern?.meta?.id;
|
||||||
if (logId === replId) {
|
if (logId === replId) {
|
||||||
setLog((l) => {
|
setLog((l) => {
|
||||||
return l.concat([e.detail]).slice(-10);
|
return l.concat([e.detail]).slice(-8);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []),
|
}, []),
|
||||||
@ -109,33 +128,46 @@ export function MiniRepl({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-hidden rounded-t-md bg-background border border-lineHighlight" ref={ref}>
|
<div className="overflow-hidden rounded-t-md bg-background border border-lineHighlight" ref={ref}>
|
||||||
<div className="flex justify-between bg-lineHighlight">
|
{!hideHeader && (
|
||||||
<div className="flex">
|
<div className="flex justify-between bg-lineHighlight">
|
||||||
<button
|
<div className="flex">
|
||||||
className={cx(
|
<button
|
||||||
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background',
|
className={cx(
|
||||||
started ? 'animate-pulse' : '',
|
'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()}
|
)}
|
||||||
>
|
onClick={() => togglePlay()}
|
||||||
<Icon type={started ? 'stop' : 'play'} />
|
>
|
||||||
</button>
|
<Icon type={started ? 'stop' : 'play'} />
|
||||||
<button
|
</button>
|
||||||
className={cx(
|
<button
|
||||||
'w-16 flex items-center justify-center p-1 text-foreground border-lineHighlight bg-lineHighlight',
|
className={cx(
|
||||||
isDirty ? 'text-foreground hover:bg-background cursor-pointer' : 'opacity-50 cursor-not-allowed',
|
'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()}
|
)}
|
||||||
>
|
onClick={() => activateCode()}
|
||||||
<Icon type="refresh" />
|
>
|
||||||
</button>
|
<Icon type="refresh" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{error && <div className="text-right p-1 text-sm text-red-200">{error.message}</div>}
|
)}
|
||||||
</div>
|
|
||||||
<div className="overflow-auto relative">
|
<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>
|
</div>
|
||||||
{drawTime && (
|
{punchcard && (
|
||||||
<canvas
|
<canvas
|
||||||
id={canvasId}
|
id={canvasId}
|
||||||
className="w-full pointer-events-none"
|
className="w-full pointer-events-none"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
:root {
|
:root {
|
||||||
--background: #222;
|
--background: #222;
|
||||||
--lineBackground: #22222250;
|
--lineBackground: #22222299;
|
||||||
--foreground: #fff;
|
--foreground: #fff;
|
||||||
--caret: #ffcc00;
|
--caret: #ffcc00;
|
||||||
--selection: rgba(128, 203, 196, 0.5);
|
--selection: rgba(128, 203, 196, 0.5);
|
||||||
|
|||||||
@ -29,7 +29,8 @@ function useStrudel({
|
|||||||
const [pattern, setPattern] = useState();
|
const [pattern, setPattern] = useState();
|
||||||
const [started, setStarted] = useState(false);
|
const [started, setStarted] = useState(false);
|
||||||
const isDirty = code !== activeCode;
|
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
|
// TODO: make sure this hook reruns when scheduler.started changes
|
||||||
const { scheduler, evaluate, start, stop, pause, setCps } = useMemo(
|
const { scheduler, evaluate, start, stop, pause, setCps } = useMemo(
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/soundfonts",
|
"name": "@strudel.cycles/soundfonts",
|
||||||
"version": "0.7.1",
|
"version": "0.8.1",
|
||||||
"description": "Soundsfont support for strudel",
|
"description": "Soundsfont support for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/tonal",
|
"name": "@strudel.cycles/tonal",
|
||||||
"version": "0.7.1",
|
"version": "0.8.1",
|
||||||
"description": "Tonal functions for strudel",
|
"description": "Tonal functions for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/transpiler",
|
"name": "@strudel.cycles/transpiler",
|
||||||
"version": "0.7.1",
|
"version": "0.8.1",
|
||||||
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
|
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
82
packages/web/README.md
Normal file
82
packages/web/README.md
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
# @strudel/web
|
||||||
|
|
||||||
|
This package provides an easy to use bundle of multiple strudel packages for the web.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Save this code as a `.html` file and double click it:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<button id="play">play</button>
|
||||||
|
<button id="stop">stop</button>
|
||||||
|
<script type="module">
|
||||||
|
import { initStrudel } from 'https://cdn.skypack.dev/@strudel/web@0.8.2';
|
||||||
|
|
||||||
|
initStrudel();
|
||||||
|
document.getElementById('play').addEventListener('click', () => note('<c a f e>(3,8)').play());
|
||||||
|
document.getElementById('stop').addEventListener('click', () => hush());
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
With the help of [skypack](https://www.skypack.dev/), you don't need a bundler nor a server.
|
||||||
|
|
||||||
|
As soon as you call `initStrudel()`, all strudel functions are made available.
|
||||||
|
In this case, we are using the `note` function to create a pattern.
|
||||||
|
To actually play the pattern, you have to append `.play()` to the end.
|
||||||
|
|
||||||
|
Note: Due to the [Autoplay policy](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Best_practices#autoplay_policy), you can only play audio in a browser after a click event.
|
||||||
|
|
||||||
|
### Via npm
|
||||||
|
|
||||||
|
If you're using a bundler, you can install the package via `npm i @strudel/web`, then just import it like:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { initStrudel } from '@strudel/web';
|
||||||
|
```
|
||||||
|
|
||||||
|
The rest of the code should be the same. Check out [vite](https://vitejs.dev/) for a good bundler / dev server.
|
||||||
|
|
||||||
|
### Loading samples
|
||||||
|
|
||||||
|
By default, no external samples are loaded, but you can add them like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
initStrudel({
|
||||||
|
prebake: () => samples('github:tidalcycles/Dirt-Samples/master'),
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('play').addEventListener('click',
|
||||||
|
() => s("bd sd").play()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
You can learn [more about the `samples` function here](https://strudel.tidalcycles.org/learn/samples#loading-custom-samples).
|
||||||
|
|
||||||
|
### Evaluating Code
|
||||||
|
|
||||||
|
Instead of creating patterns directly in JS, you might also want to take in user input and turn that into a pattern.
|
||||||
|
This is called evaluation: Taking a piece of code and executing it on the fly.
|
||||||
|
|
||||||
|
To do that, you can use the `evaluate` function:
|
||||||
|
|
||||||
|
```js
|
||||||
|
initStrudel();
|
||||||
|
document.getElementById('play').addEventListener('click',
|
||||||
|
() => evaluate('note("c a f e").jux(rev)')
|
||||||
|
);
|
||||||
|
document.getElementById('play').addEventListener('stop',
|
||||||
|
() => hush()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Double vs Single Quotes
|
||||||
|
|
||||||
|
There is a tiny difference between the [Strudel REPL](https://strudel.tidalcycles.org/) and `@strudel/web`.
|
||||||
|
In the REPL you can use 'single quotes' for regular JS strings and "double quotes" for mini notation patterns.
|
||||||
|
In `@strudel/web`, it does not matter which types of quotes you're using.
|
||||||
|
There will probably be an escapte hatch for that in the future.
|
||||||
|
|
||||||
|
## More Examples
|
||||||
|
|
||||||
|
Check out the examples folder for more examples, both using plain html and vite!
|
||||||
10
packages/web/examples/evaluate.html
Normal file
10
packages/web/examples/evaluate.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<button id="play">play</button>
|
||||||
|
<button id="stop">stop</button>
|
||||||
|
<script type="module">
|
||||||
|
import { initStrudel } from 'https://cdn.skypack.dev/@strudel/web@0.8.2';
|
||||||
|
|
||||||
|
initStrudel();
|
||||||
|
document.getElementById('play').addEventListener('click', () => evaluate('note("c a f e").jux(rev)'));
|
||||||
|
document.getElementById('play').addEventListener('stop', () => hush());
|
||||||
|
</script>
|
||||||
16
packages/web/examples/headless-serverless-buildless.html
Normal file
16
packages/web/examples/headless-serverless-buildless.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<button id="a">A</button>
|
||||||
|
<button id="b">B</button>
|
||||||
|
<button id="c">C</button>
|
||||||
|
<button id="stop">stop</button>
|
||||||
|
<script type="module">
|
||||||
|
import { initStrudel } from 'https://cdn.skypack.dev/@strudel/web@0.8.2';
|
||||||
|
initStrudel({
|
||||||
|
prebake: () => samples('github:tidalcycles/Dirt-Samples/master'),
|
||||||
|
});
|
||||||
|
const click = (id, action) => document.getElementById(id).addEventListener('click', action);
|
||||||
|
click('a', () => evaluate(`s('bd,jvbass(3,8)').jux(rev)`));
|
||||||
|
click('b', () => s('bd*2,hh(3,4),jvbass(5,8,1)').jux(rev).play());
|
||||||
|
click('c', () => s('bd*2,hh(3,4),jvbass:[0 4](5,8,1)').jux(rev).stack(s('~ sd')).play());
|
||||||
|
click('stop', () => hush());
|
||||||
|
</script>
|
||||||
10
packages/web/examples/minimal.html
Normal file
10
packages/web/examples/minimal.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<button id="play">play</button>
|
||||||
|
<button id="stop">stop</button>
|
||||||
|
<script type="module">
|
||||||
|
import { initStrudel } from 'https://cdn.skypack.dev/@strudel/web@0.8.2';
|
||||||
|
|
||||||
|
initStrudel();
|
||||||
|
document.getElementById('play').addEventListener('click', () => note('<c a f e>(3,8)').play());
|
||||||
|
document.getElementById('stop').addEventListener('click', () => hush());
|
||||||
|
</script>
|
||||||
24
packages/web/examples/repl-example/.gitignore
vendored
Normal file
24
packages/web/examples/repl-example/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
28
packages/web/examples/repl-example/index.html
Normal file
28
packages/web/examples/repl-example/index.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="https://strudel.tidalcycles.org/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>@strudel/web REPL Example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<button id="a">A</button>
|
||||||
|
<button id="b">B</button>
|
||||||
|
<button id="c">C</button>
|
||||||
|
<button id="stop">stop</button>
|
||||||
|
<script type="module">
|
||||||
|
import { initStrudel } from '@strudel/web';
|
||||||
|
initStrudel({
|
||||||
|
prebake: () => samples('github:tidalcycles/Dirt-Samples/master'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const click = (id, action) => document.getElementById(id).addEventListener('click', action);
|
||||||
|
click('a', () => evaluate(`s('bd,jvbass(3,8)').jux(rev)`));
|
||||||
|
click('b', () => s('bd*2,hh(3,4),jvbass(5,8,1)').jux(rev).play());
|
||||||
|
click('c', () => s('bd*2,hh(3,4),jvbass:[0 4](5,8,1)').jux(rev).stack(s('~ sd')).play());
|
||||||
|
click('stop', () => hush());
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
18
packages/web/examples/repl-example/package.json
Normal file
18
packages/web/examples/repl-example/package.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "repl-example",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"license": "AGPL-3.0-or-later",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^4.3.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@strudel/web": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
packages/web/examples/samples.html
Normal file
12
packages/web/examples/samples.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<button id="play">play</button>
|
||||||
|
<button id="stop">stop</button>
|
||||||
|
<script type="module">
|
||||||
|
import { initStrudel } from 'https://cdn.skypack.dev/@strudel/web@0.8.2';
|
||||||
|
|
||||||
|
initStrudel({
|
||||||
|
prebake: () => samples('github:tidalcycles/Dirt-Samples/master'),
|
||||||
|
});
|
||||||
|
document.getElementById('play').addEventListener('click', () => s('[bd sd](3,8)').play());
|
||||||
|
document.getElementById('stop').addEventListener('click', () => hush());
|
||||||
|
</script>
|
||||||
45
packages/web/package.json
Normal file
45
packages/web/package.json
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"name": "@strudel/web",
|
||||||
|
"version": "0.8.2",
|
||||||
|
"description": "Easy to setup, opiniated bundle of Strudel for the browser.",
|
||||||
|
"main": "web.mjs",
|
||||||
|
"publishConfig": {
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"module": "dist/index.mjs"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"tidalcycles",
|
||||||
|
"strudel",
|
||||||
|
"pattern",
|
||||||
|
"livecoding",
|
||||||
|
"algorave"
|
||||||
|
],
|
||||||
|
"author": "Felix Roos <flix91@gmail.com>",
|
||||||
|
"contributors": [
|
||||||
|
"Alex McLean <alex@slab.org>"
|
||||||
|
],
|
||||||
|
"license": "AGPL-3.0-or-later",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"@strudel.cycles/core": "workspace:*",
|
||||||
|
"@strudel.cycles/webaudio": "workspace:*",
|
||||||
|
"@strudel.cycles/mini": "workspace:*",
|
||||||
|
"@strudel.cycles/tonal": "workspace:*",
|
||||||
|
"@strudel.cycles/transpiler": "workspace:*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^4.3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
packages/web/vite.config.js
Normal file
19
packages/web/vite.config.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import { dependencies } from './package.json';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [],
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'web.mjs'),
|
||||||
|
formats: ['es', 'cjs'],
|
||||||
|
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: [...Object.keys(dependencies)],
|
||||||
|
},
|
||||||
|
target: 'esnext',
|
||||||
|
},
|
||||||
|
});
|
||||||
66
packages/web/web.mjs
Normal file
66
packages/web/web.mjs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
export * from '@strudel.cycles/core';
|
||||||
|
export * from '@strudel.cycles/webaudio';
|
||||||
|
//export * from '@strudel.cycles/soundfonts';
|
||||||
|
export * from '@strudel.cycles/transpiler';
|
||||||
|
export * from '@strudel.cycles/mini';
|
||||||
|
export * from '@strudel.cycles/tonal';
|
||||||
|
export * from '@strudel.cycles/webaudio';
|
||||||
|
import { Pattern, evalScope, controls } from '@strudel.cycles/core';
|
||||||
|
import { initAudioOnFirstClick, registerSynthSounds, webaudioScheduler } from '@strudel.cycles/webaudio';
|
||||||
|
// import { registerSoundfonts } from '@strudel.cycles/soundfonts';
|
||||||
|
import { evaluate as _evaluate } from '@strudel.cycles/transpiler';
|
||||||
|
import { miniAllStrings } from '@strudel.cycles/mini';
|
||||||
|
|
||||||
|
// init logic
|
||||||
|
export async function defaultPrebake() {
|
||||||
|
const loadModules = evalScope(
|
||||||
|
evalScope,
|
||||||
|
controls,
|
||||||
|
import('@strudel.cycles/core'),
|
||||||
|
import('@strudel.cycles/mini'),
|
||||||
|
import('@strudel.cycles/tonal'),
|
||||||
|
import('@strudel.cycles/webaudio'),
|
||||||
|
{ hush, evaluate },
|
||||||
|
);
|
||||||
|
await Promise.all([loadModules, registerSynthSounds() /* , registerSoundfonts() */]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// when this function finishes, everything is initialized
|
||||||
|
let initDone;
|
||||||
|
|
||||||
|
let scheduler;
|
||||||
|
export function initStrudel(options = {}) {
|
||||||
|
initAudioOnFirstClick();
|
||||||
|
miniAllStrings();
|
||||||
|
const { prebake, ...schedulerOptions } = options;
|
||||||
|
|
||||||
|
initDone = (async () => {
|
||||||
|
await defaultPrebake();
|
||||||
|
await prebake?.();
|
||||||
|
})();
|
||||||
|
scheduler = webaudioScheduler(schedulerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.initStrudel = initStrudel;
|
||||||
|
|
||||||
|
// this method will play the pattern on the default scheduler
|
||||||
|
Pattern.prototype.play = function () {
|
||||||
|
if (!scheduler) {
|
||||||
|
throw new Error('.play: no scheduler found. Have you called init?');
|
||||||
|
}
|
||||||
|
initDone.then(() => {
|
||||||
|
scheduler.setPattern(this, true);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
// stop playback
|
||||||
|
export function hush() {
|
||||||
|
scheduler.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaluate and play the given code using the transpiler
|
||||||
|
export async function evaluate(code, autoplay = true) {
|
||||||
|
const { pattern } = await _evaluate(code);
|
||||||
|
autoplay && pattern.play();
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/webaudio",
|
"name": "@strudel.cycles/webaudio",
|
||||||
"version": "0.7.1",
|
"version": "0.8.1",
|
||||||
"description": "Web Audio helpers for Strudel",
|
"description": "Web Audio helpers for Strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@ -85,7 +85,12 @@ export async function initAudioOnFirstClick() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let delays = {};
|
let delays = {};
|
||||||
|
const maxfeedback = 0.98;
|
||||||
function getDelay(orbit, delaytime, delayfeedback, t) {
|
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]) {
|
if (!delays[orbit]) {
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
|
const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
|
||||||
@ -243,3 +248,16 @@ Pattern.prototype.webaudio = function () {
|
|||||||
// TODO: refactor (t, hap, ct, cps) to (hap, deadline, duration) ?
|
// TODO: refactor (t, hap, ct, cps) to (hap, deadline, duration) ?
|
||||||
return this.onTrigger(webaudioOutputTrigger);
|
return this.onTrigger(webaudioOutputTrigger);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function webaudioScheduler(options = {}) {
|
||||||
|
options = {
|
||||||
|
getTime: () => getAudioContext().currentTime,
|
||||||
|
defaultOutput: webaudioOutput,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
const { defaultOutput, getTime } = options;
|
||||||
|
return new strudel.Cyclist({
|
||||||
|
...options,
|
||||||
|
onTrigger: strudel.getTrigger({ defaultOutput, getTime }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
160
pnpm-lock.yaml
generated
160
pnpm-lock.yaml
generated
@ -66,6 +66,34 @@ importers:
|
|||||||
specifier: ^0.28.0
|
specifier: ^0.28.0
|
||||||
version: 0.28.0(@vitest/ui@0.28.0)
|
version: 0.28.0(@vitest/ui@0.28.0)
|
||||||
|
|
||||||
|
packages/codemirror:
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/commands':
|
||||||
|
specifier: ^6.2.4
|
||||||
|
version: 6.2.4
|
||||||
|
'@codemirror/lang-javascript':
|
||||||
|
specifier: ^6.1.7
|
||||||
|
version: 6.1.7
|
||||||
|
'@codemirror/language':
|
||||||
|
specifier: ^6.6.0
|
||||||
|
version: 6.6.0
|
||||||
|
'@codemirror/state':
|
||||||
|
specifier: ^6.2.0
|
||||||
|
version: 6.2.0
|
||||||
|
'@codemirror/view':
|
||||||
|
specifier: ^6.10.0
|
||||||
|
version: 6.10.0
|
||||||
|
'@lezer/highlight':
|
||||||
|
specifier: ^1.1.4
|
||||||
|
version: 1.1.4
|
||||||
|
'@strudel.cycles/core':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../core
|
||||||
|
devDependencies:
|
||||||
|
vite:
|
||||||
|
specifier: ^4.3.3
|
||||||
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
|
|
||||||
packages/core:
|
packages/core:
|
||||||
dependencies:
|
dependencies:
|
||||||
fraction.js:
|
fraction.js:
|
||||||
@ -74,7 +102,7 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@types/node@18.16.3)
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^0.28.0
|
specifier: ^0.28.0
|
||||||
version: 0.28.0(@vitest/ui@0.28.0)
|
version: 0.28.0(@vitest/ui@0.28.0)
|
||||||
@ -99,7 +127,35 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
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:
|
||||||
|
'@strudel.cycles/core':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../..
|
||||||
|
'@strudel.cycles/mini':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../mini
|
||||||
|
'@strudel.cycles/soundfonts':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../soundfonts
|
||||||
|
'@strudel.cycles/tonal':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../tonal
|
||||||
|
'@strudel.cycles/transpiler':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../transpiler
|
||||||
|
'@strudel.cycles/webaudio':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../webaudio
|
||||||
|
'@strudel/codemirror':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../codemirror
|
||||||
|
devDependencies:
|
||||||
|
vite:
|
||||||
|
specifier: ^4.3.2
|
||||||
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
|
|
||||||
packages/csound:
|
packages/csound:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -115,7 +171,7 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@types/node@18.16.3)
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
|
|
||||||
packages/embed: {}
|
packages/embed: {}
|
||||||
|
|
||||||
@ -148,7 +204,7 @@ importers:
|
|||||||
version: link:../mini
|
version: link:../mini
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@types/node@18.16.3)
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^0.28.0
|
specifier: ^0.28.0
|
||||||
version: 0.28.0(@vitest/ui@0.28.0)
|
version: 0.28.0(@vitest/ui@0.28.0)
|
||||||
@ -167,7 +223,7 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@types/node@18.16.3)
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
|
|
||||||
packages/mini:
|
packages/mini:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -180,7 +236,7 @@ importers:
|
|||||||
version: 3.0.2
|
version: 3.0.2
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@types/node@18.16.3)
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^0.28.0
|
specifier: ^0.28.0
|
||||||
version: 0.28.0(@vitest/ui@0.28.0)
|
version: 0.28.0(@vitest/ui@0.28.0)
|
||||||
@ -199,7 +255,7 @@ importers:
|
|||||||
version: 5.8.1
|
version: 5.8.1
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@types/node@18.16.3)
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
|
|
||||||
packages/react:
|
packages/react:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -220,10 +276,10 @@ importers:
|
|||||||
version: 1.1.4
|
version: 1.1.4
|
||||||
'@replit/codemirror-emacs':
|
'@replit/codemirror-emacs':
|
||||||
specifier: ^6.0.1
|
specifier: ^6.0.1
|
||||||
version: 6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
version: 6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
||||||
'@replit/codemirror-vim':
|
'@replit/codemirror-vim':
|
||||||
specifier: ^6.0.14
|
specifier: ^6.0.14
|
||||||
version: 6.0.14(@codemirror/commands@6.2.0)(@codemirror/language@6.6.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
version: 6.0.14(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
||||||
'@strudel.cycles/core':
|
'@strudel.cycles/core':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../core
|
version: link:../core
|
||||||
@ -269,7 +325,7 @@ importers:
|
|||||||
version: 3.3.2
|
version: 3.3.2
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
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:
|
packages/react/examples/nano-repl:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -285,6 +341,9 @@ importers:
|
|||||||
'@strudel.cycles/react':
|
'@strudel.cycles/react':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../..
|
version: link:../..
|
||||||
|
'@strudel.cycles/soundfonts':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../soundfonts
|
||||||
'@strudel.cycles/tonal':
|
'@strudel.cycles/tonal':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../../tonal
|
version: link:../../../tonal
|
||||||
@ -321,7 +380,7 @@ importers:
|
|||||||
version: 3.3.2
|
version: 3.3.2
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@types/node@18.16.3)
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
|
|
||||||
packages/serial:
|
packages/serial:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -331,7 +390,7 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@types/node@18.16.3)
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
|
|
||||||
packages/soundfonts:
|
packages/soundfonts:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -353,7 +412,7 @@ importers:
|
|||||||
version: 3.3.1
|
version: 3.3.1
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@types/node@18.16.3)
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
|
|
||||||
packages/tonal:
|
packages/tonal:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -372,7 +431,7 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@types/node@18.16.3)
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^0.28.0
|
specifier: ^0.28.0
|
||||||
version: 0.28.0(@vitest/ui@0.28.0)
|
version: 0.28.0(@vitest/ui@0.28.0)
|
||||||
@ -388,7 +447,7 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@types/node@18.16.3)
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^0.28.0
|
specifier: ^0.28.0
|
||||||
version: 0.28.0(@vitest/ui@0.28.0)
|
version: 0.28.0(@vitest/ui@0.28.0)
|
||||||
@ -410,11 +469,43 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@types/node@18.16.3)
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^0.28.0
|
specifier: ^0.28.0
|
||||||
version: 0.28.0(@vitest/ui@0.28.0)
|
version: 0.28.0(@vitest/ui@0.28.0)
|
||||||
|
|
||||||
|
packages/web:
|
||||||
|
dependencies:
|
||||||
|
'@strudel.cycles/core':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../core
|
||||||
|
'@strudel.cycles/mini':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../mini
|
||||||
|
'@strudel.cycles/tonal':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../tonal
|
||||||
|
'@strudel.cycles/transpiler':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../transpiler
|
||||||
|
'@strudel.cycles/webaudio':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../webaudio
|
||||||
|
devDependencies:
|
||||||
|
vite:
|
||||||
|
specifier: ^4.3.3
|
||||||
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
|
|
||||||
|
packages/web/examples/repl-example:
|
||||||
|
dependencies:
|
||||||
|
'@strudel/web':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../..
|
||||||
|
devDependencies:
|
||||||
|
vite:
|
||||||
|
specifier: ^4.3.2
|
||||||
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
|
|
||||||
packages/webaudio:
|
packages/webaudio:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@strudel.cycles/core':
|
'@strudel.cycles/core':
|
||||||
@ -426,7 +517,7 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@types/node@18.16.3)
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
|
|
||||||
packages/webdirt:
|
packages/webdirt:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -442,7 +533,7 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@types/node@18.16.3)
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^0.28.0
|
specifier: ^0.28.0
|
||||||
version: 0.28.0(@vitest/ui@0.28.0)
|
version: 0.28.0(@vitest/ui@0.28.0)
|
||||||
@ -455,7 +546,7 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
vite:
|
vite:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 4.3.3(@types/node@18.16.3)
|
version: 4.3.3(@types/node@18.11.18)
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^0.28.0
|
specifier: ^0.28.0
|
||||||
version: 0.28.0(@vitest/ui@0.28.0)
|
version: 0.28.0(@vitest/ui@0.28.0)
|
||||||
@ -555,6 +646,9 @@ importers:
|
|||||||
canvas:
|
canvas:
|
||||||
specifier: ^2.11.2
|
specifier: ^2.11.2
|
||||||
version: 2.11.2
|
version: 2.11.2
|
||||||
|
claviature:
|
||||||
|
specifier: ^0.1.0
|
||||||
|
version: 0.1.0
|
||||||
fraction.js:
|
fraction.js:
|
||||||
specifier: ^4.2.0
|
specifier: ^4.2.0
|
||||||
version: 4.2.0
|
version: 4.2.0
|
||||||
@ -2268,8 +2362,8 @@ packages:
|
|||||||
'@lezer/common': 1.0.2
|
'@lezer/common': 1.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@codemirror/commands@6.2.0:
|
/@codemirror/commands@6.2.4:
|
||||||
resolution: {integrity: sha512-+00smmZBradoGFEkRjliN7BjqPh/Hx0KCHWOEibUmflUqZz2RwBTU0MrVovEEHozhx3AUSGcO/rl3/5f9e9Biw==}
|
resolution: {integrity: sha512-42lmDqVH0ttfilLShReLXsDfASKLXzfyC36bzwcqzox9PlHulMcsUOfHXNo2X2aFMVNUoQ7j+d4q5bnfseYoOA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@codemirror/language': 6.6.0
|
'@codemirror/language': 6.6.0
|
||||||
'@codemirror/state': 6.2.0
|
'@codemirror/state': 6.2.0
|
||||||
@ -3452,7 +3546,7 @@ packages:
|
|||||||
escalade: 3.1.1
|
escalade: 3.1.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@replit/codemirror-emacs@6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0):
|
/@replit/codemirror-emacs@6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0):
|
||||||
resolution: {integrity: sha512-2WYkODZGH1QVAXWuOxTMCwktkoZyv/BjYdJi2A5w4fRrmOQFuIACzb6pO9dgU3J+Pm2naeiX2C8veZr/3/r6AA==}
|
resolution: {integrity: sha512-2WYkODZGH1QVAXWuOxTMCwktkoZyv/BjYdJi2A5w4fRrmOQFuIACzb6pO9dgU3J+Pm2naeiX2C8veZr/3/r6AA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@codemirror/autocomplete': ^6.0.2
|
'@codemirror/autocomplete': ^6.0.2
|
||||||
@ -3462,13 +3556,13 @@ packages:
|
|||||||
'@codemirror/view': ^6.3.0
|
'@codemirror/view': ^6.3.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@codemirror/autocomplete': 6.6.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)(@lezer/common@1.0.2)
|
'@codemirror/autocomplete': 6.6.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)(@lezer/common@1.0.2)
|
||||||
'@codemirror/commands': 6.2.0
|
'@codemirror/commands': 6.2.4
|
||||||
'@codemirror/search': 6.2.3
|
'@codemirror/search': 6.2.3
|
||||||
'@codemirror/state': 6.2.0
|
'@codemirror/state': 6.2.0
|
||||||
'@codemirror/view': 6.10.0
|
'@codemirror/view': 6.10.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@replit/codemirror-vim@6.0.14(@codemirror/commands@6.2.0)(@codemirror/language@6.6.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0):
|
/@replit/codemirror-vim@6.0.14(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0):
|
||||||
resolution: {integrity: sha512-wwhqhvL76FdRTdwfUWpKCbv0hkp2fvivfMosDVlL/popqOiNLtUhL02ThgHZH8mus/NkVr5Mj582lyFZqQrjOA==}
|
resolution: {integrity: sha512-wwhqhvL76FdRTdwfUWpKCbv0hkp2fvivfMosDVlL/popqOiNLtUhL02ThgHZH8mus/NkVr5Mj582lyFZqQrjOA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@codemirror/commands': ^6.0.0
|
'@codemirror/commands': ^6.0.0
|
||||||
@ -3477,7 +3571,7 @@ packages:
|
|||||||
'@codemirror/state': ^6.0.1
|
'@codemirror/state': ^6.0.1
|
||||||
'@codemirror/view': ^6.0.3
|
'@codemirror/view': ^6.0.3
|
||||||
dependencies:
|
dependencies:
|
||||||
'@codemirror/commands': 6.2.0
|
'@codemirror/commands': 6.2.4
|
||||||
'@codemirror/language': 6.6.0
|
'@codemirror/language': 6.6.0
|
||||||
'@codemirror/search': 6.2.3
|
'@codemirror/search': 6.2.3
|
||||||
'@codemirror/state': 6.2.0
|
'@codemirror/state': 6.2.0
|
||||||
@ -4073,7 +4167,7 @@ packages:
|
|||||||
eslint-visitor-keys: 3.3.0
|
eslint-visitor-keys: 3.3.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@uiw/codemirror-extensions-basic-setup@4.19.16(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.0)(@codemirror/language@6.6.0)(@codemirror/lint@6.1.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0):
|
/@uiw/codemirror-extensions-basic-setup@4.19.16(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/lint@6.1.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0):
|
||||||
resolution: {integrity: sha512-Xm0RDpyYVZ/8hWqaBs3+wZwi4uLwZUBwp/uCt89X80FeR6mr3BFuC+a+gcDO4dBu3l+WQE3jJdhjKjB2TCY/PQ==}
|
resolution: {integrity: sha512-Xm0RDpyYVZ/8hWqaBs3+wZwi4uLwZUBwp/uCt89X80FeR6mr3BFuC+a+gcDO4dBu3l+WQE3jJdhjKjB2TCY/PQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@codemirror/autocomplete': '>=6.0.0'
|
'@codemirror/autocomplete': '>=6.0.0'
|
||||||
@ -4085,7 +4179,7 @@ packages:
|
|||||||
'@codemirror/view': '>=6.0.0'
|
'@codemirror/view': '>=6.0.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@codemirror/autocomplete': 6.6.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)(@lezer/common@1.0.2)
|
'@codemirror/autocomplete': 6.6.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)(@lezer/common@1.0.2)
|
||||||
'@codemirror/commands': 6.2.0
|
'@codemirror/commands': 6.2.4
|
||||||
'@codemirror/language': 6.6.0
|
'@codemirror/language': 6.6.0
|
||||||
'@codemirror/lint': 6.1.0
|
'@codemirror/lint': 6.1.0
|
||||||
'@codemirror/search': 6.2.3
|
'@codemirror/search': 6.2.3
|
||||||
@ -4380,11 +4474,11 @@ packages:
|
|||||||
react-dom: '>=16.8.0'
|
react-dom: '>=16.8.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.20.13
|
'@babel/runtime': 7.20.13
|
||||||
'@codemirror/commands': 6.2.0
|
'@codemirror/commands': 6.2.4
|
||||||
'@codemirror/state': 6.2.0
|
'@codemirror/state': 6.2.0
|
||||||
'@codemirror/theme-one-dark': 6.1.0
|
'@codemirror/theme-one-dark': 6.1.0
|
||||||
'@codemirror/view': 6.10.0
|
'@codemirror/view': 6.10.0
|
||||||
'@uiw/codemirror-extensions-basic-setup': 4.19.16(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.0)(@codemirror/language@6.6.0)(@codemirror/lint@6.1.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
'@uiw/codemirror-extensions-basic-setup': 4.19.16(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/lint@6.1.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
||||||
codemirror: 6.0.1(@lezer/common@1.0.2)
|
codemirror: 6.0.1(@lezer/common@1.0.2)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
@ -4415,7 +4509,7 @@ packages:
|
|||||||
'@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.21.5)
|
'@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)
|
'@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.5)
|
||||||
react-refresh: 0.14.0
|
react-refresh: 0.14.0
|
||||||
vite: 4.3.3(@types/node@18.16.3)
|
vite: 4.3.3(@types/node@18.11.18)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
@ -5338,6 +5432,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==}
|
resolution: {integrity: sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
/claviature@0.1.0:
|
||||||
|
resolution: {integrity: sha512-Ai12axNwQ7x/F9QAj64RYKsgvi5Y33+X3GUSKAC/9s/adEws8TSSc0efeiqhKNGKBo6rT/c+CSCwSXzXxwxZzQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/clean-stack@2.2.0:
|
/clean-stack@2.2.0:
|
||||||
resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
|
resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -5458,7 +5556,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==}
|
resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@codemirror/autocomplete': 6.6.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)(@lezer/common@1.0.2)
|
'@codemirror/autocomplete': 6.6.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)(@lezer/common@1.0.2)
|
||||||
'@codemirror/commands': 6.2.0
|
'@codemirror/commands': 6.2.4
|
||||||
'@codemirror/language': 6.6.0
|
'@codemirror/language': 6.6.0
|
||||||
'@codemirror/lint': 6.1.0
|
'@codemirror/lint': 6.1.0
|
||||||
'@codemirror/search': 6.2.3
|
'@codemirror/search': 6.2.3
|
||||||
|
|||||||
@ -3,4 +3,6 @@ packages:
|
|||||||
- "packages/*"
|
- "packages/*"
|
||||||
- "website/"
|
- "website/"
|
||||||
- "packages/core/examples/vite-vanilla-repl"
|
- "packages/core/examples/vite-vanilla-repl"
|
||||||
- "packages/react/examples/nano-repl"
|
- "packages/core/examples/vite-vanilla-repl-cm6"
|
||||||
|
- "packages/react/examples/nano-repl"
|
||||||
|
- "packages/web/examples/repl-example"
|
||||||
|
|||||||
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",
|
"version": "0.6.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev --host 0.0.0.0",
|
||||||
"start": "astro dev",
|
"start": "astro dev",
|
||||||
"check": "astro check && tsc",
|
"check": "astro check && tsc",
|
||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview --port 3009 --host 0.0.0.0",
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@algolia/client-search": "^4.17.0",
|
"@algolia/client-search": "^4.17.0",
|
||||||
"@astrojs/mdx": "^0.19.0",
|
"@astrojs/mdx": "^0.19.0",
|
||||||
"@astrojs/react": "^2.1.1",
|
"@astrojs/react": "^2.1.1",
|
||||||
"@astrojs/tailwind": "^3.1.1",
|
"@astrojs/tailwind": "^3.1.1",
|
||||||
"@docsearch/css": "^3.3.4",
|
"@docsearch/css": "^3.3.4",
|
||||||
@ -43,6 +43,7 @@
|
|||||||
"@uiw/codemirror-themes-all": "^4.19.16",
|
"@uiw/codemirror-themes-all": "^4.19.16",
|
||||||
"astro": "^2.3.2",
|
"astro": "^2.3.2",
|
||||||
"canvas": "^2.11.2",
|
"canvas": "^2.11.2",
|
||||||
|
"claviature": "^0.1.0",
|
||||||
"fraction.js": "^4.2.0",
|
"fraction.js": "^4.2.0",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^4.0.2",
|
||||||
"nanostores": "^0.8.1",
|
"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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -33,7 +33,7 @@ const base = BASE_URL;
|
|||||||
<style is:global>
|
<style is:global>
|
||||||
:root {
|
:root {
|
||||||
--background: #222;
|
--background: #222;
|
||||||
--lineBackground: #22222250;
|
--lineBackground: #22222299;
|
||||||
--foreground: #fff;
|
--foreground: #fff;
|
||||||
--caret: #ffcc00;
|
--caret: #ffcc00;
|
||||||
--selection: rgba(128, 203, 196, 0.5);
|
--selection: rgba(128, 203, 196, 0.5);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
// import { getLanguageFromURL } from '../../languages';
|
import { getLanguageFromURL } from '../../languages';
|
||||||
import { SIDEBAR } from '../../config';
|
import { SIDEBAR } from '../../config';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -10,7 +10,7 @@ const { currentPage } = Astro.props as Props;
|
|||||||
const { BASE_URL } = import.meta.env;
|
const { BASE_URL } = import.meta.env;
|
||||||
let currentPageMatch = currentPage.slice(BASE_URL.length, currentPage.endsWith('/') ? -1 : undefined);
|
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];
|
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 = {
|
export const KNOWN_LANGUAGES = {
|
||||||
English: 'en',
|
English: 'en',
|
||||||
|
German: 'de',
|
||||||
} as const;
|
} as const;
|
||||||
export const KNOWN_LANGUAGE_CODES = Object.values(KNOWN_LANGUAGES);
|
export const KNOWN_LANGUAGE_CODES = Object.values(KNOWN_LANGUAGES);
|
||||||
|
|
||||||
@ -38,25 +39,49 @@ export const ALGOLIA = {
|
|||||||
apiKey: 'd5044f9d21b80e7721e5b0067a8730b1',
|
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 = {
|
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: {
|
en: {
|
||||||
Tutorial: [
|
Workshop: [
|
||||||
{ text: 'Getting Started', link: 'learn/getting-started' },
|
{ text: 'Getting Started', link: 'workshop/getting-started' },
|
||||||
{ text: 'Notes', link: 'learn/notes' },
|
{ text: 'First Sounds', link: 'workshop/first-sounds' },
|
||||||
{ text: 'Sounds', link: 'learn/sounds' },
|
{ text: 'First Notes', link: 'workshop/first-notes' },
|
||||||
{ text: 'Coding syntax', link: 'learn/code' },
|
{ text: 'First Effects', link: 'workshop/first-effects' },
|
||||||
{ text: 'Mini-Notation', link: 'learn/mini-notation' },
|
{ text: 'Pattern Effects', link: 'workshop/pattern-effects' },
|
||||||
|
{ text: 'Recap', link: 'workshop/recap' },
|
||||||
|
{ text: 'Workshop in German', link: 'de/workshop/getting-started' },
|
||||||
],
|
],
|
||||||
'Making Sound': [
|
'Making Sound': [
|
||||||
{ text: 'Samples', link: 'learn/samples' },
|
{ text: 'Samples', link: 'learn/samples' },
|
||||||
{ text: 'Synths', link: 'learn/synths' },
|
{ text: 'Synths', link: 'learn/synths' },
|
||||||
{ text: 'Audio Effects', link: 'learn/effects' },
|
{ 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' },
|
{ text: 'CSound', link: 'learn/csound' },
|
||||||
],
|
],
|
||||||
'Pattern Functions': [
|
'Pattern Functions': [
|
||||||
{ text: 'Introduction', link: 'functions/intro' },
|
{ 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: 'Time Modifiers', link: 'learn/time-modifiers' },
|
||||||
{ text: 'Control Parameters', link: 'functions/value-modifiers' },
|
{ text: 'Control Parameters', link: 'functions/value-modifiers' },
|
||||||
{ text: 'Signals', link: 'learn/signals' },
|
{ text: 'Signals', link: 'learn/signals' },
|
||||||
@ -64,13 +89,6 @@ export const SIDEBAR: Sidebar = {
|
|||||||
{ text: 'Accumulation', link: 'learn/accumulation' },
|
{ text: 'Accumulation', link: 'learn/accumulation' },
|
||||||
{ text: 'Tonal Modifiers', link: 'learn/tonal' },
|
{ 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: [
|
Development: [
|
||||||
{ text: 'REPL', link: 'technical-manual/repl' },
|
{ text: 'REPL', link: 'technical-manual/repl' },
|
||||||
{ text: 'Sounds', link: 'technical-manual/sounds' },
|
{ text: 'Sounds', link: 'technical-manual/sounds' },
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
.cm-activeLine {
|
.cm-activeLine,
|
||||||
|
.cm-activeLineGutter {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7,3 +8,11 @@
|
|||||||
border: 1px solid var(--lineHighlight);
|
border: 1px solid var(--lineHighlight);
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cm-scroller {
|
||||||
|
font-family: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-gutters {
|
||||||
|
display: none !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 { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { prebake } from '../repl/prebake';
|
import { prebake } from '../repl/prebake';
|
||||||
import { themes, settings } from '../repl/themes.mjs';
|
import { themes, settings } from '../repl/themes.mjs';
|
||||||
import './MiniRepl.css';
|
import './MiniRepl.css';
|
||||||
import { useSettings } from '../settings.mjs';
|
import { useSettings } from '../settings.mjs';
|
||||||
|
import Claviature from '@components/Claviature';
|
||||||
|
|
||||||
let modules;
|
let modules;
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@ -27,9 +28,20 @@ if (typeof window !== 'undefined') {
|
|||||||
prebake();
|
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 [Repl, setRepl] = useState();
|
||||||
const { theme } = useSettings();
|
const { theme, keybindings, fontSize, fontFamily, isLineNumbersDisplayed } = useSettings();
|
||||||
|
const [activeNotes, setActiveNotes] = useState([]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// we have to load this package on the client
|
// we have to load this package on the client
|
||||||
// because codemirror throws an error on the server
|
// because codemirror throws an error on the server
|
||||||
@ -42,11 +54,39 @@ export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) {
|
|||||||
<Repl
|
<Repl
|
||||||
tune={tune}
|
tune={tune}
|
||||||
hideOutsideView={true}
|
hideOutsideView={true}
|
||||||
drawTime={drawTime}
|
drawTime={claviature ? [0, 0] : drawTime}
|
||||||
punchcard={punchcard}
|
punchcard={punchcard}
|
||||||
|
punchcardLabels={punchcardLabels}
|
||||||
|
span={span}
|
||||||
canvasHeight={canvasHeight}
|
canvasHeight={canvasHeight}
|
||||||
theme={themes[theme]}
|
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>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<pre>{tune}</pre>
|
<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 * as tunes from '../../../src/repl/tunes.mjs';
|
||||||
import HeadCommon from '../../components/HeadCommon.astro';
|
import HeadCommon from '../../components/HeadCommon.astro';
|
||||||
|
|
||||||
|
import { getMetadata } from '../metadata_parser';
|
||||||
---
|
---
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
@ -12,7 +14,7 @@ import HeadCommon from '../../components/HeadCommon.astro';
|
|||||||
Object.entries(tunes).map(([name, tune]) => (
|
Object.entries(tunes).map(([name, tune]) => (
|
||||||
<a class="rounded-md bg-slate-900 hover:bg-slate-700 cursor-pointer relative" href={`./#${btoa(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">
|
<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>
|
</div>
|
||||||
<img src={`./img/example-${name}.png`} />
|
<img src={`./img/example-${name}.png`} />
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -6,23 +6,24 @@ layout: ../../layouts/MainLayout.astro
|
|||||||
import { MiniRepl } from '../../docs/MiniRepl';
|
import { MiniRepl } from '../../docs/MiniRepl';
|
||||||
import { JsDoc } from '../../docs/JsDoc';
|
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.
|
Let's learn all about functions to create and modify patterns.
|
||||||
Internally, the mini notation will expand to use the actual functional JavaScript API.
|
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")`} />
|
<MiniRepl client:only="react" tune={`note("c3 eb3 g3")`} />
|
||||||
|
|
||||||
is equivalent to this Pattern without Mini Notation:
|
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.
|
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
|
Which representation to use is a matter of context. As a rule of thumb, functions
|
||||||
to fit better for the larger context, while mini notation is more practical for individiual rhythms.
|
are better suited in a larger context, while mini notation is more practical for individiual rhythms.
|
||||||
|
|
||||||
## Limits of Mini Notation
|
## Limits of Mini Notation
|
||||||
|
|
||||||
@ -46,10 +47,10 @@ You can freely mix JS patterns, mini patterns and values! For example, this patt
|
|||||||
<MiniRepl
|
<MiniRepl
|
||||||
client:idle
|
client:idle
|
||||||
tune={`cat(
|
tune={`cat(
|
||||||
stack(g3,b3,e4),
|
stack("g3","b3","e4"),
|
||||||
stack(a3,c3,e4),
|
stack("a3","c3","e4"),
|
||||||
stack(b3,d3,fs4),
|
stack("b3","d3","fs4"),
|
||||||
stack(b3,e4,g4)
|
stack("b3","e4","g4")
|
||||||
).note()`}
|
).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: \* / ! @.
|
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.
|
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)
|
||||||
|
|||||||
@ -6,31 +6,31 @@ layout: ../../layouts/MainLayout.astro
|
|||||||
import { MiniRepl } from '../../docs/MiniRepl';
|
import { MiniRepl } from '../../docs/MiniRepl';
|
||||||
import { JsDoc } from '../../docs/JsDoc';
|
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).
|
- 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("sine")`.
|
- 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 `freq` is purple, the brackets `()` are grey, and the content inside the `""` are green.
|
- 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?
|
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...
|
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?
|
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.
|
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 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
|
# 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)>).
|
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.
|
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.
|
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.
|
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!
|
The good news is, that this covers most of the JavaScript syntax needed for Strudel!
|
||||||
|
|
||||||
Let's now look at the way we can express [Rhythms](/learn/mini-notation)...
|
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
title: Pattern Constructors
|
title: Creating Patterns
|
||||||
layout: ../../layouts/MainLayout.astro
|
layout: ../../layouts/MainLayout.astro
|
||||||
---
|
---
|
||||||
|
|
||||||
import { MiniRepl } from '../../docs/MiniRepl';
|
import { MiniRepl } from '../../docs/MiniRepl';
|
||||||
import { JsDoc } from '../../docs/JsDoc';
|
import { JsDoc } from '../../docs/JsDoc';
|
||||||
|
|
||||||
# Pattern Constructors
|
# Creating Patterns
|
||||||
|
|
||||||
The following functions will return a pattern.
|
The following functions will return a pattern.
|
||||||
These are the equivalents used by the Mini Notation:
|
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
|
# 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.
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
<MiniRepl
|
||||||
client:idle
|
client:idle
|
||||||
|
|||||||
@ -24,7 +24,7 @@ Instead of numbers, scientific interval notation can be used as well:
|
|||||||
|
|
||||||
### scale(name)
|
### 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
|
<MiniRepl
|
||||||
client:idle
|
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() {
|
export function getMyPatterns() {
|
||||||
const my = import.meta.glob('../../../../my-patterns/**', { as: 'raw', eager: true });
|
const my = import.meta.glob('../../../../my-patterns/**', { as: 'raw', eager: true });
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.entries(my) //
|
Object.entries(my)
|
||||||
.filter(([name]) => name.endsWith('.txt')) //
|
.filter(([name]) => name.endsWith('.txt'))
|
||||||
.map(([name, raw]) => [name.split('/').slice(-1), raw]), //
|
.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 }) {
|
function ButtonGroup({ value, onChange, items }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex max-w-lg">
|
<div className="flex max-w-lg">
|
||||||
@ -355,7 +364,7 @@ const fontFamilyOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function SettingsTab({ scheduler }) {
|
function SettingsTab({ scheduler }) {
|
||||||
const { theme, keybindings, fontSize, fontFamily } = useSettings();
|
const { theme, keybindings, isLineNumbersDisplayed, fontSize, fontFamily } = useSettings();
|
||||||
return (
|
return (
|
||||||
<div className="text-foreground p-4 space-y-4">
|
<div className="text-foreground p-4 space-y-4">
|
||||||
{/* <FormItem label="Tempo">
|
{/* <FormItem label="Tempo">
|
||||||
@ -397,13 +406,20 @@ function SettingsTab({ scheduler }) {
|
|||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</div>
|
</div>
|
||||||
<FormItem label="Keybindings">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<ButtonGroup
|
<FormItem label="Keybindings">
|
||||||
value={keybindings}
|
<ButtonGroup
|
||||||
onChange={(keybindings) => settingsMap.setKey('keybindings', keybindings)}
|
value={keybindings}
|
||||||
items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs' }}
|
onChange={(keybindings) => settingsMap.setKey('keybindings', keybindings)}
|
||||||
></ButtonGroup>
|
items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs' }}
|
||||||
</FormItem>
|
></ButtonGroup>
|
||||||
|
</FormItem>
|
||||||
|
<Checkbox
|
||||||
|
label="Display line numbers"
|
||||||
|
onChange={(cbEvent) => settingsMap.setKey('isLineNumbersDisplayed', cbEvent.target.checked)}
|
||||||
|
value={isLineNumbersDisplayed}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<FormItem label="Reset Settings">
|
<FormItem label="Reset Settings">
|
||||||
<button
|
<button
|
||||||
className="bg-background p-2 max-w-[300px] rounded-md hover:opacity-50"
|
className="bg-background p-2 max-w-[300px] rounded-md hover:opacity-50"
|
||||||
|
|||||||
@ -122,7 +122,7 @@ export function Header({ context }) {
|
|||||||
{!isEmbedded && (
|
{!isEmbedded && (
|
||||||
<a
|
<a
|
||||||
title="learn"
|
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')}
|
className={cx('hover:opacity-50 flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
|
||||||
>
|
>
|
||||||
<AcademicCapIcon className="w-6 h-6" />
|
<AcademicCapIcon className="w-6 h-6" />
|
||||||
|
|||||||
@ -1,3 +1,15 @@
|
|||||||
|
:root {
|
||||||
|
--background: #222;
|
||||||
|
--lineBackground: #22222299;
|
||||||
|
--foreground: #fff;
|
||||||
|
--caret: #ffcc00;
|
||||||
|
--selection: rgba(128, 203, 196, 0.5);
|
||||||
|
--selectionMatch: #036dd626;
|
||||||
|
--lineHighlight: #00000050;
|
||||||
|
--gutterBackground: transparent;
|
||||||
|
--gutterForeground: #8a919966;
|
||||||
|
}
|
||||||
|
|
||||||
.darken::before {
|
.darken::before {
|
||||||
content: ' ';
|
content: ' ';
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -10,15 +22,26 @@
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
#code .cm-content {
|
|
||||||
padding-top: 12px !important;
|
|
||||||
padding-left: 8px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#code .cm-scroller {
|
#code .cm-scroller {
|
||||||
|
padding-top: 10px !important;
|
||||||
padding-bottom: 50vh;
|
padding-bottom: 50vh;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
#code .cm-line > * {
|
#code .cm-line > * {
|
||||||
background: var(--lineBackground);
|
background: var(--lineBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#code .cm-editor {
|
||||||
|
background-color: transparent !important;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
#code .cm-theme {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#code .cm-theme-light {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|||||||
@ -105,7 +105,7 @@ export function Repl({ embedded = false }) {
|
|||||||
const [lastShared, setLastShared] = useState();
|
const [lastShared, setLastShared] = useState();
|
||||||
const [pending, setPending] = useState(true);
|
const [pending, setPending] = useState(true);
|
||||||
|
|
||||||
const { theme, keybindings, fontSize, fontFamily } = useSettings();
|
const { theme, keybindings, fontSize, fontFamily, isLineNumbersDisplayed } = useSettings();
|
||||||
|
|
||||||
const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } =
|
const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } =
|
||||||
useStrudel({
|
useStrudel({
|
||||||
@ -157,7 +157,7 @@ export function Repl({ embedded = false }) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
flash(view);
|
flash(view);
|
||||||
await activateCode();
|
await activateCode();
|
||||||
} else if (e.key === '.') {
|
} else if (e.key === '.' || e.code === 'Period') {
|
||||||
stop();
|
stop();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
@ -271,6 +271,7 @@ export function Repl({ embedded = false }) {
|
|||||||
theme={themes[theme] || themes.strudelTheme}
|
theme={themes[theme] || themes.strudelTheme}
|
||||||
value={code}
|
value={code}
|
||||||
keybindings={keybindings}
|
keybindings={keybindings}
|
||||||
|
isLineNumbersDisplayed={isLineNumbersDisplayed}
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
fontFamily={fontFamily}
|
fontFamily={fontFamily}
|
||||||
onChange={handleChangeCode}
|
onChange={handleChangeCode}
|
||||||
|
|||||||
@ -22,8 +22,96 @@ export async function prebake() {
|
|||||||
tag: 'drum-machines',
|
tag: 'drum-machines',
|
||||||
}),
|
}),
|
||||||
samples(`./EmuSP12.json`, `./EmuSP12/`, { prebake: true, 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');
|
const maxPan = noteToMidi('C8');
|
||||||
|
|||||||
@ -80,7 +80,7 @@ export const themes = {
|
|||||||
export const settings = {
|
export const settings = {
|
||||||
strudelTheme: {
|
strudelTheme: {
|
||||||
background: '#222',
|
background: '#222',
|
||||||
lineBackground: '#22222250',
|
lineBackground: '#22222299',
|
||||||
foreground: '#fff',
|
foreground: '#fff',
|
||||||
// foreground: '#75baff',
|
// foreground: '#75baff',
|
||||||
caret: '#ffcc00',
|
caret: '#ffcc00',
|
||||||
@ -99,7 +99,7 @@ export const settings = {
|
|||||||
terminal: terminalSettings,
|
terminal: terminalSettings,
|
||||||
abcdef: {
|
abcdef: {
|
||||||
background: '#0f0f0f',
|
background: '#0f0f0f',
|
||||||
lineBackground: '#0f0f0f50',
|
lineBackground: '#0f0f0f99',
|
||||||
foreground: '#defdef',
|
foreground: '#defdef',
|
||||||
caret: '#00FF00',
|
caret: '#00FF00',
|
||||||
selection: '#515151',
|
selection: '#515151',
|
||||||
@ -110,7 +110,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
androidstudio: {
|
androidstudio: {
|
||||||
background: '#282b2e',
|
background: '#282b2e',
|
||||||
lineBackground: '#282b2e50',
|
lineBackground: '#282b2e99',
|
||||||
foreground: '#a9b7c6',
|
foreground: '#a9b7c6',
|
||||||
caret: '#00FF00',
|
caret: '#00FF00',
|
||||||
selection: '#343739',
|
selection: '#343739',
|
||||||
@ -119,7 +119,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
atomone: {
|
atomone: {
|
||||||
background: '#272C35',
|
background: '#272C35',
|
||||||
lineBackground: '#272C3550',
|
lineBackground: '#272C3599',
|
||||||
foreground: '#9d9b97',
|
foreground: '#9d9b97',
|
||||||
caret: '#797977',
|
caret: '#797977',
|
||||||
selection: '#ffffff30',
|
selection: '#ffffff30',
|
||||||
@ -131,7 +131,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
aura: {
|
aura: {
|
||||||
background: '#21202e',
|
background: '#21202e',
|
||||||
lineBackground: '#21202e50',
|
lineBackground: '#21202e99',
|
||||||
foreground: '#edecee',
|
foreground: '#edecee',
|
||||||
caret: '#a277ff',
|
caret: '#a277ff',
|
||||||
selection: '#3d375e7f',
|
selection: '#3d375e7f',
|
||||||
@ -144,7 +144,7 @@ export const settings = {
|
|||||||
bbedit: {
|
bbedit: {
|
||||||
light: true,
|
light: true,
|
||||||
background: '#FFFFFF',
|
background: '#FFFFFF',
|
||||||
lineBackground: '#FFFFFF50',
|
lineBackground: '#FFFFFF99',
|
||||||
foreground: '#000000',
|
foreground: '#000000',
|
||||||
caret: '#FBAC52',
|
caret: '#FBAC52',
|
||||||
selection: '#FFD420',
|
selection: '#FFD420',
|
||||||
@ -156,7 +156,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
bespin: {
|
bespin: {
|
||||||
background: '#28211c',
|
background: '#28211c',
|
||||||
lineBackground: '#28211c50',
|
lineBackground: '#28211c99',
|
||||||
foreground: '#9d9b97',
|
foreground: '#9d9b97',
|
||||||
caret: '#797977',
|
caret: '#797977',
|
||||||
selection: '#36312e',
|
selection: '#36312e',
|
||||||
@ -167,7 +167,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
darcula: {
|
darcula: {
|
||||||
background: '#2B2B2B',
|
background: '#2B2B2B',
|
||||||
lineBackground: '#2B2B2B50',
|
lineBackground: '#2B2B2B99',
|
||||||
foreground: '#f8f8f2',
|
foreground: '#f8f8f2',
|
||||||
caret: '#FFFFFF',
|
caret: '#FFFFFF',
|
||||||
selection: 'rgba(255, 255, 255, 0.1)',
|
selection: 'rgba(255, 255, 255, 0.1)',
|
||||||
@ -179,7 +179,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
dracula: {
|
dracula: {
|
||||||
background: '#282a36',
|
background: '#282a36',
|
||||||
lineBackground: '#282a3650',
|
lineBackground: '#282a3699',
|
||||||
foreground: '#f8f8f2',
|
foreground: '#f8f8f2',
|
||||||
caret: '#f8f8f0',
|
caret: '#f8f8f0',
|
||||||
selection: 'rgba(255, 255, 255, 0.1)',
|
selection: 'rgba(255, 255, 255, 0.1)',
|
||||||
@ -192,7 +192,7 @@ export const settings = {
|
|||||||
duotoneLight: {
|
duotoneLight: {
|
||||||
light: true,
|
light: true,
|
||||||
background: '#faf8f5',
|
background: '#faf8f5',
|
||||||
lineBackground: '#faf8f550',
|
lineBackground: '#faf8f599',
|
||||||
foreground: '#b29762',
|
foreground: '#b29762',
|
||||||
caret: '#93abdc',
|
caret: '#93abdc',
|
||||||
selection: '#e3dcce',
|
selection: '#e3dcce',
|
||||||
@ -204,7 +204,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
duotoneDark: {
|
duotoneDark: {
|
||||||
background: '#2a2734',
|
background: '#2a2734',
|
||||||
lineBackground: '#2a273450',
|
lineBackground: '#2a273499',
|
||||||
foreground: '#6c6783',
|
foreground: '#6c6783',
|
||||||
caret: '#ffad5c',
|
caret: '#ffad5c',
|
||||||
selection: 'rgba(255, 255, 255, 0.1)',
|
selection: 'rgba(255, 255, 255, 0.1)',
|
||||||
@ -215,7 +215,7 @@ export const settings = {
|
|||||||
eclipse: {
|
eclipse: {
|
||||||
light: true,
|
light: true,
|
||||||
background: '#fff',
|
background: '#fff',
|
||||||
lineBackground: '#ffffff50',
|
lineBackground: '#ffffff99',
|
||||||
foreground: '#000',
|
foreground: '#000',
|
||||||
caret: '#FFFFFF',
|
caret: '#FFFFFF',
|
||||||
selection: '#d7d4f0',
|
selection: '#d7d4f0',
|
||||||
@ -228,7 +228,7 @@ export const settings = {
|
|||||||
githubLight: {
|
githubLight: {
|
||||||
light: true,
|
light: true,
|
||||||
background: '#fff',
|
background: '#fff',
|
||||||
lineBackground: '#ffffff50',
|
lineBackground: '#ffffff99',
|
||||||
foreground: '#24292e',
|
foreground: '#24292e',
|
||||||
selection: '#BBDFFF',
|
selection: '#BBDFFF',
|
||||||
selectionMatch: '#BBDFFF',
|
selectionMatch: '#BBDFFF',
|
||||||
@ -237,7 +237,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
githubDark: {
|
githubDark: {
|
||||||
background: '#0d1117',
|
background: '#0d1117',
|
||||||
lineBackground: '#0d111750',
|
lineBackground: '#0d111799',
|
||||||
foreground: '#c9d1d9',
|
foreground: '#c9d1d9',
|
||||||
caret: '#c9d1d9',
|
caret: '#c9d1d9',
|
||||||
selection: '#003d73',
|
selection: '#003d73',
|
||||||
@ -246,7 +246,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
gruvboxDark: {
|
gruvboxDark: {
|
||||||
background: '#282828',
|
background: '#282828',
|
||||||
lineBackground: '#28282850',
|
lineBackground: '#28282899',
|
||||||
foreground: '#ebdbb2',
|
foreground: '#ebdbb2',
|
||||||
caret: '#ebdbb2',
|
caret: '#ebdbb2',
|
||||||
selection: '#bdae93',
|
selection: '#bdae93',
|
||||||
@ -258,7 +258,7 @@ export const settings = {
|
|||||||
gruvboxLight: {
|
gruvboxLight: {
|
||||||
light: true,
|
light: true,
|
||||||
background: '#fbf1c7',
|
background: '#fbf1c7',
|
||||||
lineBackground: '#fbf1c750',
|
lineBackground: '#fbf1c799',
|
||||||
foreground: '#3c3836',
|
foreground: '#3c3836',
|
||||||
caret: '#af3a03',
|
caret: '#af3a03',
|
||||||
selection: '#ebdbb2',
|
selection: '#ebdbb2',
|
||||||
@ -270,7 +270,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
materialDark: {
|
materialDark: {
|
||||||
background: '#2e3235',
|
background: '#2e3235',
|
||||||
lineBackground: '#2e323550',
|
lineBackground: '#2e323599',
|
||||||
foreground: '#bdbdbd',
|
foreground: '#bdbdbd',
|
||||||
caret: '#a0a4ae',
|
caret: '#a0a4ae',
|
||||||
selection: '#d7d4f0',
|
selection: '#d7d4f0',
|
||||||
@ -283,7 +283,7 @@ export const settings = {
|
|||||||
materialLight: {
|
materialLight: {
|
||||||
light: true,
|
light: true,
|
||||||
background: '#FAFAFA',
|
background: '#FAFAFA',
|
||||||
lineBackground: '#FAFAFA50',
|
lineBackground: '#FAFAFA99',
|
||||||
foreground: '#90A4AE',
|
foreground: '#90A4AE',
|
||||||
caret: '#272727',
|
caret: '#272727',
|
||||||
selection: '#80CBC440',
|
selection: '#80CBC440',
|
||||||
@ -296,7 +296,7 @@ export const settings = {
|
|||||||
noctisLilac: {
|
noctisLilac: {
|
||||||
light: true,
|
light: true,
|
||||||
background: '#f2f1f8',
|
background: '#f2f1f8',
|
||||||
lineBackground: '#f2f1f850',
|
lineBackground: '#f2f1f899',
|
||||||
foreground: '#0c006b',
|
foreground: '#0c006b',
|
||||||
caret: '#5c49e9',
|
caret: '#5c49e9',
|
||||||
selection: '#d5d1f2',
|
selection: '#d5d1f2',
|
||||||
@ -307,7 +307,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
nord: {
|
nord: {
|
||||||
background: '#2e3440',
|
background: '#2e3440',
|
||||||
lineBackground: '#2e344050',
|
lineBackground: '#2e344099',
|
||||||
foreground: '#FFFFFF',
|
foreground: '#FFFFFF',
|
||||||
caret: '#FFFFFF',
|
caret: '#FFFFFF',
|
||||||
selection: '#3b4252',
|
selection: '#3b4252',
|
||||||
@ -319,7 +319,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
okaidia: {
|
okaidia: {
|
||||||
background: '#272822',
|
background: '#272822',
|
||||||
lineBackground: '#27282250',
|
lineBackground: '#27282299',
|
||||||
foreground: '#FFFFFF',
|
foreground: '#FFFFFF',
|
||||||
caret: '#FFFFFF',
|
caret: '#FFFFFF',
|
||||||
selection: '#49483E',
|
selection: '#49483E',
|
||||||
@ -331,7 +331,7 @@ export const settings = {
|
|||||||
solarizedLight: {
|
solarizedLight: {
|
||||||
light: true,
|
light: true,
|
||||||
background: '#fdf6e3',
|
background: '#fdf6e3',
|
||||||
lineBackground: '#fdf6e350',
|
lineBackground: '#fdf6e399',
|
||||||
foreground: '#657b83',
|
foreground: '#657b83',
|
||||||
caret: '#586e75',
|
caret: '#586e75',
|
||||||
selection: '#dfd9c8',
|
selection: '#dfd9c8',
|
||||||
@ -342,7 +342,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
solarizedDark: {
|
solarizedDark: {
|
||||||
background: '#002b36',
|
background: '#002b36',
|
||||||
lineBackground: '#002b3650',
|
lineBackground: '#002b3699',
|
||||||
foreground: '#93a1a1',
|
foreground: '#93a1a1',
|
||||||
caret: '#839496',
|
caret: '#839496',
|
||||||
selection: '#173541',
|
selection: '#173541',
|
||||||
@ -353,7 +353,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
sublime: {
|
sublime: {
|
||||||
background: '#303841',
|
background: '#303841',
|
||||||
lineBackground: '#30384150',
|
lineBackground: '#30384199',
|
||||||
foreground: '#FFFFFF',
|
foreground: '#FFFFFF',
|
||||||
caret: '#FBAC52',
|
caret: '#FBAC52',
|
||||||
selection: '#4C5964',
|
selection: '#4C5964',
|
||||||
@ -365,7 +365,7 @@ export const settings = {
|
|||||||
tokyoNightDay: {
|
tokyoNightDay: {
|
||||||
light: true,
|
light: true,
|
||||||
background: '#e1e2e7',
|
background: '#e1e2e7',
|
||||||
lineBackground: '#e1e2e750',
|
lineBackground: '#e1e2e799',
|
||||||
foreground: '#3760bf',
|
foreground: '#3760bf',
|
||||||
caret: '#3760bf',
|
caret: '#3760bf',
|
||||||
selection: '#99a7df',
|
selection: '#99a7df',
|
||||||
@ -377,7 +377,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
tokyoNightStorm: {
|
tokyoNightStorm: {
|
||||||
background: '#24283b',
|
background: '#24283b',
|
||||||
lineBackground: '#24283b50',
|
lineBackground: '#24283b99',
|
||||||
foreground: '#7982a9',
|
foreground: '#7982a9',
|
||||||
caret: '#c0caf5',
|
caret: '#c0caf5',
|
||||||
selection: '#6f7bb630',
|
selection: '#6f7bb630',
|
||||||
@ -389,7 +389,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
tokyoNight: {
|
tokyoNight: {
|
||||||
background: '#1a1b26',
|
background: '#1a1b26',
|
||||||
lineBackground: '#1a1b2650',
|
lineBackground: '#1a1b2699',
|
||||||
foreground: '#787c99',
|
foreground: '#787c99',
|
||||||
caret: '#c0caf5',
|
caret: '#c0caf5',
|
||||||
selection: '#515c7e40',
|
selection: '#515c7e40',
|
||||||
@ -401,7 +401,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
vscodeDark: {
|
vscodeDark: {
|
||||||
background: '#1e1e1e',
|
background: '#1e1e1e',
|
||||||
lineBackground: '#1e1e1e50',
|
lineBackground: '#1e1e1e99',
|
||||||
foreground: '#9cdcfe',
|
foreground: '#9cdcfe',
|
||||||
caret: '#c6c6c6',
|
caret: '#c6c6c6',
|
||||||
selection: '#6199ff2f',
|
selection: '#6199ff2f',
|
||||||
@ -414,7 +414,7 @@ export const settings = {
|
|||||||
xcodeLight: {
|
xcodeLight: {
|
||||||
light: true,
|
light: true,
|
||||||
background: '#fff',
|
background: '#fff',
|
||||||
lineBackground: '#ffffff50',
|
lineBackground: '#ffffff99',
|
||||||
foreground: '#3D3D3D',
|
foreground: '#3D3D3D',
|
||||||
selection: '#BBDFFF',
|
selection: '#BBDFFF',
|
||||||
selectionMatch: '#BBDFFF',
|
selectionMatch: '#BBDFFF',
|
||||||
@ -424,7 +424,7 @@ export const settings = {
|
|||||||
},
|
},
|
||||||
xcodeDark: {
|
xcodeDark: {
|
||||||
background: '#292A30',
|
background: '#292A30',
|
||||||
lineBackground: '#292A3050',
|
lineBackground: '#292A3099',
|
||||||
foreground: '#CECFD0',
|
foreground: '#CECFD0',
|
||||||
caret: '#fff',
|
caret: '#fff',
|
||||||
selection: '#727377',
|
selection: '#727377',
|
||||||
|
|||||||
@ -121,8 +121,10 @@ stack(
|
|||||||
.room(1)
|
.room(1)
|
||||||
//.pianoroll({fold:1})`;
|
//.pianoroll({fold:1})`;
|
||||||
|
|
||||||
export const caverave = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const caverave = `// "Caverave"
|
||||||
// by Felix Roos
|
// @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)
|
const keys = x => x.s('sawtooth').cutoff(1200).gain(.5)
|
||||||
.attack(0).decay(.16).sustain(.3).release(.1);
|
.attack(0).decay(.16).sustain(.3).release(.1);
|
||||||
|
|
||||||
@ -184,8 +186,10 @@ export const barryHarris = `// adapted from a Barry Harris excercise
|
|||||||
.color('#00B8D4')
|
.color('#00B8D4')
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const blippyRhodes = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const blippyRhodes = `// "Blippy Rhodes"
|
||||||
// by Felix Roos
|
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
// @by Felix Roos
|
||||||
|
|
||||||
samples({
|
samples({
|
||||||
bd: 'samples/tidal/bd/BT0A0D0.wav',
|
bd: 'samples/tidal/bd/BT0A0D0.wav',
|
||||||
sn: 'samples/tidal/sn/ST0T0S3.wav',
|
sn: 'samples/tidal/sn/ST0T0S3.wav',
|
||||||
@ -226,8 +230,10 @@ stack(
|
|||||||
).fast(3/2)
|
).fast(3/2)
|
||||||
//.pianoroll({fold:1})`;
|
//.pianoroll({fold:1})`;
|
||||||
|
|
||||||
export const wavyKalimba = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const wavyKalimba = `// "Wavy kalimba"
|
||||||
// by Felix Roos
|
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
// @by Felix Roos
|
||||||
|
|
||||||
samples({
|
samples({
|
||||||
'kalimba': { c5:'https://freesound.org/data/previews/536/536549_11935698-lq.mp3' }
|
'kalimba': { c5:'https://freesound.org/data/previews/536/536549_11935698-lq.mp3' }
|
||||||
})
|
})
|
||||||
@ -256,8 +262,10 @@ stack(
|
|||||||
.delay(.2)
|
.delay(.2)
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const festivalOfFingers = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const festivalOfFingers = `// "Festival of fingers"
|
||||||
// by Felix Roos
|
// @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>";
|
const chords = "<Cm7 Fm7 G7 F#7>";
|
||||||
stack(
|
stack(
|
||||||
chords.voicings('lefthand').struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)),
|
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()`;
|
.note().piano()`;
|
||||||
|
|
||||||
// iter, echo, echoWith
|
// iter, echo, echoWith
|
||||||
export const undergroundPlumber = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const undergroundPlumber = `// "Underground plumber"
|
||||||
// by Felix Roos, inspired by Friendship - Let's not talk about it (1979) :)
|
// @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',
|
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/')
|
}, 'https://loophole-letters.vercel.app/samples/tidal/')
|
||||||
@ -297,8 +307,10 @@ stack(
|
|||||||
.fast(2/3)
|
.fast(2/3)
|
||||||
.pianoroll({})`;
|
.pianoroll({})`;
|
||||||
|
|
||||||
export const bridgeIsOver = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const bridgeIsOver = `// "Bridge is over"
|
||||||
// by Felix Roos, bassline by BDP - The 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'})
|
samples({mad:'https://freesound.org/data/previews/22/22274_109943-lq.mp3'})
|
||||||
stack(
|
stack(
|
||||||
stack(
|
stack(
|
||||||
@ -318,8 +330,10 @@ stack(
|
|||||||
.pianoroll()
|
.pianoroll()
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const goodTimes = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const goodTimes = `// "Good times"
|
||||||
// by Felix Roos
|
// @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);
|
const scale = cat('C3 dorian','Bb2 major').slow(4);
|
||||||
stack(
|
stack(
|
||||||
"2*4".add(12).scale(scale)
|
"2*4".add(12).scale(scale)
|
||||||
@ -361,8 +375,10 @@ stack(
|
|||||||
.pianoroll({maxMidi:100,minMidi:20})`;
|
.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/
|
export const echoPiano = `// "Echo piano"
|
||||||
// by Felix Roos
|
// @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>"
|
"<0 2 [4 6](3,4,2) 3*2>"
|
||||||
.scale('D minor')
|
.scale('D minor')
|
||||||
.color('salmon')
|
.color('salmon')
|
||||||
@ -408,8 +424,10 @@ stack(
|
|||||||
.legato(.5)
|
.legato(.5)
|
||||||
).fast(2).note()`;
|
).fast(2).note()`;
|
||||||
|
|
||||||
export const randomBells = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const randomBells = `// "Random bells"
|
||||||
// by Felix Roos
|
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
// @by Felix Roos
|
||||||
|
|
||||||
samples({
|
samples({
|
||||||
bell: { c6: 'https://freesound.org/data/previews/411/411089_5121236-lq.mp3' },
|
bell: { c6: 'https://freesound.org/data/previews/411/411089_5121236-lq.mp3' },
|
||||||
bass: { d2: 'https://freesound.org/data/previews/608/608286_13074022-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'})
|
.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/
|
export const waa2 = `// "Waa2"
|
||||||
// by Felix Roos
|
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
// @by Felix Roos
|
||||||
|
|
||||||
n(
|
n(
|
||||||
"a4 [a3 c3] a3 c3"
|
"a4 [a3 c3] a3 c3"
|
||||||
.sub("<7 12 5 12>".slow(2))
|
.sub("<7 12 5 12>".slow(2))
|
||||||
@ -447,8 +467,10 @@ n(
|
|||||||
.room(.5)
|
.room(.5)
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const hyperpop = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const hyperpop = `// "Hyperpop"
|
||||||
// by Felix Roos
|
// @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 lfo = cosine.slow(15);
|
||||||
const lfo2 = sine.slow(16);
|
const lfo2 = sine.slow(16);
|
||||||
const filter1 = x=>x.cutoff(lfo2.range(300,3000));
|
const filter1 = x=>x.cutoff(lfo2.range(300,3000));
|
||||||
@ -498,8 +520,10 @@ stack(
|
|||||||
).slow(2)
|
).slow(2)
|
||||||
// strudel disable-highlighting`;
|
// strudel disable-highlighting`;
|
||||||
|
|
||||||
export const festivalOfFingers3 = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const festivalOfFingers3 = `// "Festival of fingers 3"
|
||||||
// by Felix Roos
|
// @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]"
|
"[-7*3],0,2,6,[8 7]"
|
||||||
.echoWith(4,1/4, (x,n)=>x
|
.echoWith(4,1/4, (x,n)=>x
|
||||||
.add(n*7)
|
.add(n*7)
|
||||||
@ -516,8 +540,10 @@ export const festivalOfFingers3 = `// licensed with CC BY-NC-SA 4.0 https://crea
|
|||||||
.note().piano()
|
.note().piano()
|
||||||
//.pianoroll({maxMidi:160})`;
|
//.pianoroll({maxMidi:160})`;
|
||||||
|
|
||||||
export const meltingsubmarine = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const meltingsubmarine = `// "Melting submarine"
|
||||||
// by Felix Roos
|
// @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/')
|
await samples('github:tidalcycles/Dirt-Samples/master/')
|
||||||
stack(
|
stack(
|
||||||
s("bd:5,[~ <sd:1!3 sd:1(3,4,3)>],hh27(3,4,1)") // drums
|
s("bd:5,[~ <sd:1!3 sd:1(3,4,3)>],hh27(3,4,1)") // drums
|
||||||
@ -554,8 +580,10 @@ stack(
|
|||||||
)
|
)
|
||||||
.slow(3/2)`;
|
.slow(3/2)`;
|
||||||
|
|
||||||
export const outroMusic = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const outroMusic = `// "Outro music"
|
||||||
// by Felix Roos
|
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
// @by Felix Roos
|
||||||
|
|
||||||
samples({
|
samples({
|
||||||
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
|
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'],
|
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||||
@ -583,8 +611,10 @@ samples({
|
|||||||
//.pianoroll({autorange:1,vertical:1,fold:0})
|
//.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/
|
export const bassFuge = `// "Bass fuge"
|
||||||
// by Felix Roos
|
// @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'] },
|
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/')
|
'github:cleary/samples-flbass/main/')
|
||||||
samples({
|
samples({
|
||||||
@ -609,8 +639,10 @@ x=>x.add(7).color('steelblue')
|
|||||||
.stack(s("bd:1*2,~ sd:0,[~ hh:0]*2"))
|
.stack(s("bd:1*2,~ sd:0,[~ hh:0]*2"))
|
||||||
.pianoroll({vertical:1})`;
|
.pianoroll({vertical:1})`;
|
||||||
|
|
||||||
export const chop = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const chop = `// "Chop"
|
||||||
// by Felix Roos
|
// @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' })
|
samples({ p: 'https://cdn.freesound.org/previews/648/648433_11943129-lq.mp3' })
|
||||||
|
|
||||||
s("p")
|
s("p")
|
||||||
@ -622,8 +654,10 @@ s("p")
|
|||||||
.sustain(.6)
|
.sustain(.6)
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const delay = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const delay = `// "Delay"
|
||||||
// by Felix Roos
|
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
// @by Felix Roos
|
||||||
|
|
||||||
stack(
|
stack(
|
||||||
s("bd <sd cp>")
|
s("bd <sd cp>")
|
||||||
.delay("<0 .5>")
|
.delay("<0 .5>")
|
||||||
@ -631,8 +665,10 @@ stack(
|
|||||||
.delayfeedback(".6 | .8")
|
.delayfeedback(".6 | .8")
|
||||||
).sometimes(x=>x.speed("-1"))`;
|
).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/
|
export const orbit = `// "Orbit"
|
||||||
// by Felix Roos
|
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
// @by Felix Roos
|
||||||
|
|
||||||
stack(
|
stack(
|
||||||
s("bd <sd cp>")
|
s("bd <sd cp>")
|
||||||
.delay(.5)
|
.delay(.5)
|
||||||
@ -645,8 +681,10 @@ stack(
|
|||||||
.orbit(2)
|
.orbit(2)
|
||||||
).sometimes(x=>x.speed("-1"))`;
|
).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/
|
export const belldub = `// "Belldub"
|
||||||
// by Felix Roos
|
// @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'}})
|
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
|
// "Hand Bells, B, Single.wav" by InspectorJ (www.jshaw.co.uk) of Freesound.org
|
||||||
stack(
|
stack(
|
||||||
@ -678,8 +716,10 @@ stack(
|
|||||||
.mask("<1 0>/8")
|
.mask("<1 0>/8")
|
||||||
).slow(5)`;
|
).slow(5)`;
|
||||||
|
|
||||||
export const dinofunk = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const dinofunk = `// "Dinofunk"
|
||||||
// by Felix Roos
|
// @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',
|
samples({bass:'https://cdn.freesound.org/previews/614/614637_2434927-hq.mp3',
|
||||||
dino:{b4:'https://cdn.freesound.org/previews/316/316403_5123851-hq.mp3'}})
|
dino:{b4:'https://cdn.freesound.org/previews/316/316403_5123851-hq.mp3'}})
|
||||||
setVoicingRange('lefthand', ['c3','a4'])
|
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)
|
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/
|
export const sampleDemo = `// "Sample demo"
|
||||||
// by Felix Roos
|
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
// @by Felix Roos
|
||||||
|
|
||||||
stack(
|
stack(
|
||||||
// percussion
|
// percussion
|
||||||
s("[woodblock:1 woodblock:2*2] snare_rim:0,gong/8,brakedrum:1(3,8),~@3 cowbell:3")
|
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)
|
.release(.1).room(.5)
|
||||||
)`;
|
)`;
|
||||||
|
|
||||||
export const holyflute = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const holyflute = `// "Holy flute"
|
||||||
// by Felix Roos
|
// @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"
|
"c3 eb3(3,8) c4/2 g3*2"
|
||||||
.superimpose(
|
.superimpose(
|
||||||
x=>x.slow(2).add(12),
|
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})
|
.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/
|
export const flatrave = `// "Flatrave"
|
||||||
// by Felix Roos
|
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
// @by Felix Roos
|
||||||
|
|
||||||
stack(
|
stack(
|
||||||
s("bd*2,~ [cp,sd]").bank('RolandTR909'),
|
s("bd*2,~ [cp,sd]").bank('RolandTR909'),
|
||||||
|
|
||||||
@ -751,8 +797,10 @@ stack(
|
|||||||
.decay(.05).sustain(0).delay(.2).degradeBy(.5).mask("<0 1>/16")
|
.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/
|
export const amensister = `// "Amensister"
|
||||||
// by Felix Roos
|
// @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')
|
await samples('github:tidalcycles/Dirt-Samples/master')
|
||||||
|
|
||||||
stack(
|
stack(
|
||||||
@ -785,8 +833,10 @@ stack(
|
|||||||
n("0 1").s("east").delay(.5).degradeBy(.8).speed(rand.range(.5,1.5))
|
n("0 1").s("east").delay(.5).degradeBy(.8).speed(rand.range(.5,1.5))
|
||||||
).reset("<x@7 x(5,8,-1)>")`;
|
).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/
|
export const juxUndTollerei = `// "Jux und tollerei"
|
||||||
// by Felix Roos
|
// @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()
|
note("c3 eb3 g3 bb3").palindrome()
|
||||||
.s('sawtooth')
|
.s('sawtooth')
|
||||||
.jux(x=>x.rev().color('green').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)
|
.delay(.5).delaytime(.1).delayfeedback(.4)
|
||||||
.pianoroll()`;
|
.pianoroll()`;
|
||||||
|
|
||||||
export const csoundDemo = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
export const csoundDemo = `// "CSound demo"
|
||||||
// by Felix Roos
|
// @license with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
// @by Felix Roos
|
||||||
|
|
||||||
await loadCsound\`
|
await loadCsound\`
|
||||||
instr CoolSynth
|
instr CoolSynth
|
||||||
iduration = p3
|
iduration = p3
|
||||||
@ -832,10 +884,10 @@ endin\`
|
|||||||
//.pianoroll()
|
//.pianoroll()
|
||||||
.csound('CoolSynth')`;
|
.csound('CoolSynth')`;
|
||||||
|
|
||||||
export const loungeSponge = `
|
export const loungeSponge = `// "Lounge sponge"
|
||||||
// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
// by Felix Roos
|
// @by Felix Roos, livecode.orc by Steven Yi
|
||||||
// livecode.orc by Steven Yi
|
|
||||||
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
|
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
|
||||||
|
|
||||||
stack(
|
stack(
|
||||||
@ -852,8 +904,10 @@ stack(
|
|||||||
s("bd*2,[~ hh]*2,~ cp").bank('RolandTR909')
|
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/
|
export const arpoon = `// "Arpoon"
|
||||||
// "Arpoon" by Felix Roos
|
// @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')
|
await samples('github:tidalcycles/Dirt-Samples/master')
|
||||||
|
|
||||||
"<<Am7 C^7> C7 F^7 [Fm7 E7b9]>".voicings('lefthand')
|
"<<Am7 C^7> C7 F^7 [Fm7 E7b9]>".voicings('lefthand')
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useStore } from '@nanostores/react';
|
|||||||
export const defaultSettings = {
|
export const defaultSettings = {
|
||||||
activeFooter: 'intro',
|
activeFooter: 'intro',
|
||||||
keybindings: 'codemirror',
|
keybindings: 'codemirror',
|
||||||
|
isLineNumbersDisplayed: true,
|
||||||
theme: 'strudelTheme',
|
theme: 'strudelTheme',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
@ -19,6 +20,7 @@ export function useSettings() {
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isZen: [true, 'true'].includes(state.isZen) ? true : false,
|
isZen: [true, 'true'].includes(state.isZen) ? true : false,
|
||||||
|
isLineNumbersDisplayed: [true, 'true'].includes(state.isLineNumbersDisplayed) ? true : false,
|
||||||
fontSize: Number(state.fontSize),
|
fontSize: Number(state.fontSize),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,10 +27,6 @@
|
|||||||
src: url('/fonts/FiraCode/FiraCode-SemiBold.ttf');
|
src: url('/fonts/FiraCode/FiraCode-SemiBold.ttf');
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-gutters {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose > h1:not(:first-child) {
|
.prose > h1:not(:first-child) {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,11 @@
|
|||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"types": [
|
"types": [
|
||||||
"vite-plugin-pwa/client"
|
"vite-plugin-pwa/client"
|
||||||
]
|
],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@components/*": ["src/components/*"],
|
||||||
|
"@src/*": ["src/*"],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user