Compare commits

..

74 Commits

Author SHA1 Message Date
Felix Roos 6d6114beb6 Merge branch 'main' into global-repl 2023-11-15 17:46:03 +01:00
Felix Roos 1aaaaba619 Merge pull request #798 from daslyfe/phaser
Create phaser effect
2023-11-13 23:30:14 +01:00
Felix Roos b7dd9b7d35 Merge remote-tracking branch 'origin/main' into phaser 2023-11-13 21:59:10 +01:00
Alex McLean d25851ac17 support multiple named serial connections, change default baudrate to 115200 (#551) 2023-11-10 11:17:35 +00:00
Felix Roos d02450c0d9 Merge pull request #662 from gogins/main
Fix for #1. Enables named instruments for csoundm.
2023-11-09 09:27:57 +01:00
Felix Roos 400b2109fb Merge pull request #706 from Bubobubobubobubo/global-vibrato
Adding vibrato to Superdough sampler
2023-11-09 09:26:45 +01:00
Felix Roos 2a09f9ef0b fix: sampler broke without vibrato 2023-11-09 09:21:40 +01:00
Felix Roos 0052d349d9 refactor: move vibrato up + cleanup oscillator 2023-11-09 09:06:50 +01:00
Felix Roos 7c8a8b8b70 simplify 2023-11-09 08:59:23 +01:00
Felix Roos 85835087ba format 2023-11-09 08:48:23 +01:00
Felix Roos 5aac042c80 Merge branch 'main' into global-vibrato 2023-11-09 08:47:37 +01:00
Felix Roos 9dbd073073 Merge pull request #784 from ilesinge/document_pianoroll
Document pianoroll
2023-11-09 08:46:03 +01:00
Felix Roos 035f6c8098 Merge pull request #795 from bwagner/patch-2
Update first-effects.mdx
2023-11-09 08:45:29 +01:00
Felix Roos d2a06654c3 Merge pull request #796 from bwagner/patch-3
Update pattern-effects.mdx
2023-11-09 08:44:43 +01:00
Felix Roos c028686298 Merge pull request #797 from bwagner/patch-4
Update recap.mdx
2023-11-09 08:43:50 +01:00
Felix Roos 858a02634e dedupe / move supertdirt phaser controls 2023-11-09 08:40:21 +01:00
Felix Roos 2682fd38f8 add to doc page 2023-11-09 08:29:21 +01:00
Felix Roos 3ecb701504 add params to superdough readme 2023-11-09 08:19:04 +01:00
Felix Roos ac359a0e9e doc: phaser synonyms 2023-11-09 08:17:16 +01:00
Felix Roos df636cf4f6 phaser shortcuts 2023-11-09 08:16:17 +01:00
Felix Roos 791a363694 snapshots 2023-11-08 23:33:46 +01:00
Jade Rowland 06eb8b5be8 fixed doc 2023-11-07 22:57:00 -05:00
Jade Rowland 4b4290e087 add more parameters 2023-11-07 22:52:39 -05:00
Jade Rowland c89f91215c Merge branch 'tidalcycles:main' into phaser 2023-11-07 22:11:55 -05:00
Jade Rowland 65d07dbff2 create free running lfos for different orbits 2023-11-07 22:09:53 -05:00
Jade Rowland 2e4d28b910 update doc 2023-11-07 21:49:56 -05:00
Jade Rowland 51c63fa996 turn off of depth is 0 2023-11-07 21:44:57 -05:00
Jade Rowland f0a582dc92 cleaning up 2023-11-07 21:10:12 -05:00
Jade Rowland bee36a7a4f cleaning up 2023-11-07 20:25:31 -05:00
Jade Rowland 0edd54dee3 cleaning up 2023-11-07 20:23:45 -05:00
Bernhard Wagner 4a16cd891f FIXES: table 2023-11-07 18:55:35 +01:00
Bernhard Wagner 23dd9d4c01 Update recap.mdx
Adds a few missing translations
2023-11-07 16:16:46 +01:00
Bernhard Wagner df277d6b7b Update pattern-effects.mdx
grammatical amendments
2023-11-07 16:10:57 +01:00
Bernhard Wagner eed2c304e4 Update first-effects.mdx
grammatical amendments
2023-11-07 15:57:31 +01:00
Felix Roos bd5953317e Merge pull request #794 from bwagner/patch-1
Update first-sounds.mdx
2023-11-07 11:53:48 +01:00
Bernhard Wagner b88f58cc21 Update first-sounds.mdx
in eine sogenannte Funktion -> in einer sogenannten Funktion
2023-11-07 11:38:13 +01:00
Jade Rowland 9cbf3079f3 change to bus effect experiment 2023-11-06 19:02:09 -05:00
Jade Rowland a9efda3bc3 tweaking to make it sound good 2023-11-05 15:40:02 -05:00
Alexandre G.-Raymond 8fe4dca4c2 Update pianoroll documentation 2023-11-05 18:07:42 +01:00
Alexandre G.-Raymond 8fa7bf795a Fix pianoroll documentation 2023-11-05 16:25:10 +01:00
Alexandre G.-Raymond f045fb44de Add color function documentation 2023-11-05 16:23:36 +01:00
Alexandre G.-Raymond 0d06840a1b Add label function documentation 2023-11-05 16:23:21 +01:00
Alexandre G.-Raymond 624e540cb4 Add pianoroll function documentation 2023-11-05 16:23:00 +01:00
Jade Rowland b3f8df1783 initial commit 2023-11-04 20:20:25 -04:00
Felix Roos 82380a0f4e make sliders really work 2023-11-03 14:08:13 +01:00
Felix Roos c030f456c3 widget support 2023-11-03 13:50:18 +01:00
Felix Roos 694e44dc8a better init + don't draw first frame by default 2023-11-03 12:07:19 +01:00
Felix Roos 351da196b9 StrudelMirror onPaint support 2023-11-03 11:46:32 +01:00
Felix Roos e27ee03517 format 2023-11-03 11:11:23 +01:00
Felix Roos 19db419c53 handle shuffle 2023-11-03 11:10:53 +01:00
Felix Roos ff60a89668 fix: autocomplete + add missing basic extensions 2023-11-03 10:47:05 +01:00
Felix Roos 7cc17fefcb fix: undo redo key mapping
+ fix hidden line number style
2023-11-03 10:20:07 +01:00
Felix Roos 76ee9485f4 format 2023-11-03 10:05:37 +01:00
Felix Roos 1d65f09f3c - enable most settings:
- keybindings
- line numbers
- line wrapping
- highlighting (new)
- flashing (new)
- autocomplete not working yet
- move themes to codemirror package (currently duplicated)
- generally simplify settings handling
2023-11-03 10:04:58 +01:00
Felix Roos cc97f6395d better keymap 2023-11-02 23:15:41 +01:00
Felix Roos 33eef619e4 no destructuring 2023-11-02 22:49:52 +01:00
Felix Roos e9e3b97621 format 2023-11-02 22:45:52 +01:00
Felix Roos 1f765e54ca Repl: use codemirror custom keys
+ use new prebake
2023-11-02 22:45:32 +01:00
Felix Roos 2b81cc5d0c comment out (probably) dead end 2023-11-02 22:37:42 +01:00
Felix Roos ca054dbd74 restore to old state 2023-11-02 22:37:31 +01:00
Felix Roos a8433f61a4 add missing dep 2023-11-02 22:36:03 +01:00
Felix Roos 5850106eef restore react hooks 2023-11-02 22:33:16 +01:00
Felix Roos 95a9ab6182 revert Repl.jsx 2023-11-02 22:29:27 +01:00
Felix Roos ba19c4ddce duplicate new lighter repl 2023-11-02 22:28:46 +01:00
Felix Roos b49b1c98ae make play and update buttons work 2023-11-02 17:14:59 +01:00
Felix Roos 7c00aa1a36 intermediate /vanilla repl
+ react is now only used for ui around editor!
+ ui still not fully functional
2023-11-02 16:31:45 +01:00
Felix Roos 07d6bb3c44 - encapsulate repl state from react
- might still be buggy!
- smarter way to flush sounds
2023-11-02 14:37:50 +01:00
Felix Roos 3f2e2554be add back missing shortcuts 2023-11-02 11:29:36 +01:00
Felix Roos 9ebb7edde9 remove old export 2023-11-02 11:29:27 +01:00
Felix Roos a89091cbc7 bind custom keyboard shortcuts via codemirror
+ add reevaluate and panic shortcuts
2023-11-02 11:24:23 +01:00
Felix Roos 0d1a99deb2 also pull in audio init to prebake 2023-11-02 10:37:38 +01:00
Felix Roos b71f8a33d7 refactor: pull module loading into prebake 2023-11-02 10:21:39 +01:00
Raphael Forment 08aab9d4d8 Adding vibrato to Superdough sampler
Modulating the `playbackRate` to simulate a vibrato!
2023-09-20 19:44:32 +02:00
gogins 1170ff58ee Fix for #1. 2023-08-10 20:26:37 -04:00
51 changed files with 2410 additions and 441 deletions
+88
View File
@@ -0,0 +1,88 @@
import { createRoot } from 'react-dom/client';
import jsdoc from '../../doc.json';
// import { javascriptLanguage } from '@codemirror/lang-javascript';
import { autocompletion } from '@codemirror/autocomplete';
const getDocLabel = (doc) => doc.name || doc.longname;
const getInnerText = (html) => {
var div = document.createElement('div');
div.innerHTML = html;
return div.textContent || div.innerText || '';
};
export function Autocomplete({ doc }) {
return (
<div className="prose dark:prose-invert max-h-[400px] overflow-auto">
<h3 className="pt-0 mt-0">{getDocLabel(doc)}</h3>
<div dangerouslySetInnerHTML={{ __html: doc.description }} />
<ul>
{doc.params?.map(({ name, type, description }, i) => (
<li key={i}>
{name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)}</> : ''}
</li>
))}
</ul>
<div>
{doc.examples?.map((example, i) => (
<div key={i}>
<pre
className="cursor-pointer"
onMouseDown={(e) => {
console.log('ola!');
navigator.clipboard.writeText(example);
e.stopPropagation();
}}
>
{example}
</pre>
</div>
))}
</div>
</div>
);
}
const jsdocCompletions = jsdoc.docs
.filter(
(doc) =>
getDocLabel(doc) &&
!getDocLabel(doc).startsWith('_') &&
!['package'].includes(doc.kind) &&
!['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)),
)
// https://codemirror.net/docs/ref/#autocomplete.Completion
.map((doc) /*: Completion */ => ({
label: getDocLabel(doc),
// detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
info: () => {
const node = document.createElement('div');
// if Autocomplete is non-interactive, it could also be rendered at build time..
// .. using renderToStaticMarkup
createRoot(node).render(<Autocomplete doc={doc} />);
return node;
},
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
}));
export const strudelAutocomplete = (context /* : CompletionContext */) => {
let word = context.matchBefore(/\w*/);
if (word.from == word.to && !context.explicit) return null;
return {
from: word.from,
options: jsdocCompletions,
/* options: [
{ label: 'match', type: 'keyword' },
{ label: 'hello', type: 'variable', info: '(World)' },
{ label: 'magic', type: 'text', apply: '⠁⭒*.✩.*⭒⠁', detail: 'macro' },
], */
};
};
export function isAutoCompletionEnabled(on) {
return on
? [
autocompletion({ override: [strudelAutocomplete] }),
//javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }),
]
: []; // autocompletion({ override: [] })
}
+169 -40
View File
@@ -1,36 +1,78 @@
import { defaultKeymap } from '@codemirror/commands';
import { closeBrackets } from '@codemirror/autocomplete';
// import { search, highlightSelectionMatches } from '@codemirror/search';
import { history } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript';
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
import { EditorState } from '@codemirror/state';
import { EditorView, highlightActiveLineGutter, keymap, lineNumbers } from '@codemirror/view';
import { Drawer, repl } from '@strudel.cycles/core';
import { flashField, flash } from './flash.mjs';
import { highlightExtension, highlightMiniLocations } from './highlight.mjs';
import { oneDark } from './themes/one-dark';
import { Compartment, EditorState } from '@codemirror/state';
import { EditorView, highlightActiveLineGutter, highlightActiveLine, keymap, lineNumbers } from '@codemirror/view';
import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core';
import { isAutoCompletionEnabled } from './Autocomplete';
import { flash, isFlashEnabled } from './flash.mjs';
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
import { keybindings } from './keybindings.mjs';
import { theme } from './themes.mjs';
import { updateWidgets, sliderPlugin } from './slider.mjs';
const extensions = {
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []),
theme,
isAutoCompletionEnabled,
isPatternHighlightingEnabled,
isFlashEnabled,
keybindings,
};
const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
// https://codemirror.net/docs/guide/
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, theme = oneDark, root }) {
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, settings, root }) {
const initialSettings = Object.keys(compartments).map((key) =>
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
);
let state = EditorState.create({
doc: initialCode,
extensions: [
theme,
/* search(),
highlightSelectionMatches(), */
...initialSettings,
javascript(),
lineNumbers(),
highlightExtension,
sliderPlugin,
// indentOnInput(), // works without. already brought with javascript extension?
// bracketMatching(), // does not do anything
closeBrackets(),
highlightActiveLineGutter(),
highlightActiveLine(),
syntaxHighlighting(defaultHighlightStyle),
keymap.of(defaultKeymap),
flashField,
history(),
EditorView.updateListener.of((v) => onChange(v)),
keymap.of([
{
key: 'Ctrl-Enter',
run: () => onEvaluate(),
run: () => onEvaluate?.(),
},
{
key: 'Alt-Enter',
run: () => onEvaluate?.(),
},
{
key: 'Ctrl-.',
run: () => onStop(),
run: () => onStop?.(),
},
{
key: 'Alt-.',
run: (_, e) => {
e.preventDefault();
onStop?.();
},
},
/* {
key: 'Ctrl-Shift-.',
run: () => (onPanic ? onPanic() : onStop?.()),
},
{
key: 'Ctrl-Shift-Enter',
run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()),
}, */
]),
],
});
@@ -43,71 +85,158 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, the
export class StrudelMirror {
constructor(options) {
const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, ...replOptions } = options;
const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, settings, ...replOptions } = options;
this.code = initialCode;
this.root = root;
this.miniLocations = [];
this.widgets = [];
this.painters = [];
this.onDraw = onDraw;
const self = this;
this.drawer = new Drawer((haps, time) => {
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
this.highlight(currentFrame, time);
onDraw?.(haps, time, currentFrame);
this.onDraw?.(haps, time, currentFrame, this.painters);
}, 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 approach might not work with multiple repls on screen..
Pattern.prototype.onPaint = function (onPaint) {
self.painters.push(onPaint);
return this;
};
this.prebaked = prebake();
// this.drawFirstFrame();
this.repl = repl({
...replOptions,
onToggle: async (started) => {
onToggle: (started) => {
replOptions?.onToggle?.(started);
const { scheduler } = await this.repl;
if (started) {
this.drawer.start(scheduler);
this.drawer.start(this.repl.scheduler);
} else {
this.drawer.stop();
updateMiniLocations(this.editor, []);
cleanupDraw(false);
}
},
beforeEval: async () => {
await prebaked;
cleanupDraw();
this.painters = [];
await this.prebaked;
await replOptions?.beforeEval?.();
},
afterEval: (options) => {
// remember for when highlighting is toggled on
this.miniLocations = options.meta?.miniLocations;
this.widgets = options.meta?.widgets;
updateWidgets(this.editor, this.widgets);
updateMiniLocations(this.editor, this.miniLocations);
replOptions?.afterEval?.(options);
this.drawer.invalidate();
},
});
this.editor = initEditor({
root,
settings,
initialCode,
onChange: (v) => {
this.code = v.state.doc.toString();
if (v.docChanged) {
this.code = v.state.doc.toString();
this.repl.setCode(this.code);
}
},
onEvaluate: () => this.evaluate(),
onStop: () => this.stop(),
});
}
async drawFirstFrame() {
if (!this.onDraw) {
return;
}
// draw first frame instantly
await this.prebaked;
try {
await this.repl.evaluate(this.code, false);
this.drawer.invalidate(this.repl.scheduler);
this.onDraw?.(this.drawer.visibleHaps, 0, []);
} catch (err) {
console.warn('first frame could not be painted');
}
}
async evaluate() {
const { evaluate } = await this.repl;
this.flash();
await evaluate(this.code);
await this.repl.evaluate(this.code);
}
async stop() {
const { scheduler } = await this.repl;
scheduler.stop();
this.repl.scheduler.stop();
}
async toggle() {
if (this.repl.scheduler.started) {
this.repl.scheduler.stop();
} else {
this.evaluate();
}
}
flash(ms) {
flash(this.editor, ms);
}
highlight(haps, time) {
highlightMiniLocations(this.editor.view, time, haps);
highlightMiniLocations(this.editor, time, haps);
}
setFontSize(size) {
this.root.style.fontSize = size + 'px';
}
setFontFamily(family) {
this.root.style.fontFamily = family;
}
reconfigureExtension(key, value) {
if (!extensions[key]) {
console.warn(`extension ${key} is not known`);
return;
}
value = parseBooleans(value);
const newValue = extensions[key](value, this);
this.editor.dispatch({
effects: compartments[key].reconfigure(newValue),
});
}
setLineWrappingEnabled(enabled) {
this.reconfigureExtension('isLineWrappingEnabled', enabled);
}
setLineNumbersDisplayed(enabled) {
this.reconfigureExtension('isLineNumbersDisplayed', enabled);
}
setTheme(theme) {
this.reconfigureExtension('theme', theme);
}
setAutocompletionEnabled(enabled) {
this.reconfigureExtension('isAutoCompletionEnabled', enabled);
}
updateSettings(settings) {
this.setFontSize(settings.fontSize);
this.setFontFamily(settings.fontFamily);
for (let key in extensions) {
this.reconfigureExtension(key, settings[key]);
}
}
changeSetting(key, value) {
if (extensions[key]) {
this.reconfigureExtension(key, value);
return;
} else if (key === 'fontFamily') {
this.setFontFamily(value);
} else if (key === 'fontSize') {
this.setFontSize(value);
}
}
setCode(code) {
const changes = { from: 0, to: this.editor.state.doc.length, insert: code };
this.editor.dispatch({ changes });
}
}
function parseBooleans(value) {
return { true: true, false: false }[value] ?? value;
}
+2
View File
@@ -33,3 +33,5 @@ export const flash = (view, ms = 200) => {
view.dispatch({ effects: setFlash.of(false) });
}, ms);
};
export const isFlashEnabled = (on) => (on ? flashField : []);
+9
View File
@@ -124,3 +124,12 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
});
export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights];
export const isPatternHighlightingEnabled = (on, config) => {
on &&
config &&
setTimeout(() => {
updateMiniLocations(config.editor, config.miniLocations);
}, 100);
return on ? highlightExtension : [];
};
+31
View File
@@ -0,0 +1,31 @@
import { Prec } from '@codemirror/state';
import { keymap, ViewPlugin } from '@codemirror/view';
// import { searchKeymap } from '@codemirror/search';
import { emacs } from '@replit/codemirror-emacs';
import { vim } from '@replit/codemirror-vim';
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
import { defaultKeymap, historyKeymap } from '@codemirror/commands';
const vscodePlugin = ViewPlugin.fromClass(
class {
constructor() {}
},
{
provide: () => {
return Prec.highest(keymap.of([...vscodeKeymap]));
},
},
);
const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
const keymaps = {
vim,
emacs,
vscode: vscodeExtension,
};
export function keybindings(name) {
const active = keymaps[name];
return [keymap.of(defaultKeymap), keymap.of(historyKeymap), active ? active() : []];
// keymap.of(searchKeymap),
}
+9 -1
View File
@@ -33,13 +33,21 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@codemirror/autocomplete": "^6.6.0",
"@codemirror/commands": "^6.2.4",
"@codemirror/lang-javascript": "^6.1.7",
"@codemirror/language": "^6.6.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.10.0",
"@lezer/highlight": "^1.1.4",
"@strudel.cycles/core": "workspace:*"
"@replit/codemirror-emacs": "^6.0.1",
"@replit/codemirror-vim": "^6.0.14",
"@replit/codemirror-vscode-keymap": "^6.0.2",
"@strudel.cycles/core": "workspace:*",
"@uiw/codemirror-themes": "^4.19.16",
"@uiw/codemirror-themes-all": "^4.19.16",
"react-dom": "^18.2.0"
},
"devDependencies": {
"vite": "^4.3.3"
+484
View File
@@ -0,0 +1,484 @@
import {
abcdef,
androidstudio,
atomone,
aura,
bespin,
darcula,
dracula,
duotoneDark,
eclipse,
githubDark,
gruvboxDark,
materialDark,
nord,
okaidia,
solarizedDark,
sublime,
tokyoNight,
tokyoNightStorm,
vscodeDark,
xcodeDark,
bbedit,
duotoneLight,
githubLight,
gruvboxLight,
materialLight,
noctisLilac,
solarizedLight,
tokyoNightDay,
xcodeLight,
} from '@uiw/codemirror-themes-all';
import strudelTheme from './themes/strudel-theme';
import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen';
import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen';
import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen';
import teletext, { settings as teletextSettings } from './themes/teletext';
import algoboy, { settings as algoboySettings } from './themes/algoboy';
import terminal, { settings as terminalSettings } from './themes/terminal';
export const themes = {
strudelTheme,
bluescreen,
blackscreen,
whitescreen,
teletext,
algoboy,
terminal,
abcdef,
androidstudio,
atomone,
aura,
bespin,
darcula,
dracula,
duotoneDark,
eclipse,
githubDark,
gruvboxDark,
materialDark,
nord,
okaidia,
solarizedDark,
sublime,
tokyoNight,
tokyoNightStorm,
vscodeDark,
xcodeDark,
bbedit,
duotoneLight,
githubLight,
gruvboxLight,
materialLight,
noctisLilac,
solarizedLight,
tokyoNightDay,
xcodeLight,
};
// lineBackground is background with 50% opacity, to make sure the selection below is visible
export const settings = {
strudelTheme: {
background: '#222',
lineBackground: '#22222299',
foreground: '#fff',
// foreground: '#75baff',
caret: '#ffcc00',
selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626',
// lineHighlight: '#8a91991a', // original
lineHighlight: '#00000050',
gutterBackground: 'transparent',
// gutterForeground: '#8a919966',
gutterForeground: '#8a919966',
},
bluescreen: bluescreenSettings,
blackscreen: blackscreenSettings,
whitescreen: whitescreenSettings,
teletext: teletextSettings,
algoboy: algoboySettings,
terminal: terminalSettings,
abcdef: {
background: '#0f0f0f',
lineBackground: '#0f0f0f99',
foreground: '#defdef',
caret: '#00FF00',
selection: '#515151',
selectionMatch: '#515151',
gutterBackground: '#555',
gutterForeground: '#FFFFFF',
lineHighlight: '#314151',
},
androidstudio: {
background: '#282b2e',
lineBackground: '#282b2e99',
foreground: '#a9b7c6',
caret: '#00FF00',
selection: '#343739',
selectionMatch: '#343739',
lineHighlight: '#343739',
},
atomone: {
background: '#272C35',
lineBackground: '#272C3599',
foreground: '#9d9b97',
caret: '#797977',
selection: '#ffffff30',
selectionMatch: '#2B323D',
gutterBackground: '#272C35',
gutterForeground: '#465063',
gutterBorder: 'transparent',
lineHighlight: '#2B323D',
},
aura: {
background: '#21202e',
lineBackground: '#21202e99',
foreground: '#edecee',
caret: '#a277ff',
selection: '#3d375e7f',
selectionMatch: '#3d375e7f',
gutterBackground: '#21202e',
gutterForeground: '#edecee',
gutterBorder: 'transparent',
lineHighlight: '#a394f033',
},
bbedit: {
light: true,
background: '#FFFFFF',
lineBackground: '#FFFFFF99',
foreground: '#000000',
caret: '#FBAC52',
selection: '#FFD420',
selectionMatch: '#FFD420',
gutterBackground: '#f5f5f5',
gutterForeground: '#4D4D4C',
gutterBorder: 'transparent',
lineHighlight: '#00000012',
},
bespin: {
background: '#28211c',
lineBackground: '#28211c99',
foreground: '#9d9b97',
caret: '#797977',
selection: '#36312e',
selectionMatch: '#4f382b',
gutterBackground: '#28211c',
gutterForeground: '#666666',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
darcula: {
background: '#2B2B2B',
lineBackground: '#2B2B2B99',
foreground: '#f8f8f2',
caret: '#FFFFFF',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
gutterBackground: 'rgba(255, 255, 255, 0.1)',
gutterForeground: '#999',
gutterBorder: 'transparent',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
dracula: {
background: '#282a36',
lineBackground: '#282a3699',
foreground: '#f8f8f2',
caret: '#f8f8f0',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
gutterBackground: '#282a36',
gutterForeground: '#6D8A88',
gutterBorder: 'transparent',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
duotoneLight: {
light: true,
background: '#faf8f5',
lineBackground: '#faf8f599',
foreground: '#b29762',
caret: '#93abdc',
selection: '#e3dcce',
selectionMatch: '#e3dcce',
gutterBackground: '#faf8f5',
gutterForeground: '#cdc4b1',
gutterBorder: 'transparent',
lineHighlight: '#EFEFEF',
},
duotoneDark: {
background: '#2a2734',
lineBackground: '#2a273499',
foreground: '#6c6783',
caret: '#ffad5c',
selection: 'rgba(255, 255, 255, 0.1)',
gutterBackground: '#2a2734',
gutterForeground: '#545167',
lineHighlight: '#36334280',
},
eclipse: {
light: true,
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#000',
caret: '#FFFFFF',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
gutterBackground: '#f7f7f7',
gutterForeground: '#999',
lineHighlight: '#e8f2ff',
gutterBorder: 'transparent',
},
githubLight: {
light: true,
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#24292e',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#6e7781',
},
githubDark: {
background: '#0d1117',
lineBackground: '#0d111799',
foreground: '#c9d1d9',
caret: '#c9d1d9',
selection: '#003d73',
selectionMatch: '#003d73',
lineHighlight: '#36334280',
},
gruvboxDark: {
background: '#282828',
lineBackground: '#28282899',
foreground: '#ebdbb2',
caret: '#ebdbb2',
selection: '#bdae93',
selectionMatch: '#bdae93',
lineHighlight: '#3c3836',
gutterBackground: '#282828',
gutterForeground: '#7c6f64',
},
gruvboxLight: {
light: true,
background: '#fbf1c7',
lineBackground: '#fbf1c799',
foreground: '#3c3836',
caret: '#af3a03',
selection: '#ebdbb2',
selectionMatch: '#bdae93',
lineHighlight: '#ebdbb2',
gutterBackground: '#ebdbb2',
gutterForeground: '#665c54',
gutterBorder: 'transparent',
},
materialDark: {
background: '#2e3235',
lineBackground: '#2e323599',
foreground: '#bdbdbd',
caret: '#a0a4ae',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
gutterBackground: '#2e3235',
gutterForeground: '#999',
gutterActiveForeground: '#4f5b66',
lineHighlight: '#545b61',
},
materialLight: {
light: true,
background: '#FAFAFA',
lineBackground: '#FAFAFA99',
foreground: '#90A4AE',
caret: '#272727',
selection: '#80CBC440',
selectionMatch: '#FAFAFA',
gutterBackground: '#FAFAFA',
gutterForeground: '#90A4AE',
gutterBorder: 'transparent',
lineHighlight: '#CCD7DA50',
},
noctisLilac: {
light: true,
background: '#f2f1f8',
lineBackground: '#f2f1f899',
foreground: '#0c006b',
caret: '#5c49e9',
selection: '#d5d1f2',
selectionMatch: '#d5d1f2',
gutterBackground: '#f2f1f8',
gutterForeground: '#0c006b70',
lineHighlight: '#e1def3',
},
nord: {
background: '#2e3440',
lineBackground: '#2e344099',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#3b4252',
selectionMatch: '#e5e9f0',
gutterBackground: '#2e3440',
gutterForeground: '#4c566a',
gutterActiveForeground: '#d8dee9',
lineHighlight: '#4c566a',
},
okaidia: {
background: '#272822',
lineBackground: '#27282299',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#49483E',
selectionMatch: '#49483E',
gutterBackground: '#272822',
gutterForeground: '#FFFFFF70',
lineHighlight: '#00000059',
},
solarizedLight: {
light: true,
background: '#fdf6e3',
lineBackground: '#fdf6e399',
foreground: '#657b83',
caret: '#586e75',
selection: '#dfd9c8',
selectionMatch: '#dfd9c8',
gutterBackground: '#00000010',
gutterForeground: '#657b83',
lineHighlight: '#dfd9c8',
},
solarizedDark: {
background: '#002b36',
lineBackground: '#002b3699',
foreground: '#93a1a1',
caret: '#839496',
selection: '#173541',
selectionMatch: '#aafe661a',
gutterBackground: '#00252f',
gutterForeground: '#839496',
lineHighlight: '#173541',
},
sublime: {
background: '#303841',
lineBackground: '#30384199',
foreground: '#FFFFFF',
caret: '#FBAC52',
selection: '#4C5964',
selectionMatch: '#3A546E',
gutterBackground: '#303841',
gutterForeground: '#FFFFFF70',
lineHighlight: '#00000059',
},
tokyoNightDay: {
light: true,
background: '#e1e2e7',
lineBackground: '#e1e2e799',
foreground: '#3760bf',
caret: '#3760bf',
selection: '#99a7df',
selectionMatch: '#99a7df',
gutterBackground: '#e1e2e7',
gutterForeground: '#3760bf',
gutterBorder: 'transparent',
lineHighlight: '#5f5faf11',
},
tokyoNightStorm: {
background: '#24283b',
lineBackground: '#24283b99',
foreground: '#7982a9',
caret: '#c0caf5',
selection: '#6f7bb630',
selectionMatch: '#1f2335',
gutterBackground: '#24283b',
gutterForeground: '#7982a9',
gutterBorder: 'transparent',
lineHighlight: '#292e42',
},
tokyoNight: {
background: '#1a1b26',
lineBackground: '#1a1b2699',
foreground: '#787c99',
caret: '#c0caf5',
selection: '#515c7e40',
selectionMatch: '#16161e',
gutterBackground: '#1a1b26',
gutterForeground: '#787c99',
gutterBorder: 'transparent',
lineHighlight: '#1e202e',
},
vscodeDark: {
background: '#1e1e1e',
lineBackground: '#1e1e1e99',
foreground: '#9cdcfe',
caret: '#c6c6c6',
selection: '#6199ff2f',
selectionMatch: '#72a1ff59',
lineHighlight: '#ffffff0f',
gutterBackground: '#1e1e1e',
gutterForeground: '#838383',
gutterActiveForeground: '#fff',
},
xcodeLight: {
light: true,
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#3D3D3D',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#AFAFAF',
lineHighlight: '#EDF4FF',
},
xcodeDark: {
background: '#292A30',
lineBackground: '#292A3099',
foreground: '#CECFD0',
caret: '#fff',
selection: '#727377',
selectionMatch: '#727377',
lineHighlight: '#2F3239',
},
};
function getColors(str) {
const colorRegex = /#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})/g;
const colors = [];
let match;
while ((match = colorRegex.exec(str)) !== null) {
const color = match[0];
if (!colors.includes(color)) {
colors.push(color);
}
}
return colors;
}
// TODO: remove
export function themeColors(theme) {
return getColors(stringifySafe(theme));
}
function getCircularReplacer() {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
}
function stringifySafe(json) {
return JSON.stringify(json, getCircularReplacer());
}
export function injectStyle(rule) {
const newStyle = document.createElement('style');
document.head.appendChild(newStyle);
const styleSheet = newStyle.sheet;
const ruleIndex = styleSheet.insertRule(rule, 0);
return () => styleSheet.deleteRule(ruleIndex);
}
export const theme = (theme) => themes[theme] || themes.strudelTheme;
+41
View File
@@ -0,0 +1,41 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
export const settings = {
background: '#9bbc0f',
foreground: '#0f380f', // whats that?
caret: '#0f380f',
selection: '#306230',
selectionMatch: '#ffffff26',
lineHighlight: '#8bac0f',
lineBackground: '#9bbc0f50',
//lineBackground: 'transparent',
gutterBackground: 'transparent',
gutterForeground: '#0f380f',
light: true,
customStyle: '.cm-line { line-height: 1 }',
};
export default createTheme({
theme: 'light',
settings,
styles: [
{ tag: t.keyword, color: '#0f380f' },
{ tag: t.operator, color: '#0f380f' },
{ tag: t.special(t.variableName), color: '#0f380f' },
{ tag: t.typeName, color: '#0f380f' },
{ tag: t.atom, color: '#0f380f' },
{ tag: t.number, color: '#0f380f' },
{ tag: t.definition(t.variableName), color: '#0f380f' },
{ tag: t.string, color: '#0f380f' },
{ tag: t.special(t.string), color: '#0f380f' },
{ tag: t.comment, color: '#0f380f' },
{ tag: t.variableName, color: '#0f380f' },
{ tag: t.tagName, color: '#0f380f' },
{ tag: t.bracket, color: '#0f380f' },
{ tag: t.meta, color: '#0f380f' },
{ tag: t.attributeName, color: '#0f380f' },
{ tag: t.propertyName, color: '#0f380f' },
{ tag: t.className, color: '#0f380f' },
{ tag: t.invalid, color: '#0f380f' },
{ tag: [t.unit, t.punctuation], color: '#0f380f' },
],
});
+38
View File
@@ -0,0 +1,38 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
export const settings = {
background: 'black',
foreground: 'white', // whats that?
caret: 'white',
selection: '#ffffff20',
selectionMatch: '#036dd626',
lineHighlight: '#ffffff10',
lineBackground: '#00000050',
gutterBackground: 'transparent',
gutterForeground: '#8a919966',
};
export default createTheme({
theme: 'dark',
settings,
styles: [
{ tag: t.keyword, color: 'white' },
{ tag: t.operator, color: 'white' },
{ tag: t.special(t.variableName), color: 'white' },
{ tag: t.typeName, color: 'white' },
{ tag: t.atom, color: 'white' },
{ tag: t.number, color: 'white' },
{ tag: t.definition(t.variableName), color: 'white' },
{ tag: t.string, color: 'white' },
{ tag: t.special(t.string), color: 'white' },
{ tag: t.comment, color: 'white' },
{ tag: t.variableName, color: 'white' },
{ tag: t.tagName, color: 'white' },
{ tag: t.bracket, color: 'white' },
{ tag: t.meta, color: 'white' },
{ tag: t.attributeName, color: 'white' },
{ tag: t.propertyName, color: 'white' },
{ tag: t.className, color: 'white' },
{ tag: t.invalid, color: 'white' },
{ tag: [t.unit, t.punctuation], color: 'white' },
],
});
+41
View File
@@ -0,0 +1,41 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
export const settings = {
background: '#051DB5',
lineBackground: '#051DB550',
foreground: 'white', // whats that?
caret: 'white',
selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626',
// lineHighlight: '#8a91991a', // original
lineHighlight: '#00000050',
gutterBackground: 'transparent',
// gutterForeground: '#8a919966',
gutterForeground: '#8a919966',
};
export default createTheme({
theme: 'dark',
settings,
styles: [
{ tag: t.keyword, color: 'white' },
{ tag: t.operator, color: 'white' },
{ tag: t.special(t.variableName), color: 'white' },
{ tag: t.typeName, color: 'white' },
{ tag: t.atom, color: 'white' },
{ tag: t.number, color: 'white' },
{ tag: t.definition(t.variableName), color: 'white' },
{ tag: t.string, color: 'white' },
{ tag: t.special(t.string), color: 'white' },
{ tag: t.comment, color: 'white' },
{ tag: t.variableName, color: 'white' },
{ tag: t.tagName, color: 'white' },
{ tag: t.bracket, color: 'white' },
{ tag: t.meta, color: 'white' },
{ tag: t.attributeName, color: 'white' },
{ tag: t.propertyName, color: 'white' },
{ tag: t.className, color: 'white' },
{ tag: t.invalid, color: 'white' },
{ tag: [t.unit, t.punctuation], color: 'white' },
],
});
-139
View File
@@ -1,139 +0,0 @@
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)];
+45
View File
@@ -0,0 +1,45 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
export default createTheme({
theme: 'dark',
settings: {
background: '#222',
foreground: '#75baff', // whats that?
caret: '#ffcc00',
selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626',
// lineHighlight: '#8a91991a', // original
lineHighlight: '#00000050',
gutterBackground: 'transparent',
// gutterForeground: '#8a919966',
gutterForeground: '#8a919966',
},
styles: [
{ tag: t.keyword, color: '#c792ea' },
{ tag: t.operator, color: '#89ddff' },
{ tag: t.special(t.variableName), color: '#eeffff' },
// { tag: t.typeName, color: '#f07178' }, // original
{ tag: t.typeName, color: '#c3e88d' },
{ tag: t.atom, color: '#f78c6c' },
// { tag: t.number, color: '#ff5370' }, // original
{ tag: t.number, color: '#c3e88d' },
{ tag: t.definition(t.variableName), color: '#82aaff' },
{ tag: t.string, color: '#c3e88d' },
// { tag: t.special(t.string), color: '#f07178' }, // original
{ tag: t.special(t.string), color: '#c3e88d' },
{ tag: t.comment, color: '#7d8799' },
// { tag: t.variableName, color: '#f07178' }, // original
{ tag: t.variableName, color: '#c792ea' },
// { tag: t.tagName, color: '#ff5370' }, // original
{ tag: t.tagName, color: '#c3e88d' },
{ tag: t.bracket, color: '#525154' },
// { tag: t.bracket, color: '#a2a1a4' }, // original
{ tag: t.meta, color: '#ffcb6b' },
{ tag: t.attributeName, color: '#c792ea' },
{ tag: t.propertyName, color: '#c792ea' },
{ tag: t.className, color: '#decb6b' },
{ tag: t.invalid, color: '#ffffff' },
{ tag: [t.unit, t.punctuation], color: '#82aaff' },
],
});
+50
View File
@@ -0,0 +1,50 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
let colorA = '#6edee4';
//let colorB = 'magenta';
let colorB = 'white';
let colorC = 'red';
let colorD = '#f8fc55';
export const settings = {
background: '#000000',
foreground: colorA, // whats that?
caret: colorC,
selection: colorD,
selectionMatch: colorA,
lineHighlight: '#6edee440', // panel bg
lineBackground: '#00000040',
gutterBackground: 'transparent',
gutterForeground: '#8a919966',
customStyle: '.cm-line { line-height: 1 }',
};
let punctuation = colorD;
let mini = colorB;
export default createTheme({
theme: 'dark',
settings,
styles: [
{ tag: t.keyword, color: colorA },
{ tag: t.operator, color: mini },
{ tag: t.special(t.variableName), color: colorA },
{ tag: t.typeName, color: colorA },
{ tag: t.atom, color: colorA },
{ tag: t.number, color: mini },
{ tag: t.definition(t.variableName), color: colorA },
{ tag: t.string, color: mini },
{ tag: t.special(t.string), color: mini },
{ tag: t.comment, color: punctuation },
{ tag: t.variableName, color: colorA },
{ tag: t.tagName, color: colorA },
{ tag: t.bracket, color: punctuation },
{ tag: t.meta, color: colorA },
{ tag: t.attributeName, color: colorA },
{ tag: t.propertyName, color: colorA }, // methods
{ tag: t.className, color: colorA },
{ tag: t.invalid, color: colorC },
{ tag: [t.unit, t.punctuation], color: punctuation },
],
});
+36
View File
@@ -0,0 +1,36 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
export const settings = {
background: 'black',
foreground: '#41FF00', // whats that?
caret: '#41FF00',
selection: '#ffffff20',
selectionMatch: '#036dd626',
lineHighlight: '#ffffff10',
gutterBackground: 'transparent',
gutterForeground: '#8a919966',
};
export default createTheme({
theme: 'dark',
settings,
styles: [
{ tag: t.keyword, color: '#41FF00' },
{ tag: t.operator, color: '#41FF00' },
{ tag: t.special(t.variableName), color: '#41FF00' },
{ tag: t.typeName, color: '#41FF00' },
{ tag: t.atom, color: '#41FF00' },
{ tag: t.number, color: '#41FF00' },
{ tag: t.definition(t.variableName), color: '#41FF00' },
{ tag: t.string, color: '#41FF00' },
{ tag: t.special(t.string), color: '#41FF00' },
{ tag: t.comment, color: '#41FF00' },
{ tag: t.variableName, color: '#41FF00' },
{ tag: t.tagName, color: '#41FF00' },
{ tag: t.bracket, color: '#41FF00' },
{ tag: t.meta, color: '#41FF00' },
{ tag: t.attributeName, color: '#41FF00' },
{ tag: t.propertyName, color: '#41FF00' },
{ tag: t.className, color: '#41FF00' },
{ tag: t.invalid, color: '#41FF00' },
],
});
+38
View File
@@ -0,0 +1,38 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
export const settings = {
background: 'white',
foreground: 'black', // whats that?
caret: 'black',
selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#ffffff26',
lineHighlight: '#cccccc50',
lineBackground: '#ffffff50',
gutterBackground: 'transparent',
gutterForeground: 'black',
light: true,
};
export default createTheme({
theme: 'light',
settings,
styles: [
{ tag: t.keyword, color: 'black' },
{ tag: t.operator, color: 'black' },
{ tag: t.special(t.variableName), color: 'black' },
{ tag: t.typeName, color: 'black' },
{ tag: t.atom, color: 'black' },
{ tag: t.number, color: 'black' },
{ tag: t.definition(t.variableName), color: 'black' },
{ tag: t.string, color: 'black' },
{ tag: t.special(t.string), color: 'black' },
{ tag: t.comment, color: 'black' },
{ tag: t.variableName, color: 'black' },
{ tag: t.tagName, color: 'black' },
{ tag: t.bracket, color: 'black' },
{ tag: t.meta, color: 'black' },
{ tag: t.attributeName, color: 'black' },
{ tag: t.propertyName, color: 'black' },
{ tag: t.className, color: 'black' },
{ tag: t.invalid, color: 'black' },
],
});
+64 -5
View File
@@ -380,6 +380,62 @@ const generic_params = [
*
*/
['coarse'],
['phaserrate', 'phasr'], // superdirt only
/**
* Phaser audio effect that approximates popular guitar pedals.
*
* @name phaser
* @synonyms ph
* @param {number | Pattern} speed speed of modulation
* @example
* n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
* .phaser("<1 2 4 8>")
*
*/
[['phaser', 'phaserdepth', 'phasercenter', 'phasersweep'], 'ph'],
/**
* The frequency sweep range of the lfo for the phaser effect. Defaults to 2000
*
* @name phasersweep
* @synonyms phs
* @param {number | Pattern} phasersweep most useful values are between 0 and 4000
* @example
* n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
* .phaser(2).phasersweep("<800 2000 4000>")
*
*/
['phasersweep', 'phs'],
/**
* The center frequency of the phaser in HZ. Defaults to 1000
*
* @name phasercenter
* @synonyms phc
* @param {number | Pattern} centerfrequency in HZ
* @example
* n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
* .phaser(2).phasercenter("<800 2000 4000>")
*
*/
['phasercenter', 'phc'],
/**
* The amount the signal is affected by the phaser effect. Defaults to 0.75
*
* @name phaserdepth
* @synonyms phd
* @param {number | Pattern} depth number between 0 and 1
* @example
* n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
* .phaser(2).phaserdepth("<0 .5 .75 1>")
*
*/
['phaserdepth', 'phd', 'phasdp'], // also a superdirt control
/**
* choose the channel the pattern is sent to in superdirt
*
@@ -867,7 +923,12 @@ const generic_params = [
*
*/
['lsize'],
// label for pianoroll
/**
* Sets the displayed text for an event on the pianoroll
*
* @name label
* @param {string} label text to display
*/
['activeLabel'],
[['label', 'activeLabel']],
// ['lfo'],
@@ -1031,7 +1092,8 @@ const generic_params = [
*/
['roomfade', 'rfade'],
/**
* Sets the sample to use as an impulse response for the reverb. * * @name iresponse
* Sets the sample to use as an impulse response for the reverb.
* @name iresponse
* @param {string | Pattern} sample to use as an impulse response
* @synonyms ir
* @example
@@ -1169,9 +1231,6 @@ const generic_params = [
*/
['tremolodepth', 'tremdp'],
['tremolorate', 'tremr'],
// TODO: doesn't seem to do anything
['phaserdepth', 'phasdp'],
['phaserrate', 'phasr'],
['fshift'],
['fshiftnote'],
+1 -1
View File
@@ -146,7 +146,7 @@ export class Drawer {
);
}
invalidate(scheduler = this.scheduler) {
if (!scheduler) {
if (!scheduler || !scheduler.pattern) {
return;
}
this.scheduler = scheduler;
+2 -1
View File
@@ -31,7 +31,8 @@
},
"homepage": "https://strudel.cc",
"dependencies": {
"fraction.js": "^4.2.0"
"fraction.js": "^4.2.0",
"nanostores": "^0.8.1"
},
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
"devDependencies": {
+4 -2
View File
@@ -9,7 +9,7 @@ import Fraction from './fraction.mjs';
import Hap from './hap.mjs';
import State from './state.mjs';
import { unionWithObj } from './value.mjs';
import { char2fraction } from './util.mjs';
import { compose, removeUndefineds, flatten, id, listRange, curry, _mod, numeralArgs, parseNumeral } from './util.mjs';
import drawLine from './drawLine.mjs';
import { logger } from './logger.mjs';
@@ -1903,7 +1903,6 @@ export const when = register('when', function (on, func, pat) {
* "c3 eb3 g3".off(1/8, x=>x.add(7)).note()
*/
export const off = register('off', function (time_pat, func, pat) {
time_pat = char2fraction(time_pat);
return stack(pat, func(pat.late(time_pat)));
});
@@ -2192,6 +2191,9 @@ export const duration = register('duration', function (value, pat) {
/**
* Sets the color of the hap in visualizations like pianoroll or highlighting.
* @name color
* @synonyms colour
* @param {string} color Hexadecimal or CSS color name
*/
// TODO: move this to controls https://github.com/tidalcycles/strudel/issues/288
export const { color, colour } = register(['color', 'colour'], function (color, pat) {
+36 -1
View File
@@ -56,6 +56,40 @@ Pattern.prototype.pianoroll = function (options = {}) {
// this function allows drawing a pianoroll without ties to Pattern.prototype
// it will probably replace the above in the future
/**
* Displays a midi-style piano roll
*
* @name pianoroll
* @param {Object} options Object containing all the optional following parameters as key value pairs:
* @param {integer} cycles number of cycles to be displayed at the same time - defaults to 4
* @param {number} playhead location of the active notes on the time axis - 0 to 1, defaults to 0.5
* @param {boolean} vertical displays the roll vertically - 0 by default
* @param {boolean} labels displays labels on individual notes (see the label function) - 0 by default
* @param {boolean} flipTime reverse the direction of the roll - 0 by default
* @param {boolean} flipValues reverse the relative location of notes on the value axis - 0 by default
* @param {number} overscan lookup X cycles outside of the cycles window to display notes in advance - 1 by default
* @param {boolean} hideNegative hide notes with negative time (before starting playing the pattern) - 0 by default
* @param {boolean} smear notes leave a solid trace - 0 by default
* @param {boolean} fold notes takes the full value axis width - 0 by default
* @param {string} active hexadecimal or CSS color of the active notes - defaults to #FFCA28
* @param {string} inactive hexadecimal or CSS color of the inactive notes - defaults to #7491D2
* @param {string} background hexadecimal or CSS color of the background - defaults to transparent
* @param {string} playheadColor hexadecimal or CSS color of the line representing the play head - defaults to white
* @param {boolean} fill notes are filled with color (otherwise only the label is displayed) - 0 by default
* @param {boolean} fillActive active notes are filled with color - 0 by default
* @param {boolean} stroke notes are shown with colored borders - 0 by default
* @param {boolean} strokeActive active notes are shown with colored borders - 0 by default
* @param {boolean} hideInactive only active notes are shown - 0 by default
* @param {boolean} colorizeInactive use note color for inactive notes - 1 by default
* @param {string} fontFamily define the font used by notes labels - defaults to 'monospace'
* @param {integer} minMidi minimum note value to display on the value axis - defaults to 10
* @param {integer} maxMidi maximum note value to display on the value axis - defaults to 90
* @param {boolean} autorange automatically calculate the minMidi and maxMidi parameters - 0 by default
*
* @example
* note("C2 A2 G2").euclid(5,8).s('piano').clip(1).color('salmon').pianoroll({vertical:1, labels:1})
*/
export function pianoroll({
time,
haps,
@@ -86,6 +120,7 @@ export function pianoroll({
hideInactive = 0,
colorizeInactive = 1,
fontFamily,
clear = true,
ctx,
} = {}) {
const w = ctx.canvas.width;
@@ -131,7 +166,7 @@ export function pianoroll({
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
ctx.fillStyle = background;
ctx.globalAlpha = 1; // reset!
if (!smear) {
if (clear && !smear) {
ctx.clearRect(0, 0, w, h);
ctx.fillRect(0, 0, w, h);
}
+34 -2
View File
@@ -4,6 +4,22 @@ import { logger } from './logger.mjs';
import { setTime } from './time.mjs';
import { evalScope } from './evaluate.mjs';
import { register } from './pattern.mjs';
import { atom, computed } from 'nanostores';
export const $replstate = atom({
schedulerError: undefined,
evalError: undefined,
code: '// LOADING',
activeCode: '// LOADING',
pattern: undefined,
miniLocations: [],
widgets: [],
pending: true,
started: false,
});
export const $repldirty = computed($replstate, (s) => s.code !== s.activeCode);
export const setReplState = (key, value) => $replstate.set({ ...$replstate.get(), [key]: value });
export function repl({
interval,
@@ -22,7 +38,10 @@ export function repl({
onTrigger: getTrigger({ defaultOutput, getTime }),
onError: onSchedulerError,
getTime,
onToggle,
onToggle: (started) => {
setReplState('started', started);
onToggle?.(started);
},
});
let playPatterns = [];
const setPattern = (pattern, autostart = true) => {
@@ -35,6 +54,8 @@ export function repl({
throw new Error('no code to evaluate');
}
try {
setReplState('code', code);
setReplState('pending', true);
await beforeEval?.({ code });
playPatterns = [];
let { pattern, meta } = await _evaluate(code, transpiler);
@@ -43,17 +64,27 @@ export function repl({
}
logger(`[eval] code updated`);
setPattern(pattern, autostart);
setReplState('miniLocations', meta?.miniLocations || []);
setReplState('widgets', meta?.widgets || []);
setReplState('activeCode', code);
setReplState('pattern', pattern);
setReplState('evalError', undefined);
setReplState('schedulerError', undefined);
setReplState('pending', false);
afterEval?.({ code, pattern, meta });
return pattern;
} catch (err) {
// console.warn(`[repl] eval error: ${err.message}`);
logger(`[eval] error: ${err.message}`, 'error');
setReplState('evalError', err);
setReplState('pending', false);
onEvalError?.(err);
}
};
const stop = () => scheduler.stop();
const start = () => scheduler.start();
const pause = () => scheduler.pause();
const toggle = () => scheduler.toggle();
const setCps = (cps) => scheduler.setCps(cps);
const setCpm = (cpm) => scheduler.setCps(cpm / 60);
@@ -87,7 +118,8 @@ export function repl({
setcpm: setCpm,
});
return { scheduler, evaluate, start, stop, pause, setCps, setPattern };
const setCode = (c) => setReplState('code', c);
return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle };
}
export const getTrigger =
+3 -3
View File
@@ -67,9 +67,9 @@ Pattern.prototype.spiral = function (options = {}) {
// logSpiral = true,
} = options;
function spiral({ ctx, time, haps, drawTime }) {
function spiral({ ctx, time, haps, drawTime, clear }) {
const [w, h] = [ctx.canvas.width, ctx.canvas.height];
ctx.clearRect(0, 0, w * 2, h * 2);
clear && ctx.clearRect(0, 0, w * 2, h * 2);
const [cx, cy] = [w / 2, h / 2];
const settings = {
margin: size / stretch,
@@ -114,5 +114,5 @@ Pattern.prototype.spiral = function (options = {}) {
});
}
return this.onPaint((ctx, time, haps, drawTime) => spiral({ ctx, time, haps, drawTime }));
return this.onPaint((ctx, time, haps, drawTime, options) => spiral({ ctx, time, haps, drawTime, ...options }));
};
-20
View File
@@ -1,5 +1,3 @@
import Fraction from './fraction.mjs';
/*
util.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/util.mjs>
@@ -276,21 +274,3 @@ export const sol2note = (n, notation = 'letters') => {
const oct = Math.floor(n / 12) - 1;
return note + oct;
};
const fractionCharacters = {
w: Fraction(1),
h: Fraction(0.5),
q: Fraction(0.25),
e: Fraction(0.125),
s: Fraction(0.0625),
t: Fraction(1 / 3),
f: Fraction(0.2),
x: Fraction(1 / 6),
};
const validFractionCharacters = Object.keys(fractionCharacters);
export const char2fraction = (maybeChar) => {
if (typeof maybeChar === 'string' && validFractionCharacters.includes(maybeChar)) {
return fractionCharacters[maybeChar];
}
return maybeChar;
};
+1 -1
View File
@@ -137,7 +137,7 @@ export async function loadOrc(url) {
export const csoundm = register('csoundm', (instrument, pat) => {
let p1 = instrument;
if (typeof instrument === 'string') {
p1 = `"{instrument}"`;
p1 = `"${instrument}"`;
}
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
return pat.onTrigger((tidal_time, hap) => {
+23 -28
View File
@@ -1,5 +1,5 @@
import { controls, evalScope } from '@strudel.cycles/core';
import { CodeMirror, useHighlighting, useKeydown, useStrudel, flash } from '@strudel.cycles/react';
import { CodeMirror, useHighlighting, useStrudel, flash } from '@strudel.cycles/react';
import {
getAudioContext,
initAudioOnFirstClick,
@@ -93,32 +93,6 @@ function App() {
});
const error = evalError || schedulerError;
useKeydown(
useCallback(
async (e) => {
if (e.ctrlKey || e.altKey) {
if (e.code === 'Enter') {
e.preventDefault();
flash(view);
await evaluate(code);
if (e.shiftKey) {
panic();
scheduler.stop();
scheduler.start();
}
if (!scheduler.started) {
scheduler.start();
}
} else if (e.code === 'Period') {
scheduler.stop();
panic();
e.preventDefault();
}
}
},
[scheduler, evaluate, view, code],
),
);
return (
<div>
<nav className="z-[12] w-full flex justify-center fixed bottom-0">
@@ -136,7 +110,28 @@ function App() {
</div>
{error && <p>error {error.message}</p>}
</nav>
<CodeMirror value={code} onChange={setCode} onViewChanged={setView} />
<CodeMirror
value={code}
onChange={setCode}
onViewChanged={setView}
onEvaluate={async () => {
flash(view);
await evaluate(code);
if (!scheduler.started) {
scheduler.start();
}
}}
onReEvaluate={async () => {
await evaluate(code);
panic();
scheduler.stop();
scheduler.start();
}}
onStop={() => {
scheduler.stop();
panic();
}}
/>
</div>
);
}
+47 -4
View File
@@ -30,6 +30,10 @@ export default function CodeMirror({
onViewChanged,
onSelectionChange,
onDocChange,
onEvaluate,
onReEvaluate,
onPanic,
onStop,
theme,
keybindings,
isLineNumbersDisplayed,
@@ -95,19 +99,58 @@ export default function CodeMirror({
} else {
_extensions.push(autocompletion({ override: [] }));
}
if (isTooltipEnabled) {
_extensions.push(strudelTooltip);
}
_extensions.push([keymap.of({})]);
//_extensions.push([keymap.of({})]);
if (isLineWrappingEnabled) {
_extensions.push(EditorView.lineWrapping);
}
_extensions.push(
keymap.of([
{
key: 'Ctrl-Enter',
run: () => onEvaluate?.(),
},
{
key: 'Alt-Enter',
run: () => onEvaluate?.(),
},
{
key: 'Ctrl-.',
run: () => onStop?.(),
},
{
key: 'Alt-.',
run: (_, e) => {
e.preventDefault();
onStop?.();
},
},
{
key: 'Ctrl-Shift-.',
run: () => (onPanic ? onPanic() : onStop?.()),
},
{
key: 'Ctrl-Shift-Enter',
run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()),
},
]),
);
return _extensions;
}, [keybindings, isAutoCompletionEnabled, isTooltipEnabled, isLineWrappingEnabled]);
}, [
keybindings,
isAutoCompletionEnabled,
isTooltipEnabled,
isLineWrappingEnabled,
onEvaluate,
onReEvaluate,
onStop,
onPanic,
]);
const basicSetup = useMemo(() => ({ lineNumbers: isLineNumbersDisplayed }), [isLineNumbersDisplayed]);
+5 -22
View File
@@ -10,7 +10,6 @@ import { Icon } from './Icon';
import './style.css';
import { logger } from '@strudel.cycles/core';
import useEvent from '../hooks/useEvent.mjs';
import useKeydown from '../hooks/useKeydown.mjs';
const getTime = () => getAudioContext().currentTime;
@@ -92,27 +91,6 @@ export function MiniRepl({
getTime: () => scheduler.now(),
});
// keyboard shortcuts
useKeydown(
useCallback(
async (e) => {
if (view?.hasFocus) {
if (e.ctrlKey || e.altKey) {
if (e.code === 'Enter') {
e.preventDefault();
flash(view);
await activateCode();
} else if (e.key === '.' || e.code === 'Period') {
stop();
e.preventDefault();
}
}
}
},
[activateCode, stop, view],
),
);
const [log, setLog] = useState([]);
useLogger(
useCallback((e) => {
@@ -164,6 +142,11 @@ export function MiniRepl({
fontSize={fontSize}
keybindings={keybindings}
isLineNumbersDisplayed={isLineNumbersDisplayed}
onEvaluate={() => {
flash(view);
activateCode();
}}
onStop={() => stop()}
/>
)}
{error && <div className="text-right p-1 text-md text-red-200">{error.message}</div>}
+9 -9
View File
@@ -6,23 +6,23 @@ This program is free software: you can redistribute it and/or modify it under th
import { Pattern, isPattern } from '@strudel.cycles/core';
var writeMessage;
var writeMessagers = {};
var choosing = false;
export async function getWriter(br = 38400) {
export async function getWriter(name, br) {
if (choosing) {
return;
}
choosing = true;
if (writeMessage) {
return writeMessage;
if (name in writeMessagers) {
return writeMessagers[name];
}
if ('serial' in navigator) {
const port = await navigator.serial.requestPort();
await port.open({ baudRate: br });
const encoder = new TextEncoder();
const writer = port.writable.getWriter();
writeMessage = function (message, chk) {
writeMessagers[name] = function (message, chk) {
const encoded = encoder.encode(message);
if (!chk) {
writer.write(encoded);
@@ -63,10 +63,10 @@ function crc16(data) {
return crc & 0xffff;
}
Pattern.prototype.serial = function (br = 38400, sendcrc = false, singlecharids = false) {
Pattern.prototype.serial = function (br = 115200, sendcrc = false, singlecharids = false, name = 'default') {
return this.withHap((hap) => {
if (!writeMessage) {
getWriter(br);
if (!(name in writeMessagers)) {
getWriter(name, br);
}
const onTrigger = (time, hap, currentTime) => {
var message = '';
@@ -108,7 +108,7 @@ Pattern.prototype.serial = function (br = 38400, sendcrc = false, singlecharids
const offset = (time - currentTime + latency) * 1000;
window.setTimeout(function () {
writeMessage(message, chk);
writeMessagers[name](message, chk);
}, offset);
};
return hap.setContext({ ...hap.context, onTrigger, dominantTrigger: true });
+4
View File
@@ -67,6 +67,10 @@ superdough({ s: 'bd', delay: 0.5 }, 0, 1);
- `crush`: amplitude bit crusher using given number of bits
- `shape`: distortion effect from 0 (none) to 1 (full). might get loud!
- `pan`: stereo panning from 0 (left) to 1 (right)
- `phaser`: sets the speed of the modulation
- `phaserdepth`: the amount the signal is affected by the phaser effect.
- `phasersweep`: the frequency sweep range of the lfo for the phaser effect.
- `phasercenter`: the amount the signal is affected by the phaser effect.
- `vowel`: vowel filter. possible values: "a", "e", "i", "o", "u"
- `delay`: delay mix
- `delayfeedback`: delay feedback
+16 -1
View File
@@ -57,7 +57,6 @@ export const getSampleBufferSource = async (s, n, note, speed, freq, bank, resol
const bufferSource = ac.createBufferSource();
bufferSource.buffer = buffer;
const playbackRate = 1.0 * Math.pow(2, transpose / 12);
// bufferSource.playbackRate.value = Math.pow(2, transpose / 12);
bufferSource.playbackRate.value = playbackRate;
return bufferSource;
};
@@ -240,6 +239,8 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
begin = 0,
loopEnd = 1,
end = 1,
vib,
vibmod = 0.5,
} = value;
// load sample
if (speed === 0) {
@@ -255,6 +256,19 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank, resolveUrl);
// vibrato
let vibratoOscillator;
if (vib > 0) {
vibratoOscillator = getAudioContext().createOscillator();
vibratoOscillator.frequency.value = vib;
const gain = getAudioContext().createGain();
// Vibmod is the amount of vibrato, in semitones
gain.gain.value = vibmod * 100;
vibratoOscillator.connect(gain);
gain.connect(bufferSource.detune);
vibratoOscillator.start(0);
}
// asny stuff above took too long?
if (ac.currentTime > t) {
logger(`[sampler] still loading sound "${s}:${n}"`, 'highlight');
@@ -286,6 +300,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
envelope.connect(out);
bufferSource.onended = function () {
bufferSource.disconnect();
vibratoOscillator?.stop();
envelope.disconnect();
out.disconnect();
onended();
+54
View File
@@ -112,6 +112,48 @@ function getDelay(orbit, delaytime, delayfeedback, t) {
return delays[orbit];
}
// each orbit will have its own lfo
const phaserLFOs = {};
function getPhaser(orbit, t, speed = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) {
//gain
const ac = getAudioContext();
const lfoGain = ac.createGain();
lfoGain.gain.value = sweep;
//LFO
if (phaserLFOs[orbit] == null) {
phaserLFOs[orbit] = ac.createOscillator();
phaserLFOs[orbit].frequency.value = speed;
phaserLFOs[orbit].type = 'sine';
phaserLFOs[orbit].start();
}
phaserLFOs[orbit].connect(lfoGain);
if (phaserLFOs[orbit].frequency.value != speed) {
phaserLFOs[orbit].frequency.setValueAtTime(speed, t);
}
//filters
const numStages = 2; //num of filters in series
let fOffset = 0;
const filterChain = [];
for (let i = 0; i < numStages; i++) {
const filter = ac.createBiquadFilter();
filter.type = 'notch';
filter.gain.value = 1;
filter.frequency.value = centerFrequency + fOffset;
filter.Q.value = 2 - Math.min(Math.max(depth * 2, 0), 1.9);
lfoGain.connect(filter.detune);
fOffset += 282;
if (i > 0) {
filterChain[i - 1].connect(filter);
}
filterChain.push(filter);
}
return filterChain[filterChain.length - 1];
}
let reverbs = {};
let hasChanged = (now, before) => now !== undefined && now !== before;
@@ -226,6 +268,12 @@ export const superdough = async (value, deadline, hapDuration) => {
bpsustain = 1,
bprelease = 0.01,
bandq = 1,
//phaser
phaser,
phaserdepth = 0.75,
phasersweep,
phasercenter,
//
coarse,
crush,
@@ -260,6 +308,7 @@ export const superdough = async (value, deadline, hapDuration) => {
if (bank && s) {
s = `${bank}_${s}`;
}
// get source AudioNode
let sourceNode;
if (source) {
@@ -377,6 +426,11 @@ export const superdough = async (value, deadline, hapDuration) => {
panner.pan.value = 2 * pan - 1;
chain.push(panner);
}
// phaser
if (phaser !== undefined && phaserdepth > 0) {
const phaserFX = getPhaser(orbit, t, phaser, phaserdepth, phasercenter, phasersweep);
chain.push(phaserFX);
}
// last gain
const post = gainNode(postgain);
+88 -1
View File
@@ -71,6 +71,9 @@ importers:
packages/codemirror:
dependencies:
'@codemirror/autocomplete':
specifier: ^6.6.0
version: 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':
specifier: ^6.2.4
version: 6.2.4
@@ -80,6 +83,9 @@ importers:
'@codemirror/language':
specifier: ^6.6.0
version: 6.6.0
'@codemirror/search':
specifier: ^6.0.0
version: 6.5.4
'@codemirror/state':
specifier: ^6.2.0
version: 6.2.0
@@ -89,9 +95,27 @@ importers:
'@lezer/highlight':
specifier: ^1.1.4
version: 1.1.4
'@replit/codemirror-emacs':
specifier: ^6.0.1
version: 6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/search@6.5.4)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
'@replit/codemirror-vim':
specifier: ^6.0.14
version: 6.0.14(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/search@6.5.4)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
'@replit/codemirror-vscode-keymap':
specifier: ^6.0.2
version: 6.0.2(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/lint@6.1.0)(@codemirror/search@6.5.4)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
'@strudel.cycles/core':
specifier: workspace:*
version: link:../core
'@uiw/codemirror-themes':
specifier: ^4.19.16
version: 4.19.16(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
'@uiw/codemirror-themes-all':
specifier: ^4.19.16
version: 4.19.16(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
react-dom:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
devDependencies:
vite:
specifier: ^4.3.3
@@ -102,6 +126,9 @@ importers:
fraction.js:
specifier: ^4.2.0
version: 4.2.0
nanostores:
specifier: ^0.8.1
version: 0.8.1
devDependencies:
vite:
specifier: ^4.3.3
@@ -2475,6 +2502,14 @@ packages:
crelt: 1.0.5
dev: false
/@codemirror/search@6.5.4:
resolution: {integrity: sha512-YoTrvjv9e8EbPs58opjZKyJ3ewFrVSUzQ/4WXlULQLSDDr1nGPJ67mMXFNNVYwdFhybzhrzrtqgHmtpJwIF+8g==}
dependencies:
'@codemirror/state': 6.2.0
'@codemirror/view': 6.10.0
crelt: 1.0.5
dev: false
/@codemirror/state@6.2.0:
resolution: {integrity: sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==}
dev: false
@@ -3963,6 +3998,22 @@ packages:
'@codemirror/view': 6.10.0
dev: false
/@replit/codemirror-emacs@6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/search@6.5.4)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0):
resolution: {integrity: sha512-2WYkODZGH1QVAXWuOxTMCwktkoZyv/BjYdJi2A5w4fRrmOQFuIACzb6pO9dgU3J+Pm2naeiX2C8veZr/3/r6AA==}
peerDependencies:
'@codemirror/autocomplete': ^6.0.2
'@codemirror/commands': ^6.0.0
'@codemirror/search': ^6.0.0
'@codemirror/state': ^6.0.1
'@codemirror/view': ^6.3.0
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/commands': 6.2.4
'@codemirror/search': 6.5.4
'@codemirror/state': 6.2.0
'@codemirror/view': 6.10.0
dev: false
/@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==}
peerDependencies:
@@ -3979,6 +4030,22 @@ packages:
'@codemirror/view': 6.10.0
dev: false
/@replit/codemirror-vim@6.0.14(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/search@6.5.4)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0):
resolution: {integrity: sha512-wwhqhvL76FdRTdwfUWpKCbv0hkp2fvivfMosDVlL/popqOiNLtUhL02ThgHZH8mus/NkVr5Mj582lyFZqQrjOA==}
peerDependencies:
'@codemirror/commands': ^6.0.0
'@codemirror/language': ^6.1.0
'@codemirror/search': ^6.2.0
'@codemirror/state': ^6.0.1
'@codemirror/view': ^6.0.3
dependencies:
'@codemirror/commands': 6.2.4
'@codemirror/language': 6.6.0
'@codemirror/search': 6.5.4
'@codemirror/state': 6.2.0
'@codemirror/view': 6.10.0
dev: false
/@replit/codemirror-vscode-keymap@6.0.2(@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-j45qTwGxzpsv82lMD/NreGDORFKSctMDVkGRopaP+OrzSzv+pXDQuU3LnFvKpasyjVT0lf+PKG1v2DSCn/vxxg==}
peerDependencies:
@@ -3999,6 +4066,26 @@ packages:
'@codemirror/view': 6.10.0
dev: false
/@replit/codemirror-vscode-keymap@6.0.2(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/lint@6.1.0)(@codemirror/search@6.5.4)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0):
resolution: {integrity: sha512-j45qTwGxzpsv82lMD/NreGDORFKSctMDVkGRopaP+OrzSzv+pXDQuU3LnFvKpasyjVT0lf+PKG1v2DSCn/vxxg==}
peerDependencies:
'@codemirror/autocomplete': ^6.0.0
'@codemirror/commands': ^6.0.0
'@codemirror/language': ^6.0.0
'@codemirror/lint': ^6.0.0
'@codemirror/search': ^6.0.0
'@codemirror/state': ^6.0.0
'@codemirror/view': ^6.0.0
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/commands': 6.2.4
'@codemirror/language': 6.6.0
'@codemirror/lint': 6.1.0
'@codemirror/search': 6.5.4
'@codemirror/state': 6.2.0
'@codemirror/view': 6.10.0
dev: false
/@rollup/plugin-babel@5.3.1(@babel/core@7.23.2)(rollup@2.79.1):
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
engines: {node: '>= 10.0.0'}
@@ -6120,7 +6207,7 @@ packages:
'@codemirror/commands': 6.2.4
'@codemirror/language': 6.6.0
'@codemirror/lint': 6.1.0
'@codemirror/search': 6.2.3
'@codemirror/search': 6.5.4
'@codemirror/state': 6.2.0
'@codemirror/view': 6.10.0
transitivePeerDependencies:
+194
View File
@@ -2439,6 +2439,19 @@ exports[`runs examples > example "irand" example index 0 1`] = `
]
`;
exports[`runs examples > example "iresponse" example index 0 1`] = `
[
"[ 0/1 → 1/2 | s:bd room:0.8 ir:shaker_large i:0 ]",
"[ 1/2 → 1/1 | s:sd room:0.8 ir:shaker_large i:0 ]",
"[ 1/1 → 3/2 | s:bd room:0.8 ir:shaker_large i:2 ]",
"[ 3/2 → 2/1 | s:sd room:0.8 ir:shaker_large i:2 ]",
"[ 2/1 → 5/2 | s:bd room:0.8 ir:shaker_large i:0 ]",
"[ 5/2 → 3/1 | s:sd room:0.8 ir:shaker_large i:0 ]",
"[ 3/1 → 7/2 | s:bd room:0.8 ir:shaker_large i:2 ]",
"[ 7/2 → 4/1 | s:sd room:0.8 ir:shaker_large i:2 ]",
]
`;
exports[`runs examples > example "iter" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:A3 ]",
@@ -3306,6 +3319,187 @@ exports[`runs examples > example "perlin" example index 0 1`] = `
]
`;
exports[`runs examples > example "phaser" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:1 ]",
"[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:1 ]",
"[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:1 ]",
"[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:1 ]",
"[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:1 ]",
"[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:1 ]",
"[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:1 ]",
"[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:1 ]",
"[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 ]",
"[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 ]",
"[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 ]",
"[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 ]",
"[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 ]",
"[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 ]",
"[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 ]",
"[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 ]",
"[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:4 ]",
"[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:4 ]",
"[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:4 ]",
"[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:4 ]",
"[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:4 ]",
"[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:4 ]",
"[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:4 ]",
"[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:4 ]",
"[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:8 ]",
"[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:8 ]",
"[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:8 ]",
"[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:8 ]",
"[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:8 ]",
"[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:8 ]",
"[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:8 ]",
"[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:8 ]",
]
`;
exports[`runs examples > example "phasercenter" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
"[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
"[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
"[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
]
`;
exports[`runs examples > example "phaserdepth" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
"[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
"[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
"[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
"[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
"[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
"[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
"[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
"[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
"[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
"[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
]
`;
exports[`runs examples > example "phasersweep" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
"[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
"[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
"[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
]
`;
exports[`runs examples > example "pianoroll" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:C2 s:piano clip:1 ]",
"[ (1/4 → 1/3) ⇝ 3/8 | note:C2 s:piano clip:1 ]",
"[ 1/4 ⇜ (1/3 → 3/8) | note:A2 s:piano clip:1 ]",
"[ 3/8 → 1/2 | note:A2 s:piano clip:1 ]",
"[ (5/8 → 2/3) ⇝ 3/4 | note:A2 s:piano clip:1 ]",
"[ 5/8 ⇜ (2/3 → 3/4) | note:G2 s:piano clip:1 ]",
"[ 3/4 → 7/8 | note:G2 s:piano clip:1 ]",
"[ 1/1 → 9/8 | note:C2 s:piano clip:1 ]",
"[ (5/4 → 4/3) ⇝ 11/8 | note:C2 s:piano clip:1 ]",
"[ 5/4 ⇜ (4/3 → 11/8) | note:A2 s:piano clip:1 ]",
"[ 11/8 → 3/2 | note:A2 s:piano clip:1 ]",
"[ (13/8 → 5/3) ⇝ 7/4 | note:A2 s:piano clip:1 ]",
"[ 13/8 ⇜ (5/3 → 7/4) | note:G2 s:piano clip:1 ]",
"[ 7/4 → 15/8 | note:G2 s:piano clip:1 ]",
"[ 2/1 → 17/8 | note:C2 s:piano clip:1 ]",
"[ (9/4 → 7/3) ⇝ 19/8 | note:C2 s:piano clip:1 ]",
"[ 9/4 ⇜ (7/3 → 19/8) | note:A2 s:piano clip:1 ]",
"[ 19/8 → 5/2 | note:A2 s:piano clip:1 ]",
"[ (21/8 → 8/3) ⇝ 11/4 | note:A2 s:piano clip:1 ]",
"[ 21/8 ⇜ (8/3 → 11/4) | note:G2 s:piano clip:1 ]",
"[ 11/4 → 23/8 | note:G2 s:piano clip:1 ]",
"[ 3/1 → 25/8 | note:C2 s:piano clip:1 ]",
"[ (13/4 → 10/3) ⇝ 27/8 | note:C2 s:piano clip:1 ]",
"[ 13/4 ⇜ (10/3 → 27/8) | note:A2 s:piano clip:1 ]",
"[ 27/8 → 7/2 | note:A2 s:piano clip:1 ]",
"[ (29/8 → 11/3) ⇝ 15/4 | note:A2 s:piano clip:1 ]",
"[ 29/8 ⇜ (11/3 → 15/4) | note:G2 s:piano clip:1 ]",
"[ 15/4 → 31/8 | note:G2 s:piano clip:1 ]",
]
`;
exports[`runs examples > example "pick" example index 0 1`] = `
[
"[ 0/1 → 1/2 | note:g ]",
+5 -23
View File
@@ -1,32 +1,14 @@
import { evalScope, controls, noteToMidi } from '@strudel.cycles/core';
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import { noteToMidi } from '@strudel.cycles/core';
import { useEffect, useState } from 'react';
import { prebake } from '../repl/prebake';
import { themes, settings } from '../repl/themes.mjs';
import { themes } from '../repl/themes.mjs';
import './MiniRepl.css';
import { useSettings } from '../settings.mjs';
import Claviature from '@components/Claviature';
let modules;
let init;
if (typeof window !== 'undefined') {
modules = evalScope(
controls,
import('@strudel.cycles/core'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/midi'),
import('@strudel.cycles/xen'),
import('@strudel.cycles/webaudio'),
import('@strudel.cycles/osc'),
import('@strudel.cycles/csound'),
import('@strudel.cycles/soundfonts'),
import('@strudel/hydra'),
);
}
if (typeof window !== 'undefined') {
initAudioOnFirstClick();
prebake();
init = prebake();
}
export function MiniRepl({
@@ -46,7 +28,7 @@ export function MiniRepl({
useEffect(() => {
// we have to load this package on the client
// because codemirror throws an error on the server
Promise.all([import('@strudel.cycles/react'), modules])
Promise.all([import('@strudel.cycles/react'), init])
.then(([res]) => setRepl(() => res.MiniRepl))
.catch((err) => console.error(err));
}, []);
+23 -23
View File
@@ -25,7 +25,7 @@ import Box from '@components/Box.astro';
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 🚪
- Ändere `lpf` in 200. Hörst du, wie der Bass dumpfer klingt? Es klingt so, als würde die Musik hinter einer geschlossenen Tür spielen 🚪
- Lass uns nun die Tür öffnen: Ändere `lpf` in 5000. Der Klang wird dadurch viel heller und schärfer ✨🪩
</Box>
@@ -42,9 +42,9 @@ lpf = **l**ow **p**ass **f**ilter
<Box>
- Füg noch mehr `lpf` Werte hinzu
- Das pattern in `lpf` ändert nicht den Rhythmus der Bassline
- Das Pattern in `lpf` ändert nicht den Rhythmus der Basslinie
Später sehen wir wie man mit Wellenformen Dinge automatisieren kann.
Später sehen wir, wie man mit Wellenformen Dinge automatisieren kann.
</Box>
@@ -73,7 +73,7 @@ Später sehen wir wie man mit Wellenformen Dinge automatisieren kann.
Bei Rhythmen ist die Dynamik (= Veränderungen der Lautstärke) sehr wichtig.
- Entferne `.gain(...)` und achte darauf wie es viel flacher klingt.
- Entferne `.gain(...)` und achte darauf, wie es viel flacher klingt.
- Mach es rückgängig (strg+z dann strg+enter)
</Box>
@@ -99,13 +99,13 @@ Lass uns die obigen Beispiele kombinieren:
<Box>
Versuche die einzelnen Teile innerhalb `stack` zu erkennen, schau dir an wie die Kommas gesetzt sind.
Versuche die einzelnen Teile innerhalb von `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
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**
**Den Sound formen mit ADSR-Hüllkurve**
<MiniRepl
hideHeader
@@ -120,14 +120,14 @@ Die 3 Teile (Drums, Bass, Akkorde) sind genau wie vorher, nur in einem `stack`,
<Box>
Versuche herauszufinden was die Zahlen machen. Probier folgendes:
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?
Kannst du erraten, was die einzelnen Werte machen?
</Box>
@@ -142,7 +142,7 @@ Kannst du erraten was die einzelnen Werte machen?
</QA>
**adsr Kurznotation**
**adsr-Kurznotation**
<MiniRepl
hideHeader
@@ -169,9 +169,9 @@ Kannst du erraten was die einzelnen Werte machen?
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:.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?
Was passiert, wenn du `.delay(".8:.06:.8")` schreibst? Kannst du erraten, was die dritte Zahl macht?
</Box>
@@ -181,7 +181,7 @@ Was passiert wenn du `.delay(".8:.06:.8")` schreibst? Kannst du erraten was die
- a: Lautstärke des Delays
- b: Verzögerungszeit
- c: Feedback (je kleiner desto schneller verschwindet das Delay)
- c: Feedback (je kleiner, desto schneller verschwindet das Delay)
</QA>
@@ -203,7 +203,7 @@ Füg auch ein Delay hinzu!
</Box>
**kleiner dub tune**
**kleiner Dub-Tune**
<MiniRepl
hideHeader
@@ -238,7 +238,7 @@ Für echten Dub fehlt noch der Bass:
<Box>
Füg `.hush()` ans ende eines Patterns im stack...
Füg `.hush()` ans Ende eines Patterns im stack...
</Box>
@@ -258,25 +258,25 @@ Füg `.hush()` ans ende eines Patterns im stack...
**fast and slow = schnell und langsam**
Mit `fast` und `slow` kann man das tempo eines patterns außerhalb der Mini-Notation ändern:
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`.
Ändere den `slow`-Wert. Ersetze `slow` durch `fast`.
Was passiert wenn du den Wert automatisierst? z.b. `.fast("<1 [2 4]>")` ?
Was passiert, wenn du den Wert automatisierst? z.b. `.fast("<1 [2 4]>")` ?
</Box>
Übrigens, innerhalb der Mini-Notation, `fast` ist `*` und `slow` ist `/`.
Ü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:
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} />
@@ -296,7 +296,7 @@ Signale bewegen sich standardmäßig zwischen 0 und 1. Wir können das mit `rang
<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)
`range` ist nützlich wenn wir Funktionen mit einem anderen Wertebereich als 0 und 1 automatisieren wollen (z.b. `lpf`)
<Box>
@@ -322,7 +322,7 @@ Die ganze Automation braucht nun 8 cycle bis sie sich wiederholt.
## Rückblick
| name | example |
| Name | Beispiel |
| ----- | -------------------------------------------------------------------------------------------------- |
| lpf | <MiniRepl hideHeader client:visible tune={`note("c2 c3").s("sawtooth").lpf("<400 2000>")`} /> |
| vowel | <MiniRepl hideHeader client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
@@ -333,4 +333,4 @@ Die ganze Automation braucht nun 8 cycle bis sie sich wiederholt.
| 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).
Lass uns nun die für Tidal typischen [Pattern-Effekte anschauen](/de/workshop/pattern-effects).
@@ -277,7 +277,7 @@ Das haben wir bisher gelernt:
| 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 mit Apostrophen umgebene Mini-Notation benutzt man normalerweise in einer sogenannten Funktion.
Die folgenden Funktionen haben wir bereits gesehen:
| Name | Description | Example |
@@ -1,5 +1,5 @@
---
title: Pattern Effekte
title: Pattern-Effekte
layout: ../../../layouts/MainLayout.astro
---
@@ -7,11 +7,11 @@ import { MiniRepl } from '@src/docs/MiniRepl';
import Box from '@components/Box.astro';
import QA from '@components/QA';
# Pattern Effekte
# 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.
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.
In diesem Kapitel beschäftigen wir uns mit Funktionen die weniger herkömmlich oder auch einzigartig sind.
**rev = rückwärts abspielen**
@@ -21,7 +21,7 @@ In diesem Kapitel beschäftigen wir uns mit Funktionen die weniger herkömmlich
<MiniRepl hideHeader client:visible tune={`n("0 1 [4 3] 2").sound("jazz").jux(rev)`} />
So würde man das ohne jux schreiben:
So würde man das ohne `jux` schreiben:
<MiniRepl
hideHeader
@@ -32,7 +32,7 @@ So würde man das ohne jux schreiben:
)`}
/>
Lass uns visualisieren was hier passiert:
Lass uns visualisieren, was hier passiert:
<MiniRepl
hideHeader
@@ -54,7 +54,7 @@ Schreibe `//` vor eine der beiden Zeilen im `stack`!
<MiniRepl hideHeader client:visible tune={`note("c2, eb3 g3 [bb3 c4]").sound("piano").slow("1,2,3")`} />
Das hat den gleichen Effekt wie:
Das hat den gleichen Effekt, wie:
<MiniRepl
hideHeader
@@ -161,11 +161,11 @@ Probier `ply` mit einem pattern zu automatisieren, z.b. `"<1 2 1 3>"`
<Box>
In der notation `x=>x.`, das `x` ist das Pattern das wir bearbeiten.
In der Notation `x=>x.`, ist `x` das Pattern, das wir bearbeiten.
</Box>
`off` ist auch nützlich für sounds:
`off` ist auch nützlich für Sounds:
<MiniRepl
hideHeader
@@ -174,10 +174,10 @@ In der notation `x=>x.`, das `x` ist das Pattern das wir bearbeiten.
.off(1/8, x=>x.speed(1.5).gain(.25))`}
/>
| name | description | example |
| Name | Beschreibung | Beispiel |
| ---- | --------------------------------- | ---------------------------------------------------------------------------------------------- |
| 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))`} /> |
| jux | einen Stereo-Kanal modifizieren | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
| add | addiert Zahlen oder Noten | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
| ply | multipliziert jedes Element x mal | <MiniRepl hideHeader client:visible tune={`s("bd sd").ply("<1 2 3>")`} /> |
| off | verzögert eine modifizierte Kopie | <MiniRepl hideHeader client:visible tune={`s("bd sd, hh*4").off(1/8, x=>x.speed(2))`} /> |
+25 -25
View File
@@ -7,19 +7,19 @@ import { MiniRepl } from '../../../docs/MiniRepl';
# Workshop Rückblick
Diese Seite ist eine Auflistung aller im Workshop enthaltenen Funktionen.
Diese Seite ist eine Auflistung aller im Workshop vorgestellten Funktionen.
## Mini Notation
| Concept | Syntax | Example |
| Konzept | Syntax | Beispiel |
| --------------------- | -------- | -------------------------------------------------------------------------------- |
| 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")`} /> |
| Sequenz | space | <MiniRepl hideHeader client:visible tune={`sound("bd bd sn hh")`} /> |
| Sample-Nummer | :x | <MiniRepl hideHeader client:visible tune={`sound("hh:0 hh:1 hh:2 hh:3")`} /> |
| Pausen | ~ | <MiniRepl hideHeader client:visible tune={`sound("metal ~ jazz jazz:1")`} /> |
| Unter-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]]")`} /> |
| Unter-Sequenzen | \[\] | <MiniRepl hideHeader client:visible tune={`sound("bd wind [metal jazz] hh")`} /> |
| Unter-Unter-Sequenzen | \[\[\]\] | <MiniRepl hideHeader client:visible tune={`sound("bd [metal [jazz sn]]")`} /> |
| Schneller | \* | <MiniRepl hideHeader client:visible tune={`sound("bd sn*2 cp*3")`} /> |
| Slow down | \/ | <MiniRepl hideHeader client:visible tune={`note("[c a f e]/2")`} /> |
| Verlangsamen | \/ | <MiniRepl hideHeader client:visible tune={`note("[c a f e]/2")`} /> |
| Parallel | , | <MiniRepl hideHeader client:visible tune={`sound("bd*2, hh*2 [hh oh]")`} /> |
| Alternieren | \<\> | <MiniRepl hideHeader client:visible tune={`note("c <e g>")`} /> |
| Verlängern | @ | <MiniRepl hideHeader client:visible tune={`note("c@3 e")`} /> |
@@ -27,23 +27,23 @@ Diese Seite ist eine Auflistung aller im Workshop enthaltenen Funktionen.
## Sounds
| Name | Description | Example |
| Name | Beschreibung | Beispiel |
| ----- | -------------------------- | ---------------------------------------------------------------------------------- |
| sound | spielt den sound mit namen | <MiniRepl hideHeader client:visible tune={`sound("bd sd")`} /> |
| bank | wählt die soundbank | <MiniRepl hideHeader client:visible tune={`sound("bd sd").bank("RolandTR909")`} /> |
| n | wählt sample mit nummer | <MiniRepl hideHeader client:visible tune={`n("0 1 4 2").sound("jazz")`} /> |
| 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
## Noten
| Name | Description | Example |
| Name | Beschreibung | Beispiel |
| --------- | ---------------------------------- | -------------------------------------------------------------------------------------------- |
| note | wählt note per zahl oder buchstabe | <MiniRepl hideHeader client:visible tune={`note("b g e c").sound("piano")`} /> |
| n + scale | wählt note n in skala | <MiniRepl hideHeader client:visible tune={`n("6 4 2 0").scale("C:minor").sound("piano")`} /> |
| stack | spielt mehrere patterns parallel | <MiniRepl hideHeader client:visible tune={`stack(s("bd sd"),note("c eb g"))`} /> |
| 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
## Audio-Effekte
| name | example |
| Name | Beispiele |
| ----- | -------------------------------------------------------------------------------------------------- |
| lpf | <MiniRepl hideHeader client:visible tune={`note("c2 c3").s("sawtooth").lpf("<400 2000>")`} /> |
| vowel | <MiniRepl hideHeader client:visible tune={`note("c3 eb3 g3").s("sawtooth").vowel("<a e i o>")`} /> |
@@ -54,15 +54,15 @@ Diese Seite ist eine Auflistung aller im Workshop enthaltenen Funktionen.
| 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
## Pattern-Effekte
| name | description | example |
| Name | Beschreibung | Beispiel |
| ---- | --------------------------------- | ---------------------------------------------------------------------------------------------- |
| cpm | tempo in cycles pro minute | <MiniRepl hideHeader client:visible tune={`sound("bd sd").cpm(90)`} /> |
| 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))`} /> |
| jux | einen Stereo-Kanal modifizieren | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6").scale("C:minor").jux(rev)`} /> |
| add | addiert Zahlen oder Noten | <MiniRepl hideHeader client:visible tune={`n("0 2 4 6".add("<0 1 2 1>")).scale("C:minor")`} /> |
| ply | jedes Element schneller machen | <MiniRepl hideHeader client:visible tune={`s("bd sd").ply("<1 2 3>")`} /> |
| off | verzögert eine modifizierte Kopie | <MiniRepl hideHeader client:visible tune={`s("bd sd, hh*4").off(1/8, x=>x.speed(2))`} /> |
+18
View File
@@ -240,3 +240,21 @@ global effects use the same chain for all events of the same orbit:
<JsDoc client:idle name="iresponse" h={0} />
Next, we'll look at strudel's support for [Csound](/learn/csound).
## Phaser
### phaser
<JsDoc client:idle name="phaser" h={0} />
### phaserdepth
<JsDoc client:idle name="phaserdepth" h={0} />
### phasercenter
<JsDoc client:idle name="phasercenter" h={0} />
### phasersweep
<JsDoc client:idle name="phasersweep" h={0} />
+14
View File
@@ -0,0 +1,14 @@
---
import HeadCommon from '../components/HeadCommon.astro';
import VanillaRepl from '../repl/VanillaRepl.astro';
---
<html lang="en" class="dark">
<head>
<HeadCommon />
<title>Strudel Vanilla REPL</title>
</head>
<body class="h-app-height bg-background">
<VanillaRepl />
</body>
</html>
+12
View File
@@ -385,6 +385,8 @@ function SettingsTab({ scheduler }) {
keybindings,
isLineNumbersDisplayed,
isAutoCompletionEnabled,
isFlashEnabled,
isPatternHighlightingEnabled,
isTooltipEnabled,
isLineWrappingEnabled,
fontSize,
@@ -468,6 +470,16 @@ function SettingsTab({ scheduler }) {
onChange={(cbEvent) => settingsMap.setKey('isLineWrappingEnabled', cbEvent.target.checked)}
value={isLineWrappingEnabled}
/>
<Checkbox
label="Enable pattern highlighting"
onChange={(cbEvent) => settingsMap.setKey('isPatternHighlightingEnabled', cbEvent.target.checked)}
value={isPatternHighlightingEnabled}
/>
<Checkbox
label="Enable flashing on update"
onChange={(cbEvent) => settingsMap.setKey('isFlashEnabled', cbEvent.target.checked)}
value={isFlashEnabled}
/>
</FormItem>
<FormItem label="Zen Mode">Try clicking the logo in the top left!</FormItem>
<FormItem label="Reset Settings">
+22 -68
View File
@@ -4,22 +4,21 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { cleanupDraw, cleanupUi, controls, evalScope, getDrawContext, logger } from '@strudel.cycles/core';
import { CodeMirror, cx, flash, useHighlighting, useStrudel, useKeydown } from '@strudel.cycles/react';
import { getAudioContext, initAudioOnFirstClick, resetLoadedSounds, webaudioOutput } from '@strudel.cycles/webaudio';
import { cleanupDraw, cleanupUi, logger, getDrawContext } from '@strudel.cycles/core';
import { CodeMirror, cx, flash, useHighlighting, useStrudel } from '@strudel.cycles/react';
import { getAudioContext, initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio';
import { createClient } from '@supabase/supabase-js';
import { nanoid } from 'nanoid';
import React, { createContext, useCallback, useEffect, useState, useMemo } from 'react';
import './Repl.css';
import { Footer } from './Footer';
import { Header } from './Header';
import { prebake } from './prebake.mjs';
import { prebake, resetSounds } from './prebake.mjs';
import * as tunes from './tunes.mjs';
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
import { themes } from './themes.mjs';
import { settingsMap, useSettings, setLatestCode } from '../settings.mjs';
import Loader from './Loader';
import { settingPatterns } from '../settings.mjs';
import { code2hash, hash2code } from './helpers.mjs';
import { isTauri } from '../tauri.mjs';
import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs';
@@ -35,41 +34,7 @@ const supabase = createClient(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM',
);
let modules = [
import('@strudel.cycles/core'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/xen'),
import('@strudel.cycles/webaudio'),
import('@strudel/codemirror'),
import('@strudel/hydra'),
import('@strudel.cycles/serial'),
import('@strudel.cycles/soundfonts'),
import('@strudel.cycles/csound'),
];
if (isTauri()) {
modules = modules.concat([
import('@strudel/desktopbridge/loggerbridge.mjs'),
import('@strudel/desktopbridge/midibridge.mjs'),
import('@strudel/desktopbridge/oscbridge.mjs'),
]);
} else {
modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]);
}
const modulesLoading = evalScope(
controls, // sadly, this cannot be exported from core direclty
settingPatterns,
...modules,
);
const presets = prebake();
let drawContext, clearCanvas;
if (typeof window !== 'undefined') {
drawContext = getDrawContext();
clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width);
}
const init = prebake();
const getTime = () => getAudioContext().currentTime;
@@ -141,7 +106,7 @@ export function Repl({ embedded = false }) {
getTime,
beforeEval: async () => {
setPending(true);
await modulesLoading;
await init;
cleanupUi();
cleanupDraw();
},
@@ -163,7 +128,7 @@ export function Repl({ embedded = false }) {
window.postMessage('strudel-start');
}
},
drawContext,
drawContext: getDrawContext(),
// drawTime: [0, 6],
paintOptions,
});
@@ -187,29 +152,6 @@ export function Repl({ embedded = false }) {
});
}, []);
// keyboard shortcuts
useKeydown(
useCallback(
async (e) => {
if (e.ctrlKey || e.altKey) {
if (e.code === 'Enter') {
if (getAudioContext().state !== 'running') {
alert('please click play to initialize the audio. you can use shortcuts after that!');
return;
}
e.preventDefault();
flash(view);
await activateCode();
} else if (e.key === '.' || e.code === 'Period') {
stop();
e.preventDefault();
}
}
},
[activateCode, stop, view],
),
);
// highlighting
const { setMiniLocations } = useHighlighting({
view,
@@ -252,10 +194,9 @@ export function Repl({ embedded = false }) {
const handleShuffle = async () => {
const { code, name } = getRandomTune();
logger(`[repl] ✨ loading random tune "${name}"`);
clearCanvas();
resetLoadedSounds();
cleanupDraw();
await resetSounds();
scheduler.setCps(1);
await prebake(); // declare default samples
await evaluate(code, false);
};
@@ -307,6 +248,17 @@ export function Repl({ embedded = false }) {
setView(v);
}, []);
// Codemirror hooks
const onEvaluate = () => {
if (getAudioContext().state !== 'running') {
alert('please click play to initialize the audio. you can use shortcuts after that!');
return;
}
flash(view);
activateCode();
};
const onStop = () => stop();
return (
// bg-gradient-to-t from-blue-900 to-slate-900
// bg-gradient-to-t from-green-900 to-slate-900
@@ -343,6 +295,8 @@ export function Repl({ embedded = false }) {
onChange={handleChangeCode}
onViewChanged={handleViewChanged}
onSelectionChange={handleSelectionChange}
onEvaluate={onEvaluate}
onStop={onStop}
/>
</section>
{panelPosition === 'right' && !isEmbedded && <Footer context={context} />}
+144
View File
@@ -0,0 +1,144 @@
/*
App.js - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/App.js>
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 PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
import { getDrawContext, logger, $replstate, $repldirty } from '@strudel.cycles/core';
import { cx } from '@strudel.cycles/react';
import { getAudioContext } from '@strudel.cycles/webaudio';
import { writeText } from '@tauri-apps/api/clipboard';
import { nanoid } from 'nanoid';
import { createContext, useState } from 'react';
import { useSettings } from '../settings.mjs';
import { isTauri } from '../tauri.mjs';
import { Footer } from './Footer';
import { Header } from './Header';
import Loader from './Loader';
import './Repl.css';
import { resetSounds } from './prebake.mjs';
import { useStore } from '@nanostores/react';
import { getRandomTune } from './helpers.mjs';
export const ReplContext = createContext(null);
export function Repl({ embedded = false }) {
//const isEmbedded = embedded || window.location !== window.parent.location;
const isEmbedded = false;
const [lastShared, setLastShared] = useState();
const { panelPosition, isZen } = useSettings();
const replState = useStore($replstate);
const isDirty = useStore($repldirty);
//
// UI Actions
//
const handleTogglePlay = async () => {
window.postMessage('strudel-toggle-play');
/* await getAudioContext().resume(); // fixes no sound in ios webkit
if (!started) {
logger('[repl] started. tip: you can also start by pressing ctrl+enter', 'highlight');
activateCode();
} else {
logger('[repl] stopped. tip: you can also stop by pressing ctrl+dot', 'highlight');
stop();
} */
};
const handleUpdate = () => {
isDirty && activateCode();
logger('[repl] code updated! tip: you can also update the code by pressing ctrl+enter', 'highlight');
};
const handleShuffle = async () => {
window.postMessage('strudel-shuffle');
};
const handleShare = async () => {
const codeToShare = activeCode || code;
if (lastShared === codeToShare) {
logger(`Link already generated!`, 'error');
return;
}
// generate uuid in the browser
const hash = nanoid(12);
const shareUrl = window.location.origin + window.location.pathname + '?' + hash;
const { data, error } = await supabase.from('code').insert([{ code: codeToShare, hash }]);
if (!error) {
setLastShared(activeCode || code);
// copy shareUrl to clipboard
if (isTauri()) {
await writeText(shareUrl);
} else {
await navigator.clipboard.writeText(shareUrl);
}
const message = `Link copied to clipboard: ${shareUrl}`;
alert(message);
// alert(message);
logger(message, 'highlight');
} else {
console.log('error', error);
const message = `Error: ${error.message}`;
// alert(message);
logger(message);
}
};
const pending = false;
const error = undefined;
const { started, activeCode } = replState;
const context = {
// scheduler,
embedded,
started,
pending,
isDirty,
lastShared,
activeCode,
// handleChangeCode: codemirror.handleChangeCode,
handleTogglePlay,
handleUpdate,
handleShuffle,
handleShare,
};
return (
// bg-gradient-to-t from-blue-900 to-slate-900
// bg-gradient-to-t from-green-900 to-slate-900
<ReplContext.Provider value={context}>
<div
className={cx(
'h-full flex flex-col relative',
// overflow-hidden
)}
>
<Loader active={pending} />
<Header context={context} />
{/* isEmbedded && !started && (
<button
onClick={() => handleTogglePlay()}
className="text-white text-2xl fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-[1000] m-auto p-4 bg-black rounded-md flex items-center space-x-2"
>
<PlayCircleIcon className="w-6 h-6" />
<span>play</span>
</button>
) */}
<div className="grow flex relative overflow-hidden">
<section
className={'text-gray-100 cursor-text pb-0 overflow-auto grow' + (isZen ? ' px-10' : '')}
id="code"
ref={() => window.postMessage('strudel-container')}
>
{/* <CodeMirror {...codemirror} /> */}
</section>
{panelPosition === 'right' && !isEmbedded && <Footer context={context} />}
</div>
{error && (
<div className="text-red-500 p-4 bg-lineHighlight animate-pulse">{error.message || 'Unknown Error :-/'}</div>
)}
{panelPosition === 'bottom' && !isEmbedded && <Footer context={context} />}
</div>
</ReplContext.Provider>
);
}
+15
View File
@@ -0,0 +1,15 @@
---
import HeadCommon from '../components/HeadCommon.astro';
import { Repl } from '../repl/Repl2.jsx';
---
<html lang="en" class="dark">
<head>
<HeadCommon />
<title>Strudel REPL</title>
</head>
<body class="h-app-height bg-background">
<Repl client:only="react" />
<script src="./vanillarepl.mjs"></script>
</body>
</html>
+9
View File
@@ -1,3 +1,5 @@
import * as tunes from './tunes.mjs';
export function unicodeToBase64(text) {
const utf8Bytes = new TextEncoder().encode(text);
const base64String = btoa(String.fromCharCode(...utf8Bytes));
@@ -23,3 +25,10 @@ export function hash2code(hash) {
return base64ToUnicode(decodeURIComponent(hash));
//return atob(decodeURIComponent(codeParam || ''));
}
export function getRandomTune() {
const allTunes = Object.entries(tunes);
const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
const [name, code] = randomItem(allTunes);
return { name, code };
}
+49 -6
View File
@@ -1,12 +1,13 @@
import { Pattern, noteToMidi, valueToMidi } from '@strudel.cycles/core';
import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio';
import { Pattern, noteToMidi, valueToMidi, controls, evalScope } from '@strudel.cycles/core';
import { initAudioOnFirstClick, registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio';
import './piano.mjs';
import './files.mjs';
import { isTauri } from '../tauri.mjs';
import { settingPatterns } from '../settings.mjs';
import { soundMap } from '@strudel.cycles/webaudio';
export async function prebake() {
// https://archive.org/details/SalamanderGrandPianoV3
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
await Promise.all([
export function registerStockSounds() {
return Promise.all([
registerSynthSounds(),
registerZZFXSounds(),
//registerSoundfonts(),
@@ -14,6 +15,8 @@ export async function prebake() {
// => getting "window is not defined", as soon as "@strudel.cycles/soundfonts" is imported statically
// seems to be a problem with soundfont2
import('@strudel.cycles/soundfonts').then(({ registerSoundfonts }) => registerSoundfonts()),
// https://archive.org/details/SalamanderGrandPianoV3
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
samples(`./piano.json`, `./piano/`, { prebake: true }),
// https://github.com/sgossner/VCSL/
// https://api.github.com/repositories/126427031/contents/
@@ -111,11 +114,51 @@ export async function prebake() {
],
},
'github:tidalcycles/Dirt-Samples/master/',
{ prebake: true },
),
]);
}
export async function prebake() {
const initAudio = initAudioOnFirstClick();
// lazy load modules
let modules = [
import('@strudel.cycles/core'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/xen'),
import('@strudel.cycles/webaudio'),
import('@strudel/codemirror'),
import('@strudel/hydra'),
import('@strudel.cycles/serial'),
import('@strudel.cycles/soundfonts'),
import('@strudel.cycles/csound'),
];
if (isTauri()) {
modules = modules.concat([
import('@strudel/desktopbridge/loggerbridge.mjs'),
import('@strudel/desktopbridge/midibridge.mjs'),
import('@strudel/desktopbridge/oscbridge.mjs'),
]);
} else {
modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]);
}
// register modules in global scope
const modulesLoading = evalScope(
controls, // sadly, this cannot be exported from core direclty
settingPatterns,
...modules,
);
// register sounds and samples
return Promise.all([/* initAudio, */ modulesLoading, registerStockSounds()]);
// await samples('github:tidalcycles/Dirt-Samples/master');
}
export const resetSounds = () => {
soundMap.set({});
return registerStockSounds();
};
const maxPan = noteToMidi('C8');
const panwidth = (pan, width) => pan * width + (1 - width) / 2;
+175
View File
@@ -0,0 +1,175 @@
/* import { useStore } from '@nanostores/react';
import { webrepl, $replstate, setReplState } from './webrepl.mjs';
import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
import { useHighlighting, flash, usePatternFrame } from '@strudel.cycles/react';
import { getAudioContext } from '@strudel.cycles/webaudio';
import { useSettings } from '../settings.mjs';
import { themes } from './themes.mjs';
import { updateWidgets } from '@strudel/codemirror';
import { getDrawContext } from '@strudel.cycles/core';
export function useRepl({
drawContext = getDrawContext(),
editPattern,
evalOnMount = false,
canvasId,
drawTime = [-2, 2],
afterEval,
} = {}) {
const [view, setView] = useState(); // codemirror view
const handleViewChanged = useCallback((v) => setView(v), []);
const setCode = (code) => setReplState('code', code);
const setPending = (pending) => setReplState('pending', pending);
const handleChangeCode = useCallback((c) => setReplState('code', c), []); // [started] ?
// const handleSelectionChange = useCallback((selection) => {}, []);
// onSelectionChange={handleSelectionChange}
const repl = webrepl({ editPattern, afterEval });
const { evaluate, stop, scheduler } = repl;
const { schedulerError, evalError, code, started, pattern, miniLocations, activeCode, widgets, pending } =
useStore($replstate);
const error = schedulerError || evalError;
const settings = useSettings();
const {
theme,
keybindings,
fontSize,
fontFamily,
isLineNumbersDisplayed,
isAutoCompletionEnabled,
isLineWrappingEnabled,
panelPosition,
isZen,
} = settings;
// very ugly draw logic
const shouldPaint = useCallback((pat) => !!pat?.context?.onPaint, []);
const paintOptions = useMemo(() => ({ fontFamily }), [fontFamily]);
const id = useMemo(() => s4(), []);
canvasId = canvasId || `canvas-${id}`; // draw logic
const onDraw = useCallback(
(pattern, time, haps, drawTime) => {
const { onPaint } = pattern.context || {};
const ctx = typeof drawContext === 'function' ? drawContext(canvasId) : drawContext;
onPaint?.(ctx, time, haps, drawTime, paintOptions);
},
[drawContext, canvasId, paintOptions],
);
const drawFirstFrame = useCallback(
(pat) => {
if (shouldPaint(pat)) {
const [_, lookahead] = drawTime;
const haps = pat.queryArc(0, lookahead);
// draw at -0.001 to avoid activating haps at 0
onDraw(pat, -0.001, haps, drawTime);
}
},
[drawTime, onDraw, shouldPaint],
);
const inited = useRef();
useEffect(() => {
if (!inited.current && evalOnMount && code) {
inited.current = true;
evaluate(code, false).then((pat) => drawFirstFrame(pat));
}
}, [evalOnMount, code, evaluate, drawFirstFrame]);
usePatternFrame({
pattern,
started: shouldPaint(pattern) && started,
getTime: () => scheduler.now(),
drawTime,
onDraw,
});
//
const currentTheme = useMemo(() => themes[theme] || themes.strudelTheme, [theme]);
const isDirty = code !== activeCode;
// highlighting => should probably also just live outside of react...
const { setMiniLocations } = useHighlighting({
view,
pattern,
active: started && !activeCode?.includes('strudel disable-highlighting'),
getTime: () => scheduler.now(),
});
useEffect(() => {
if (view) {
updateWidgets(view, widgets);
}
}, [view, widgets]);
useEffect(() => {
setMiniLocations(miniLocations);
}, [miniLocations]);
const activateCode = useCallback(
async (autostart = true) => {
const res = await evaluate(code, autostart);
// broadcast({ type: 'start', from: id });
return res;
},
[evaluate, code],
);
// Codemirror hooks
const onEvaluate = () => {
if (getAudioContext().state !== 'running') {
alert('please click play to initialize the audio. you can use shortcuts after that!');
return;
}
flash(view);
activateCode();
};
const onReEvaluate = () => {
stop();
panic();
activateCode();
};
const onPanic = () => {
stop();
panic();
};
const onStop = () => stop();
const codemirror = {
theme: currentTheme,
value: code,
keybindings: keybindings,
isLineNumbersDisplayed: isLineNumbersDisplayed,
isAutoCompletionEnabled: isAutoCompletionEnabled,
isLineWrappingEnabled: isLineWrappingEnabled,
fontSize: fontSize,
fontFamily: fontFamily,
onChange: handleChangeCode,
onViewChanged: handleViewChanged,
onEvaluate: onEvaluate,
onReEvaluate: onReEvaluate,
onPanic: onPanic,
onStop: onStop,
};
return {
code,
setCode,
scheduler,
evaluate,
activateCode,
isDirty,
activeCode,
pattern,
started,
stop,
error,
widgets,
pending,
setPending,
codemirror,
};
}
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
*/
+135
View File
@@ -0,0 +1,135 @@
import { logger, getDrawContext, silence } from '@strudel.cycles/core';
import { StrudelMirror } from '@strudel/codemirror';
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
import { transpiler } from '@strudel.cycles/transpiler';
import { prebake, resetSounds } from './prebake.mjs';
import { settingsMap } from '@src/settings.mjs';
import { setLatestCode } from '../settings.mjs';
import { hash2code, code2hash } from './helpers.mjs';
import { createClient } from '@supabase/supabase-js';
import { getRandomTune } from './helpers.mjs';
const supabase = createClient(
'https://pidxdsxphlhzjnzmifth.supabase.co',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM',
);
async function initCodeFromUrl() {
// await new Promise((resolve) => setTimeout(resolve, 2000));
// load code from url hash (either short hash from database or decode long hash)
try {
const initialUrl = window.location.href;
const hash = initialUrl.split('?')[1]?.split('#')?.[0];
const codeParam = window.location.href.split('#')[1] || '';
// looking like https://strudel.cc/?J01s5i1J0200 (fixed hash length)
if (codeParam) {
// looking like https://strudel.cc/#ImMzIGUzIg%3D%3D (hash length depends on code length)
return hash2code(codeParam);
} else if (hash) {
return supabase
.from('code')
.select('code')
.eq('hash', hash)
.then(({ data, error }) => {
if (error) {
console.warn('failed to load hash', error);
}
if (data.length) {
//console.log('load hash from database', hash);
return data[0].code;
}
});
}
} catch (err) {
console.warn('failed to decode', err);
}
}
const initialCode = initCodeFromUrl();
async function run() {
const container = document.getElementById('code');
if (!container) {
console.warn('could not init: no container found');
return;
}
// Create a single supabase client for interacting with your database
const settings = settingsMap.get();
const drawContext = getDrawContext();
const drawTime = [-2, 2];
const editor = new StrudelMirror({
defaultOutput: webaudioOutput,
getTime: () => getAudioContext().currentTime,
transpiler,
root: container,
initialCode: '// LOADING',
pattern: silence,
settings,
drawTime,
onDraw: (haps, time, frame, painters) => {
painters.length && drawContext.clearRect(0, 0, drawContext.canvas.width * 2, drawContext.canvas.height * 2);
painters?.forEach((painter) => {
// ctx time haps drawTime paintOptions
painter(drawContext, time, haps, drawTime, { clear: false });
});
},
prebake: () => prebake(),
afterEval: ({ code }) => {
setLatestCode(code);
window.location.hash = '#' + code2hash(code);
},
});
// init settings
editor.updateSettings(settings);
const decoded = await initialCode;
let msg;
if (decoded) {
editor.setCode(decoded);
msg = `I have loaded the code from the URL.`;
} else if (settings.latestCode) {
editor.setCode(settings.latestCode);
msg = `Your last session has been loaded!`;
} else {
const { code: randomTune, name } = getRandomTune();
editor.setCode(randomTune);
msg = `A random code snippet named "${name}" has been loaded!`;
}
logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight');
// setPending(false);
settingsMap.listen((settings, key) => editor.changeSetting(key, settings[key]));
onEvent('strudel-toggle-play', () => editor.toggle());
onEvent('strudel-shuffle', async () => {
const { code, name } = getRandomTune();
logger(`[repl] ✨ loading random tune "${name}"`);
console.log(code);
editor.setCode(code);
await resetSounds();
editor.repl.setCps(1);
editor.repl.evaluate(code, false);
});
// const isEmbedded = embedded || window.location !== window.parent.location;
}
let inited = false;
onEvent('strudel-container', () => {
if (!inited) {
inited = true;
run();
}
});
function onEvent(key, callback) {
const listener = (e) => {
if (e.data === key) {
callback();
}
};
window.addEventListener('message', listener);
return () => window.removeEventListener('message', listener);
}
+78
View File
@@ -0,0 +1,78 @@
/* import { cleanupDraw, cleanupUi, repl, getDrawContext, silence } from '@strudel.cycles/core';
import { webaudioOutput, getAudioContext } from '@strudel.cycles/webaudio';
import { transpiler } from '@strudel.cycles/transpiler';
import { prebake } from './prebake.mjs';
import { atom } from 'nanostores';
export const $replstate = atom({
schedulerError: undefined,
evalError: undefined,
code: '// LOADING',
activeCode: '// LOADING',
pattern: silence,
miniLocations: [],
widgets: [],
pending: true,
});
export const setReplState = (key, value) => $replstate.set({ ...$replstate.get(), [key]: value });
let instance;
export function webrepl({ beforeEval, onEvalError, afterEval, editPattern } = {}) {
if (instance) {
// TODO: find way to attack hooks (beforeEval, ...) to existing instance
// maybe use subscriber pattern
return instance;
}
if (typeof window === 'undefined') {
throw new Error('webrepl can only be created in a browser!');
}
const init = prebake(); // load modules + samples
instance = repl({
// interval,
defaultOutput: webaudioOutput,
getTime: () => getAudioContext().currentTime,
onSchedulerError: (err) => setReplState('schedulerError', err),
onEvalError: (err) => {
setReplState('evalError', err);
setReplState('pending', false);
onEvalError?.(err);
},
drawContext: getDrawContext(),
transpiler,
editPattern,
beforeEval: async ({ code }) => {
setReplState('code', code);
setReplState('pending', true);
await init;
await beforeEval?.();
cleanupUi();
cleanupDraw();
},
afterEval: async (res) => {
const { pattern: _pattern, code, meta } = res;
setReplState('miniLocations', meta?.miniLocations || []);
setReplState('widgets', meta?.widgets || []);
setReplState('activeCode', code);
setReplState('pattern', _pattern);
setReplState('evalError', undefined);
setReplState('schedulerError', undefined);
setReplState('pending', false);
await afterEval?.(res);
},
onToggle: (started) => {
setReplState('started', started);
// this was so far only used in main repl...
// is this also relevant for mini repl?
if (!started) {
cleanupDraw(false);
window.postMessage('strudel-stop');
} else {
window.postMessage('strudel-start');
}
},
});
return instance;
}
*/
+4
View File
@@ -7,6 +7,8 @@ export const defaultSettings = {
keybindings: 'codemirror',
isLineNumbersDisplayed: true,
isAutoCompletionEnabled: false,
isPatternHighlightingEnabled: true,
isFlashEnabled: true,
isTooltipEnabled: false,
isLineWrappingEnabled: false,
theme: 'strudelTheme',
@@ -29,6 +31,8 @@ export function useSettings() {
isAutoCompletionEnabled: [true, 'true'].includes(state.isAutoCompletionEnabled) ? true : false,
isTooltipEnabled: [true, 'true'].includes(state.isTooltipEnabled) ? true : false,
isLineWrappingEnabled: [true, 'true'].includes(state.isLineWrappingEnabled) ? true : false,
isPatternHighlightingEnabled: [true, 'true'].includes(state.isPatternHighlightingEnabled) ? true : false,
isFlashEnabled: [true, 'true'].includes(state.isFlashEnabled) ? true : false,
fontSize: Number(state.fontSize),
panelPosition: state.activeFooter !== '' ? state.panelPosition : 'bottom',
};
+1
View File
@@ -12,6 +12,7 @@
"paths": {
"@components/*": ["src/components/*"],
"@src/*": ["src/*"],
"~/*": ["/*"],
}
}
}