mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 21:58:37 +00:00
Merge pull request #866 from tidalcycles/vanilla-repl-2
more work on vanilla repl: repl web component + package
This commit is contained in:
commit
8b4be4d100
@ -10,8 +10,9 @@ import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core';
|
||||
import { flash, isFlashEnabled } from './flash.mjs';
|
||||
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
|
||||
import { keybindings } from './keybindings.mjs';
|
||||
import { theme } from './themes.mjs';
|
||||
import { initTheme, activateTheme, theme } from './themes.mjs';
|
||||
import { updateWidgets, sliderPlugin } from './slider.mjs';
|
||||
import { persistentAtom } from '@nanostores/persistent';
|
||||
|
||||
const extensions = {
|
||||
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
|
||||
@ -25,11 +26,32 @@ const extensions = {
|
||||
};
|
||||
const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
|
||||
|
||||
export const defaultSettings = {
|
||||
keybindings: 'codemirror',
|
||||
isLineNumbersDisplayed: true,
|
||||
isActiveLineHighlighted: false,
|
||||
isAutoCompletionEnabled: false,
|
||||
isPatternHighlightingEnabled: true,
|
||||
isFlashEnabled: true,
|
||||
isTooltipEnabled: false,
|
||||
isLineWrappingEnabled: false,
|
||||
theme: 'strudelTheme',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 18,
|
||||
};
|
||||
|
||||
export const codemirrorSettings = persistentAtom('codemirror-settings', defaultSettings, {
|
||||
encode: JSON.stringify,
|
||||
decode: JSON.parse,
|
||||
});
|
||||
|
||||
// https://codemirror.net/docs/guide/
|
||||
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, settings, root }) {
|
||||
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, root }) {
|
||||
const settings = codemirrorSettings.get();
|
||||
const initialSettings = Object.keys(compartments).map((key) =>
|
||||
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
|
||||
);
|
||||
initTheme(settings.theme);
|
||||
let state = EditorState.create({
|
||||
doc: initialCode,
|
||||
extensions: [
|
||||
@ -86,7 +108,7 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, set
|
||||
|
||||
export class StrudelMirror {
|
||||
constructor(options) {
|
||||
const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, settings, ...replOptions } = options;
|
||||
const { root, id, initialCode = '', onDraw, drawTime = [-2, 2], autodraw, prebake, ...replOptions } = options;
|
||||
this.code = initialCode;
|
||||
this.root = root;
|
||||
this.miniLocations = [];
|
||||
@ -94,6 +116,7 @@ export class StrudelMirror {
|
||||
this.painters = [];
|
||||
this.onDraw = onDraw;
|
||||
const self = this;
|
||||
this.id = id || s4();
|
||||
|
||||
this.drawer = new Drawer((haps, time) => {
|
||||
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
|
||||
@ -101,14 +124,15 @@ export class StrudelMirror {
|
||||
this.onDraw?.(haps, time, currentFrame, this.painters);
|
||||
}, drawTime);
|
||||
|
||||
// this approach might not work with multiple repls on screen..
|
||||
// this approach does not work with multiple repls on screen
|
||||
// TODO: refactor onPaint usages + find fix, maybe remove painters here?
|
||||
Pattern.prototype.onPaint = function (onPaint) {
|
||||
self.painters.push(onPaint);
|
||||
return this;
|
||||
};
|
||||
|
||||
this.prebaked = prebake();
|
||||
// this.drawFirstFrame();
|
||||
autodraw && this.drawFirstFrame();
|
||||
|
||||
this.repl = repl({
|
||||
...replOptions,
|
||||
@ -116,6 +140,12 @@ export class StrudelMirror {
|
||||
replOptions?.onToggle?.(started);
|
||||
if (started) {
|
||||
this.drawer.start(this.repl.scheduler);
|
||||
// stop other repls when this one is started
|
||||
document.dispatchEvent(
|
||||
new CustomEvent('start-repl', {
|
||||
detail: this.id,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
this.drawer.stop();
|
||||
updateMiniLocations(this.editor, []);
|
||||
@ -140,13 +170,11 @@ export class StrudelMirror {
|
||||
});
|
||||
this.editor = initEditor({
|
||||
root,
|
||||
settings,
|
||||
initialCode,
|
||||
onChange: (v) => {
|
||||
if (v.docChanged) {
|
||||
this.code = v.state.doc.toString();
|
||||
// TODO: repl is still untouched to make sure the old Repl.jsx stays untouched..
|
||||
// this.repl.setCode(this.code);
|
||||
this.repl.setCode?.(this.code);
|
||||
}
|
||||
},
|
||||
onEvaluate: () => this.evaluate(),
|
||||
@ -154,9 +182,17 @@ export class StrudelMirror {
|
||||
});
|
||||
const cmEditor = this.root.querySelector('.cm-editor');
|
||||
if (cmEditor) {
|
||||
this.root.style.display = 'block';
|
||||
this.root.style.backgroundColor = 'var(--background)';
|
||||
cmEditor.style.backgroundColor = 'transparent';
|
||||
}
|
||||
// stop this repl when another repl is started
|
||||
this.onStartRepl = (e) => {
|
||||
if (e.detail !== this.id) {
|
||||
this.stop();
|
||||
}
|
||||
};
|
||||
document.addEventListener('start-repl', this.onStartRepl);
|
||||
}
|
||||
async drawFirstFrame() {
|
||||
if (!this.onDraw) {
|
||||
@ -166,8 +202,9 @@ export class StrudelMirror {
|
||||
await this.prebaked;
|
||||
try {
|
||||
await this.repl.evaluate(this.code, false);
|
||||
this.drawer.invalidate(this.repl.scheduler);
|
||||
this.onDraw?.(this.drawer.visibleHaps, 0, []);
|
||||
this.drawer.invalidate(this.repl.scheduler, -0.001);
|
||||
// draw at -0.001 to avoid haps at 0 to be visualized as active
|
||||
this.onDraw?.(this.drawer.visibleHaps, -0.001, [], this.painters);
|
||||
} catch (err) {
|
||||
console.warn('first frame could not be painted');
|
||||
}
|
||||
@ -181,7 +218,7 @@ export class StrudelMirror {
|
||||
}
|
||||
async toggle() {
|
||||
if (this.repl.scheduler.started) {
|
||||
this.repl.scheduler.stop();
|
||||
this.repl.stop();
|
||||
} else {
|
||||
this.evaluate();
|
||||
}
|
||||
@ -212,6 +249,9 @@ export class StrudelMirror {
|
||||
this.editor.dispatch({
|
||||
effects: compartments[key].reconfigure(newValue),
|
||||
});
|
||||
if (key === 'theme') {
|
||||
activateTheme(value);
|
||||
}
|
||||
}
|
||||
setLineWrappingEnabled(enabled) {
|
||||
this.reconfigureExtension('isLineWrappingEnabled', enabled);
|
||||
@ -231,6 +271,8 @@ export class StrudelMirror {
|
||||
for (let key in extensions) {
|
||||
this.reconfigureExtension(key, settings[key]);
|
||||
}
|
||||
const updated = { ...codemirrorSettings.get(), ...settings };
|
||||
codemirrorSettings.set(updated);
|
||||
}
|
||||
changeSetting(key, value) {
|
||||
if (extensions[key]) {
|
||||
@ -246,8 +288,18 @@ export class StrudelMirror {
|
||||
const changes = { from: 0, to: this.editor.state.doc.length, insert: code };
|
||||
this.editor.dispatch({ changes });
|
||||
}
|
||||
clear() {
|
||||
this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl);
|
||||
}
|
||||
}
|
||||
|
||||
function parseBooleans(value) {
|
||||
return { true: true, false: false }[value] ?? value;
|
||||
}
|
||||
|
||||
// helper function to generate repl ids
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
}
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@ -1,87 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>StrudelMirror Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="settings">
|
||||
<form name="settings">
|
||||
<label
|
||||
>theme
|
||||
<select name="theme">
|
||||
<option>strudelTheme</option>
|
||||
<option>bluescreen</option>
|
||||
<option>blackscreen</option>
|
||||
<option>whitescreen</option>
|
||||
<option>teletext</option>
|
||||
<option>algoboy</option>
|
||||
<option>terminal</option>
|
||||
<option>abcdef</option>
|
||||
<option>androidstudio</option>
|
||||
<option>atomone</option>
|
||||
<option>aura</option>
|
||||
<option>bespin</option>
|
||||
<option>darcula</option>
|
||||
<option>dracula</option>
|
||||
<option>duotoneDark</option>
|
||||
<option>eclipse</option>
|
||||
<option>githubDark</option>
|
||||
<option>gruvboxDark</option>
|
||||
<option>materialDark</option>
|
||||
<option>nord</option>
|
||||
<option>okaidia</option>
|
||||
<option>solarizedDark</option>
|
||||
<option>sublime</option>
|
||||
<option>tokyoNight</option>
|
||||
<option>tokyoNightStorm</option>
|
||||
<option>vscodeDark</option>
|
||||
<option>xcodeDark</option>
|
||||
<option>bbedit</option>
|
||||
<option>duotoneLight</option>
|
||||
<option>githubLight</option>
|
||||
<option>gruvboxLight</option>
|
||||
<option>materialLight</option>
|
||||
<option>noctisLilac</option>
|
||||
<option>solarizedLight</option>
|
||||
<option>tokyoNightDay</option>
|
||||
<option>xcodeLight</option>
|
||||
</select> </label
|
||||
><br />
|
||||
<!-- <label
|
||||
>keybindings
|
||||
<select name="keybindings">
|
||||
<option>codemirror</option>
|
||||
<option>vim</option>
|
||||
<option>emacs</option>
|
||||
<option>vscode</option>
|
||||
</select> </label
|
||||
><br />
|
||||
<label
|
||||
>fontFamily
|
||||
<select name="fontFamily">
|
||||
<option>monospace</option>
|
||||
<option>helvetica</option>
|
||||
</select> </label
|
||||
><br /> -->
|
||||
<label>fontSize <input type="number" name="fontSize" /></label>
|
||||
<br />
|
||||
<label><input type="checkbox" name="isLineNumbersDisplayed" />isLineNumbersDisplayed</label>
|
||||
<br />
|
||||
<label><input type="checkbox" name="isActiveLineHighlighted" />isActiveLineHighlighted</label>
|
||||
<br />
|
||||
<label><input type="checkbox" name="isPatternHighlightingEnabled" />isPatternHighlightingEnabled</label>
|
||||
<br />
|
||||
<label><input type="checkbox" name="isFlashEnabled" />isFlashEnabled</label>
|
||||
<br />
|
||||
<label><input type="checkbox" name="isLineWrappingEnabled" />isLineWrappingEnabled</label>
|
||||
<!-- <label><input type="checkbox" name="isAutoCompletionEnabled" />isAutoCompletionEnabled</label> -->
|
||||
<!-- <label><input type="checkbox" name="isTooltipEnabled" />isTooltipEnabled</label> -->
|
||||
</form>
|
||||
</div>
|
||||
<div id="code"></div>
|
||||
<script type="module" src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,199 +0,0 @@
|
||||
import { logger, getDrawContext, silence, controls, evalScope, hash2code, code2hash } from '@strudel.cycles/core';
|
||||
import { StrudelMirror } from '@strudel/codemirror';
|
||||
import { transpiler } from '@strudel.cycles/transpiler';
|
||||
import {
|
||||
getAudioContext,
|
||||
webaudioOutput,
|
||||
registerSynthSounds,
|
||||
registerZZFXSounds,
|
||||
samples,
|
||||
} from '@strudel.cycles/webaudio';
|
||||
import './style.css';
|
||||
|
||||
let editor;
|
||||
const initialSettings = {
|
||||
keybindings: 'codemirror',
|
||||
isLineNumbersDisplayed: true,
|
||||
isActiveLineHighlighted: true,
|
||||
isAutoCompletionEnabled: false,
|
||||
isPatternHighlightingEnabled: true,
|
||||
isFlashEnabled: true,
|
||||
isTooltipEnabled: false,
|
||||
isLineWrappingEnabled: false,
|
||||
theme: 'teletext',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 18,
|
||||
};
|
||||
|
||||
async function run() {
|
||||
const container = document.getElementById('code');
|
||||
if (!container) {
|
||||
console.warn('could not init: no container found');
|
||||
return;
|
||||
}
|
||||
|
||||
const drawContext = getDrawContext();
|
||||
const drawTime = [-2, 2];
|
||||
editor = new StrudelMirror({
|
||||
defaultOutput: webaudioOutput,
|
||||
getTime: () => getAudioContext().currentTime,
|
||||
transpiler,
|
||||
root: container,
|
||||
initialCode: '// LOADING',
|
||||
pattern: silence,
|
||||
settings: initialSettings,
|
||||
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: async () => {
|
||||
// populate scope / lazy load modules
|
||||
const modulesLoading = evalScope(
|
||||
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'),
|
||||
/* import('@strudel.cycles/midi'), */
|
||||
// import('@strudel.cycles/osc'),
|
||||
controls, // sadly, this cannot be exported from core directly (yet)
|
||||
);
|
||||
// load samples
|
||||
const ds = 'https://raw.githubusercontent.com/felixroos/dough-samples/main/';
|
||||
await Promise.all([
|
||||
modulesLoading,
|
||||
registerSynthSounds(),
|
||||
registerZZFXSounds(),
|
||||
samples(`${ds}/tidal-drum-machines.json`),
|
||||
samples(`${ds}/piano.json`),
|
||||
samples(`${ds}/Dirt-Samples.json`),
|
||||
samples(`${ds}/EmuSP12.json`),
|
||||
samples(`${ds}/vcsl.json`),
|
||||
]);
|
||||
},
|
||||
afterEval: ({ code }) => {
|
||||
window.location.hash = '#' + code2hash(code);
|
||||
},
|
||||
});
|
||||
|
||||
// init settings
|
||||
editor.updateSettings(initialSettings);
|
||||
|
||||
logger(`Welcome to Strudel! Click into the editor and then hit ctrl+enter to run the code!`, 'highlight');
|
||||
const codeParam = window.location.href.split('#')[1] || '';
|
||||
|
||||
const initialCode = codeParam
|
||||
? hash2code(codeParam)
|
||||
: `// @date 23-11-30
|
||||
// "teigrührgerät" @by froos
|
||||
|
||||
stack(
|
||||
stack(
|
||||
s("bd(<3!3 5>,6)/2").bank('RolandTR707')
|
||||
,
|
||||
s("~ sd:<0 1>").bank('RolandTR707').room("<0 .5>")
|
||||
.lastOf(8, x=>x.segment("12").end(.2).gain(isaw))
|
||||
,
|
||||
s("[tb ~ tb]").bank('RolandTR707')
|
||||
.clip(0).release(.08).room(.2)
|
||||
).off(-1/6, x=>x.speed(.7).gain(.2).degrade())
|
||||
,
|
||||
stack(
|
||||
note("<g1(<3 4>,6) ~!2 [f1?]*2>")
|
||||
.s("sawtooth").lpf(perlin.range(400,1000))
|
||||
.lpa(.1).lpenv(-3).room(.2)
|
||||
.lpq(8).noise(.2)
|
||||
.add(note("0,.1"))
|
||||
,
|
||||
chord("<~ Gm9 ~!2>")
|
||||
.dict('ireal').voicing()
|
||||
.s("sawtooth").vib("2:.1")
|
||||
.lpf(1000).lpa(.1).lpenv(-4)
|
||||
.room(.5)
|
||||
,
|
||||
n(run(3)).chord("<Gm9 Gm11>/8")
|
||||
.dict('ireal-ext')
|
||||
.off(1/2, add(n(4)))
|
||||
.voicing()
|
||||
.clip(.1).release(.05)
|
||||
.s("sine").jux(rev)
|
||||
.sometimesBy(sine.slow(16), add(note(12)))
|
||||
.room(.75)
|
||||
.lpf(sine.range(200,2000).slow(16))
|
||||
.gain(saw.slow(4).div(2))
|
||||
).add(note(perlin.range(0,.5)))
|
||||
)`;
|
||||
|
||||
editor.setCode(initialCode); // simpler alternative to above init
|
||||
|
||||
// settingsMap.listen((settings, key) => editor.changeSetting(key, settings[key]));
|
||||
onEvent('strudel-toggle-play', () => editor.toggle());
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
function onEvent(key, callback) {
|
||||
const listener = (e) => {
|
||||
if (e.data === key) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
window.addEventListener('message', listener);
|
||||
return () => window.removeEventListener('message', listener);
|
||||
}
|
||||
|
||||
// settings form
|
||||
function getInput(form, name) {
|
||||
return form.querySelector(`input[name=${name}]`) || form.querySelector(`select[name=${name}]`);
|
||||
}
|
||||
function getFormValues(form, initial) {
|
||||
const entries = Object.entries(initial).map(([key, initialValue]) => {
|
||||
const input = getInput(form, key);
|
||||
if (!input) {
|
||||
return [key, initialValue]; // fallback
|
||||
}
|
||||
if (input.type === 'checkbox') {
|
||||
return [key, input.checked];
|
||||
}
|
||||
if (input.type === 'number') {
|
||||
return [key, Number(input.value)];
|
||||
}
|
||||
if (input.tagName === 'SELECT') {
|
||||
return [key, input.value];
|
||||
}
|
||||
return [key, input.value];
|
||||
});
|
||||
return Object.fromEntries(entries);
|
||||
}
|
||||
function setFormValues(form, values) {
|
||||
Object.entries(values).forEach(([key, value]) => {
|
||||
const input = getInput(form, key);
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
if (input.type === 'checkbox') {
|
||||
input.checked = !!value;
|
||||
} else if (input.type === 'number') {
|
||||
input.value = value;
|
||||
} else if (input.tagName) {
|
||||
input.value = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const form = document.querySelector('form[name=settings]');
|
||||
setFormValues(form, initialSettings);
|
||||
form.addEventListener('change', () => {
|
||||
const values = getFormValues(form, initialSettings);
|
||||
// console.log('values', values);
|
||||
editor.updateSettings(values);
|
||||
});
|
||||
@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "strudelmirror",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strudel/codemirror": "workspace:*",
|
||||
"@strudel.cycles/core":"workspace:*",
|
||||
"@strudel.cycles/transpiler":"workspace:*",
|
||||
"@strudel.cycles/tonal":"workspace:*",
|
||||
"@strudel.cycles/mini":"workspace:*",
|
||||
"@strudel.cycles/xen":"workspace:*",
|
||||
"@strudel.cycles/webaudio":"workspace:*",
|
||||
"@strudel/hydra":"workspace:*",
|
||||
"@strudel.cycles/serial":"workspace:*",
|
||||
"@strudel.cycles/soundfonts":"workspace:*",
|
||||
"@strudel.cycles/csound":"workspace:*",
|
||||
"@strudel.cycles/midi":"workspace:*",
|
||||
"@strudel.cycles/osc":"workspace:*"
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
:root {
|
||||
--foreground: white;
|
||||
}
|
||||
|
||||
body,
|
||||
input {
|
||||
font-family: monospace;
|
||||
background: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#code,
|
||||
.cm-editor,
|
||||
.cm-scroller {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.settings {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
display: flex-col;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.settings > form > * + * {
|
||||
margin-top: 10px;
|
||||
}
|
||||
@ -47,7 +47,9 @@
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@uiw/codemirror-themes": "^4.19.16",
|
||||
"@uiw/codemirror-themes-all": "^4.19.16",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.2.0",
|
||||
"nanostores": "^0.8.1",
|
||||
"@nanostores/persistent": "^0.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^4.3.3"
|
||||
|
||||
14
packages/codemirror/themes.mjs
vendored
14
packages/codemirror/themes.mjs
vendored
@ -484,11 +484,16 @@ export function injectStyle(rule) {
|
||||
return () => styleSheet.deleteRule(ruleIndex);
|
||||
}
|
||||
|
||||
let currentTheme, resetThemeStyle, themeStyle;
|
||||
let currentTheme,
|
||||
resetThemeStyle,
|
||||
themeStyle,
|
||||
styleID = 'strudel-theme-vars';
|
||||
export function initTheme(theme) {
|
||||
themeStyle = document.createElement('style');
|
||||
themeStyle.id = 'strudel-theme';
|
||||
document.head.append(themeStyle);
|
||||
if (!document.getElementById(styleID)) {
|
||||
themeStyle = document.createElement('style');
|
||||
themeStyle.id = styleID;
|
||||
document.head.append(themeStyle);
|
||||
}
|
||||
activateTheme(theme);
|
||||
}
|
||||
|
||||
@ -496,6 +501,7 @@ export function activateTheme(name) {
|
||||
if (currentTheme === name) {
|
||||
return;
|
||||
}
|
||||
currentTheme = name;
|
||||
if (!settings[name]) {
|
||||
console.warn('theme', name, 'has no settings.. defaulting to strudelTheme settings');
|
||||
}
|
||||
|
||||
@ -145,12 +145,13 @@ export class Drawer {
|
||||
},
|
||||
);
|
||||
}
|
||||
invalidate(scheduler = this.scheduler) {
|
||||
invalidate(scheduler = this.scheduler, t) {
|
||||
if (!scheduler) {
|
||||
return;
|
||||
}
|
||||
// TODO: scheduler.now() seems to move even when it's stopped, this hints at a bug...
|
||||
t = t ?? scheduler.now();
|
||||
this.scheduler = scheduler;
|
||||
const t = scheduler.now();
|
||||
let [_, lookahead] = this.drawTime;
|
||||
const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1];
|
||||
// remove all future haps
|
||||
|
||||
@ -256,10 +256,13 @@ export function getDrawOptions(drawTime, options = {}) {
|
||||
return { fold: 1, ...options, cycles, playhead };
|
||||
}
|
||||
|
||||
export const getPunchcardPainter =
|
||||
(options = {}) =>
|
||||
(ctx, time, haps, drawTime, paintOptions = {}) =>
|
||||
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, { ...paintOptions, ...options }) });
|
||||
|
||||
Pattern.prototype.punchcard = function (options) {
|
||||
return this.onPaint((ctx, time, haps, drawTime, paintOptions = {}) =>
|
||||
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, { ...paintOptions, ...options }) }),
|
||||
);
|
||||
return this.onPaint(getPunchcardPainter(options));
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -16,13 +16,36 @@ export function repl({
|
||||
transpiler,
|
||||
onToggle,
|
||||
editPattern,
|
||||
onUpdateState,
|
||||
}) {
|
||||
const state = {
|
||||
schedulerError: undefined,
|
||||
evalError: undefined,
|
||||
code: '// LOADING',
|
||||
activeCode: '// LOADING',
|
||||
pattern: undefined,
|
||||
miniLocations: [],
|
||||
widgets: [],
|
||||
pending: true,
|
||||
started: false,
|
||||
};
|
||||
|
||||
const updateState = (update) => {
|
||||
Object.assign(state, update);
|
||||
state.isDirty = state.code !== state.activeCode;
|
||||
state.error = state.evalError || state.schedulerError;
|
||||
onUpdateState?.(state);
|
||||
};
|
||||
|
||||
const scheduler = new Cyclist({
|
||||
interval,
|
||||
onTrigger: getTrigger({ defaultOutput, getTime }),
|
||||
onError: onSchedulerError,
|
||||
getTime,
|
||||
onToggle,
|
||||
onToggle: (started) => {
|
||||
updateState({ started });
|
||||
onToggle?.(started);
|
||||
},
|
||||
});
|
||||
let pPatterns = {};
|
||||
let allTransform;
|
||||
@ -43,6 +66,7 @@ export function repl({
|
||||
throw new Error('no code to evaluate');
|
||||
}
|
||||
try {
|
||||
updateState({ code, pending: true });
|
||||
await beforeEval?.({ code });
|
||||
shouldHush && hush();
|
||||
let { pattern, meta } = await _evaluate(code, transpiler);
|
||||
@ -58,17 +82,28 @@ export function repl({
|
||||
}
|
||||
logger(`[eval] code updated`);
|
||||
setPattern(pattern, autostart);
|
||||
updateState({
|
||||
miniLocations: meta?.miniLocations || [],
|
||||
widgets: meta?.widgets || [],
|
||||
activeCode: code,
|
||||
pattern,
|
||||
evalError: undefined,
|
||||
schedulerError: undefined,
|
||||
pending: false,
|
||||
});
|
||||
afterEval?.({ code, pattern, meta });
|
||||
return pattern;
|
||||
} catch (err) {
|
||||
// console.warn(`[repl] eval error: ${err.message}`);
|
||||
logger(`[eval] error: ${err.message}`, 'error');
|
||||
updateState({ evalError: err, 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);
|
||||
|
||||
@ -127,8 +162,8 @@ export function repl({
|
||||
setCpm,
|
||||
setcpm: setCpm,
|
||||
});
|
||||
|
||||
return { scheduler, evaluate, start, stop, pause, setCps, setPattern };
|
||||
const setCode = (code) => updateState({ code });
|
||||
return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle, state };
|
||||
}
|
||||
|
||||
export const getTrigger =
|
||||
|
||||
1
packages/repl/.gitignore
vendored
Normal file
1
packages/repl/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
stats.html
|
||||
3
packages/repl/README.md
Normal file
3
packages/repl/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# @strudel/repl
|
||||
|
||||
The Strudel REPL as a web component.
|
||||
33
packages/repl/examples/simple.html
Normal file
33
packages/repl/examples/simple.html
Normal file
@ -0,0 +1,33 @@
|
||||
<!-- <script src="https://unpkg.com/@strudel/repl@latest"></script> -->
|
||||
<!-- <script src="https://unpkg.com/@strudel/repl@0.9.4"></script> -->
|
||||
<script src="../dist/index.js"></script>
|
||||
<strudel-editor>
|
||||
<!--
|
||||
// @date 23-08-15
|
||||
// "golf rolf" @by froos @license CC BY-NC-SA 4.0
|
||||
|
||||
setcps(1)
|
||||
stack(
|
||||
s("bd*2, ~ rim*<1!3 2>, hh*4").bank('RolandTR909')
|
||||
.off(-1/8, set(speed("1.5").gain(.25)))
|
||||
.mask("<0!16 1!64>")
|
||||
,
|
||||
note("g1(3,8)")
|
||||
.s("gm_synth_bass_2:<0 2>")
|
||||
.delay(".8:.25:.25")
|
||||
.clip("<.5!16 2!32>")
|
||||
.off(1/8, add(note("12?0.7")))
|
||||
.lpf(sine.range(500,2000).slow(32)).lpq(8)
|
||||
.add(note("0,.05"))
|
||||
.mask("<0!8 1!32>")
|
||||
,
|
||||
n("<0 1 2 3 4>*8").scale('G4 minor')
|
||||
.s("gm_lead_6_voice")
|
||||
.clip(sine.range(.2,.8).slow(8))
|
||||
.jux(rev)
|
||||
.room(2)
|
||||
.sometimes(add(note("12")))
|
||||
.lpf(perlin.range(200,20000).slow(4))
|
||||
).reset("<x@15 x(5,8)>")
|
||||
-->
|
||||
</strudel-editor>
|
||||
2
packages/repl/index.mjs
Normal file
2
packages/repl/index.mjs
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './repl-component.mjs';
|
||||
export * from './prebake.mjs';
|
||||
51
packages/repl/package.json
Normal file
51
packages/repl/package.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "@strudel/repl",
|
||||
"version": "0.9.4",
|
||||
"description": "Strudel REPL as a Web Component",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tidalcycles",
|
||||
"strudel",
|
||||
"pattern",
|
||||
"livecoding",
|
||||
"algorave"
|
||||
],
|
||||
"author": "Felix Roos <flix91@gmail.com>",
|
||||
"contributors": [
|
||||
"Alex McLean <alex@slab.org>"
|
||||
],
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@rollup/plugin-replace": "^5.0.5",
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@strudel.cycles/midi": "workspace:*",
|
||||
"@strudel.cycles/mini": "workspace:*",
|
||||
"@strudel.cycles/soundfonts": "workspace:*",
|
||||
"@strudel.cycles/tonal": "workspace:*",
|
||||
"@strudel.cycles/transpiler": "workspace:*",
|
||||
"@strudel.cycles/webaudio": "workspace:*",
|
||||
"@strudel/codemirror": "workspace:*",
|
||||
"@strudel/hydra": "workspace:*",
|
||||
"rollup-plugin-visualizer": "^5.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^4.3.3"
|
||||
}
|
||||
}
|
||||
54
packages/repl/prebake.mjs
Normal file
54
packages/repl/prebake.mjs
Normal file
@ -0,0 +1,54 @@
|
||||
import { controls, noteToMidi, valueToMidi, Pattern, evalScope } from '@strudel.cycles/core';
|
||||
import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio';
|
||||
import * as core from '@strudel.cycles/core';
|
||||
|
||||
export async function prebake() {
|
||||
const modulesLoading = evalScope(
|
||||
// import('@strudel.cycles/core'),
|
||||
core,
|
||||
import('@strudel.cycles/mini'),
|
||||
import('@strudel.cycles/tonal'),
|
||||
import('@strudel.cycles/webaudio'),
|
||||
import('@strudel/codemirror'),
|
||||
import('@strudel/hydra'),
|
||||
import('@strudel.cycles/soundfonts'),
|
||||
import('@strudel.cycles/midi'),
|
||||
// import('@strudel.cycles/xen'),
|
||||
// import('@strudel.cycles/serial'),
|
||||
// import('@strudel.cycles/csound'),
|
||||
// import('@strudel.cycles/osc'),
|
||||
controls, // sadly, this cannot be exported from core directly (yet)
|
||||
);
|
||||
// load samples
|
||||
const ds = 'https://raw.githubusercontent.com/felixroos/dough-samples/main/';
|
||||
await Promise.all([
|
||||
modulesLoading,
|
||||
registerSynthSounds(),
|
||||
registerZZFXSounds(),
|
||||
//registerSoundfonts(),
|
||||
// need dynamic import here, because importing @strudel.cycles/soundfonts fails on server:
|
||||
// => 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()),
|
||||
samples(`${ds}/tidal-drum-machines.json`),
|
||||
samples(`${ds}/piano.json`),
|
||||
samples(`${ds}/Dirt-Samples.json`),
|
||||
samples(`${ds}/EmuSP12.json`),
|
||||
samples(`${ds}/vcsl.json`),
|
||||
]);
|
||||
}
|
||||
|
||||
const maxPan = noteToMidi('C8');
|
||||
const panwidth = (pan, width) => pan * width + (1 - width) / 2;
|
||||
|
||||
Pattern.prototype.piano = function () {
|
||||
return this.fmap((v) => ({ ...v, clip: v.clip ?? 1 })) // set clip if not already set..
|
||||
.s('piano')
|
||||
.release(0.1)
|
||||
.fmap((value) => {
|
||||
const midi = valueToMidi(value);
|
||||
// pan by pitch
|
||||
const pan = panwidth(Math.min(Math.round(midi) / maxPan, 1), 0.5);
|
||||
return { ...value, pan: (value.pan || 1) * pan };
|
||||
});
|
||||
};
|
||||
69
packages/repl/repl-component.mjs
Normal file
69
packages/repl/repl-component.mjs
Normal file
@ -0,0 +1,69 @@
|
||||
import { getDrawContext, silence } from '@strudel.cycles/core';
|
||||
import { transpiler } from '@strudel.cycles/transpiler';
|
||||
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||
import { StrudelMirror, codemirrorSettings } from '@strudel/codemirror';
|
||||
import { prebake } from './prebake.mjs';
|
||||
|
||||
if (typeof HTMLElement !== 'undefined') {
|
||||
class StrudelRepl extends HTMLElement {
|
||||
static observedAttributes = ['code'];
|
||||
settings = codemirrorSettings.get();
|
||||
editor = null;
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
if (name === 'code') {
|
||||
this.code = newValue;
|
||||
this.editor?.setCode(newValue);
|
||||
}
|
||||
}
|
||||
connectedCallback() {
|
||||
// setTimeout makes sure the dom is ready
|
||||
setTimeout(() => {
|
||||
const code = (this.innerHTML + '').replace('<!--', '').replace('-->', '').trim();
|
||||
if (code) {
|
||||
// use comment code in element body if present
|
||||
this.setAttribute('code', code);
|
||||
}
|
||||
});
|
||||
// use a separate container for the editor, to make sure the innerHTML stays as is
|
||||
const container = document.createElement('div');
|
||||
this.parentElement.insertBefore(container, this.nextSibling);
|
||||
const drawContext = getDrawContext();
|
||||
const drawTime = [-2, 2];
|
||||
this.editor = new StrudelMirror({
|
||||
defaultOutput: webaudioOutput,
|
||||
getTime: () => getAudioContext().currentTime,
|
||||
transpiler,
|
||||
root: container,
|
||||
initialCode: '// LOADING',
|
||||
pattern: silence,
|
||||
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,
|
||||
afterEval: ({ code }) => {
|
||||
// window.location.hash = '#' + code2hash(code);
|
||||
},
|
||||
onUpdateState: (state) => {
|
||||
const event = new CustomEvent('update', {
|
||||
detail: state,
|
||||
});
|
||||
this.dispatchEvent(event);
|
||||
},
|
||||
});
|
||||
// init settings
|
||||
this.editor.updateSettings(this.settings);
|
||||
this.editor.setCode(this.code);
|
||||
}
|
||||
// Element functionality written in here
|
||||
}
|
||||
|
||||
customElements.define('strudel-editor', StrudelRepl);
|
||||
}
|
||||
28
packages/repl/vite.config.js
Normal file
28
packages/repl/vite.config.js
Normal file
@ -0,0 +1,28 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import { dependencies } from './package.json';
|
||||
import { resolve } from 'path';
|
||||
// import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
name: 'strudel',
|
||||
formats: ['es', 'iife'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', iife: 'index.js' }[ext]),
|
||||
},
|
||||
rollupOptions: {
|
||||
// external: [...Object.keys(dependencies)],
|
||||
plugins: [
|
||||
replace({
|
||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||
preventAssignment: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
target: 'esnext',
|
||||
},
|
||||
});
|
||||
333
pnpm-lock.yaml
generated
333
pnpm-lock.yaml
generated
@ -99,6 +99,9 @@ importers:
|
||||
'@lezer/highlight':
|
||||
specifier: ^1.1.4
|
||||
version: 1.1.4
|
||||
'@nanostores/persistent':
|
||||
specifier: ^0.8.0
|
||||
version: 0.8.0(nanostores@0.8.1)
|
||||
'@replit/codemirror-emacs':
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
||||
@ -117,6 +120,9 @@ importers:
|
||||
'@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)
|
||||
nanostores:
|
||||
specifier: ^0.8.1
|
||||
version: 0.8.1
|
||||
react-dom:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
@ -125,52 +131,6 @@ importers:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3
|
||||
|
||||
packages/codemirror/examples/strudelmirror:
|
||||
dependencies:
|
||||
'@strudel.cycles/core':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core
|
||||
'@strudel.cycles/csound':
|
||||
specifier: workspace:*
|
||||
version: link:../../../csound
|
||||
'@strudel.cycles/midi':
|
||||
specifier: workspace:*
|
||||
version: link:../../../midi
|
||||
'@strudel.cycles/mini':
|
||||
specifier: workspace:*
|
||||
version: link:../../../mini
|
||||
'@strudel.cycles/osc':
|
||||
specifier: workspace:*
|
||||
version: link:../../../osc
|
||||
'@strudel.cycles/serial':
|
||||
specifier: workspace:*
|
||||
version: link:../../../serial
|
||||
'@strudel.cycles/soundfonts':
|
||||
specifier: workspace:*
|
||||
version: link:../../../soundfonts
|
||||
'@strudel.cycles/tonal':
|
||||
specifier: workspace:*
|
||||
version: link:../../../tonal
|
||||
'@strudel.cycles/transpiler':
|
||||
specifier: workspace:*
|
||||
version: link:../../../transpiler
|
||||
'@strudel.cycles/webaudio':
|
||||
specifier: workspace:*
|
||||
version: link:../../../webaudio
|
||||
'@strudel.cycles/xen':
|
||||
specifier: workspace:*
|
||||
version: link:../../../xen
|
||||
'@strudel/codemirror':
|
||||
specifier: workspace:*
|
||||
version: link:../..
|
||||
'@strudel/hydra':
|
||||
specifier: workspace:*
|
||||
version: link:../../../hydra
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^5.0.8
|
||||
version: 5.0.8
|
||||
|
||||
packages/core:
|
||||
dependencies:
|
||||
fraction.js:
|
||||
@ -468,6 +428,46 @@ importers:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3
|
||||
|
||||
packages/repl:
|
||||
dependencies:
|
||||
'@rollup/plugin-replace':
|
||||
specifier: ^5.0.5
|
||||
version: 5.0.5
|
||||
'@strudel.cycles/core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
'@strudel.cycles/midi':
|
||||
specifier: workspace:*
|
||||
version: link:../midi
|
||||
'@strudel.cycles/mini':
|
||||
specifier: workspace:*
|
||||
version: link:../mini
|
||||
'@strudel.cycles/soundfonts':
|
||||
specifier: workspace:*
|
||||
version: link:../soundfonts
|
||||
'@strudel.cycles/tonal':
|
||||
specifier: workspace:*
|
||||
version: link:../tonal
|
||||
'@strudel.cycles/transpiler':
|
||||
specifier: workspace:*
|
||||
version: link:../transpiler
|
||||
'@strudel.cycles/webaudio':
|
||||
specifier: workspace:*
|
||||
version: link:../webaudio
|
||||
'@strudel/codemirror':
|
||||
specifier: workspace:*
|
||||
version: link:../codemirror
|
||||
'@strudel/hydra':
|
||||
specifier: workspace:*
|
||||
version: link:../hydra
|
||||
rollup-plugin-visualizer:
|
||||
specifier: ^5.8.1
|
||||
version: 5.9.0
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
version: 4.5.0
|
||||
|
||||
packages/serial:
|
||||
dependencies:
|
||||
'@strudel.cycles/core':
|
||||
@ -702,6 +702,9 @@ importers:
|
||||
'@strudel/hydra':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/hydra
|
||||
'@strudel/repl':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/repl
|
||||
'@supabase/supabase-js':
|
||||
specifier: ^2.21.0
|
||||
version: 2.21.0
|
||||
@ -750,6 +753,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
react-hook-inview:
|
||||
specifier: ^4.5.0
|
||||
version: 4.5.0(react-dom@18.2.0)(react@18.2.0)
|
||||
rehype-autolink-headings:
|
||||
specifier: ^6.1.1
|
||||
version: 6.1.1
|
||||
@ -4115,6 +4121,19 @@ packages:
|
||||
rollup: 2.79.1
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-replace@5.0.5:
|
||||
resolution: {integrity: sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.0
|
||||
magic-string: 0.30.5
|
||||
dev: false
|
||||
|
||||
/@rollup/pluginutils@3.1.0(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
@ -4127,109 +4146,19 @@ packages:
|
||||
rollup: 2.79.1
|
||||
dev: true
|
||||
|
||||
/@rollup/rollup-android-arm-eabi@4.9.0:
|
||||
resolution: {integrity: sha512-+1ge/xmaJpm1KVBuIH38Z94zj9fBD+hp+/5WLaHgyY8XLq1ibxk/zj6dTXaqM2cAbYKq8jYlhHd6k05If1W5xA==}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-android-arm64@4.9.0:
|
||||
resolution: {integrity: sha512-im6hUEyQ7ZfoZdNvtwgEJvBWZYauC9KVKq1w58LG2Zfz6zMd8gRrbN+xCVoqA2hv/v6fm9lp5LFGJ3za8EQH3A==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-darwin-arm64@4.9.0:
|
||||
resolution: {integrity: sha512-u7aTMskN6Dmg1lCT0QJ+tINRt+ntUrvVkhbPfFz4bCwRZvjItx2nJtwJnJRlKMMaQCHRjrNqHRDYvE4mBm3DlQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-darwin-x64@4.9.0:
|
||||
resolution: {integrity: sha512-8FvEl3w2ExmpcOmX5RJD0yqXcVSOqAJJUJ29Lca29Ik+3zPS1yFimr2fr5JSZ4Z5gt8/d7WqycpgkX9nocijSw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-arm-gnueabihf@4.9.0:
|
||||
resolution: {integrity: sha512-lHoKYaRwd4gge+IpqJHCY+8Vc3hhdJfU6ukFnnrJasEBUvVlydP8PuwndbWfGkdgSvZhHfSEw6urrlBj0TSSfg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-arm64-gnu@4.9.0:
|
||||
resolution: {integrity: sha512-JbEPfhndYeWHfOSeh4DOFvNXrj7ls9S/2omijVsao+LBPTPayT1uKcK3dHW3MwDJ7KO11t9m2cVTqXnTKpeaiw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-arm64-musl@4.9.0:
|
||||
resolution: {integrity: sha512-ahqcSXLlcV2XUBM3/f/C6cRoh7NxYA/W7Yzuv4bDU1YscTFw7ay4LmD7l6OS8EMhTNvcrWGkEettL1Bhjf+B+w==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-riscv64-gnu@4.9.0:
|
||||
resolution: {integrity: sha512-uwvOYNtLw8gVtrExKhdFsYHA/kotURUmZYlinH2VcQxNCQJeJXnkmWgw2hI9Xgzhgu7J9QvWiq9TtTVwWMDa+w==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-x64-gnu@4.9.0:
|
||||
resolution: {integrity: sha512-m6pkSwcZZD2LCFHZX/zW2aLIISyzWLU3hrLLzQKMI12+OLEzgruTovAxY5sCZJkipklaZqPy/2bEEBNjp+Y7xg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-x64-musl@4.9.0:
|
||||
resolution: {integrity: sha512-VFAC1RDRSbU3iOF98X42KaVicAfKf0m0OvIu8dbnqhTe26Kh6Ym9JrDulz7Hbk7/9zGc41JkV02g+p3BivOdAg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-win32-arm64-msvc@4.9.0:
|
||||
resolution: {integrity: sha512-9jPgMvTKXARz4inw6jezMLA2ihDBvgIU9Ml01hjdVpOcMKyxFBJrn83KVQINnbeqDv0+HdO1c09hgZ8N0s820Q==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-win32-ia32-msvc@4.9.0:
|
||||
resolution: {integrity: sha512-WE4pT2kTXQN2bAv40Uog0AsV7/s9nT9HBWXAou8+++MBCnY51QS02KYtm6dQxxosKi1VIz/wZIrTQO5UP2EW+Q==}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-win32-x64-msvc@4.9.0:
|
||||
resolution: {integrity: sha512-aPP5Q5AqNGuT0tnuEkK/g4mnt3ZhheiXrDIiSVIHN9mcN21OyXDVbEMqmXPE7e2OplNLDkcvV+ZoGJa2ZImFgw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
/@rollup/pluginutils@5.1.0:
|
||||
resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/estree': 1.0.0
|
||||
estree-walker: 2.0.2
|
||||
picomatch: 2.3.1
|
||||
dev: false
|
||||
|
||||
/@sigstore/protobuf-specs@0.1.0:
|
||||
resolution: {integrity: sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ==}
|
||||
@ -6234,7 +6163,6 @@ packages:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
dev: true
|
||||
|
||||
/clone-buffer@1.0.0:
|
||||
resolution: {integrity: sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==}
|
||||
@ -6739,7 +6667,6 @@ packages:
|
||||
/define-lazy-prop@2.0.0:
|
||||
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/define-properties@1.1.4:
|
||||
resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
|
||||
@ -7547,6 +7474,10 @@ packages:
|
||||
resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==}
|
||||
dev: true
|
||||
|
||||
/estree-walker@2.0.2:
|
||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||
dev: false
|
||||
|
||||
/estree-walker@3.0.3:
|
||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||
dependencies:
|
||||
@ -8003,7 +7934,6 @@ packages:
|
||||
/get-caller-file@2.0.5:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
dev: true
|
||||
|
||||
/get-func-name@2.0.0:
|
||||
resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
|
||||
@ -8898,7 +8828,6 @@ packages:
|
||||
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/is-docker@3.0.0:
|
||||
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
|
||||
@ -9125,7 +9054,6 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
is-docker: 2.2.1
|
||||
dev: true
|
||||
|
||||
/is-wsl@3.1.0:
|
||||
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
|
||||
@ -11259,7 +11187,6 @@ packages:
|
||||
define-lazy-prop: 2.0.0
|
||||
is-docker: 2.2.1
|
||||
is-wsl: 2.2.0
|
||||
dev: true
|
||||
|
||||
/optionator@0.8.3:
|
||||
resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==}
|
||||
@ -12525,7 +12452,6 @@ packages:
|
||||
/require-directory@2.1.1:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/require-from-string@2.0.2:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
@ -12697,7 +12623,6 @@ packages:
|
||||
picomatch: 2.3.1
|
||||
source-map: 0.7.4
|
||||
yargs: 17.6.2
|
||||
dev: true
|
||||
|
||||
/rollup@2.79.1:
|
||||
resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==}
|
||||
@ -12722,27 +12647,6 @@ packages:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
/rollup@4.9.0:
|
||||
resolution: {integrity: sha512-bUHW/9N21z64gw8s6tP4c88P382Bq/L5uZDowHlHx6s/QWpjJXivIAbEw6LZthgSvlEizZBfLC4OAvWe7aoF7A==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
optionalDependencies:
|
||||
'@rollup/rollup-android-arm-eabi': 4.9.0
|
||||
'@rollup/rollup-android-arm64': 4.9.0
|
||||
'@rollup/rollup-darwin-arm64': 4.9.0
|
||||
'@rollup/rollup-darwin-x64': 4.9.0
|
||||
'@rollup/rollup-linux-arm-gnueabihf': 4.9.0
|
||||
'@rollup/rollup-linux-arm64-gnu': 4.9.0
|
||||
'@rollup/rollup-linux-arm64-musl': 4.9.0
|
||||
'@rollup/rollup-linux-riscv64-gnu': 4.9.0
|
||||
'@rollup/rollup-linux-x64-gnu': 4.9.0
|
||||
'@rollup/rollup-linux-x64-musl': 4.9.0
|
||||
'@rollup/rollup-win32-arm64-msvc': 4.9.0
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.9.0
|
||||
'@rollup/rollup-win32-x64-msvc': 4.9.0
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/run-async@2.4.1:
|
||||
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
@ -14331,6 +14235,41 @@ packages:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/vite@4.5.0:
|
||||
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': '>= 14'
|
||||
less: '*'
|
||||
lightningcss: ^1.21.0
|
||||
sass: '*'
|
||||
stylus: '*'
|
||||
sugarss: '*'
|
||||
terser: ^5.4.0
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
lightningcss:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
dependencies:
|
||||
esbuild: 0.18.20
|
||||
postcss: 8.4.32
|
||||
rollup: 3.28.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/vite@4.5.0(@types/node@18.16.3):
|
||||
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
@ -14366,41 +14305,6 @@ packages:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
/vite@5.0.8:
|
||||
resolution: {integrity: sha512-jYMALd8aeqR3yS9xlHd0OzQJndS9fH5ylVgWdB+pxTwxLKdO1pgC5Dlb398BUxpfaBxa4M9oT7j1g503Gaj5IQ==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': ^18.0.0 || >=20.0.0
|
||||
less: '*'
|
||||
lightningcss: ^1.21.0
|
||||
sass: '*'
|
||||
stylus: '*'
|
||||
sugarss: '*'
|
||||
terser: ^5.4.0
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
lightningcss:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
dependencies:
|
||||
esbuild: 0.19.5
|
||||
postcss: 8.4.32
|
||||
rollup: 4.9.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/vitefu@0.2.4(vite@4.5.0):
|
||||
resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==}
|
||||
peerDependencies:
|
||||
@ -14815,7 +14719,6 @@ packages:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
dev: true
|
||||
|
||||
/wrap-ansi@8.1.0:
|
||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||
@ -14898,7 +14801,6 @@ packages:
|
||||
/y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/yaeti@0.0.6:
|
||||
resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==}
|
||||
@ -14958,7 +14860,6 @@ packages:
|
||||
string-width: 4.2.3
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.1.1
|
||||
dev: true
|
||||
|
||||
/yocto-queue@0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
|
||||
@ -7,4 +7,3 @@ packages:
|
||||
- "packages/react/examples/nano-repl"
|
||||
- "packages/web/examples/repl-example"
|
||||
- "packages/superdough/example"
|
||||
- "packages/codemirror/examples/strudelmirror"
|
||||
|
||||
@ -34,9 +34,10 @@
|
||||
"@strudel.cycles/transpiler": "workspace:*",
|
||||
"@strudel.cycles/webaudio": "workspace:*",
|
||||
"@strudel.cycles/xen": "workspace:*",
|
||||
"@strudel/hydra": "workspace:*",
|
||||
"@strudel/codemirror": "workspace:*",
|
||||
"@strudel/desktopbridge": "workspace:*",
|
||||
"@strudel/hydra": "workspace:*",
|
||||
"@strudel/repl": "workspace:*",
|
||||
"@supabase/supabase-js": "^2.21.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
@ -53,6 +54,7 @@
|
||||
"nanostores": "^0.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-inview": "^4.5.0",
|
||||
"rehype-autolink-headings": "^6.1.1",
|
||||
"rehype-slug": "^5.0.1",
|
||||
"rehype-urls": "^1.1.1",
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
---
|
||||
import { pwaInfo } from 'virtual:pwa-info';
|
||||
import '../styles/index.css';
|
||||
|
||||
const { BASE_URL } = import.meta.env;
|
||||
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
|
||||
---
|
||||
|
||||
<!-- Global Metadata -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href={`${baseNoTrailing}/favicon.ico`} />
|
||||
|
||||
<meta
|
||||
name="description"
|
||||
content="Strudel is a music live coding environment for the browser, porting the TidalCycles pattern language to JavaScript."
|
||||
/>
|
||||
<link rel="icon" href={`${baseNoTrailing}/favicon.ico`} />
|
||||
<link rel="apple-touch-icon" href={`${baseNoTrailing}/icons/apple-icon-180.png`} sizes="180x180" />
|
||||
<meta name="theme-color" content="#222222" />
|
||||
|
||||
<base href={BASE_URL} />
|
||||
|
||||
<!-- Scrollable a11y code helper -->
|
||||
<script src{`${baseNoTrailing}/make-scrollable-code-focusable.js`} is:inline></script>
|
||||
|
||||
<script src="/src/pwa.ts"></script>
|
||||
<!-- this does not work for some reason: -->
|
||||
<!-- <style is:global define:vars={strudelTheme}></style> -->
|
||||
<!-- the following variables are just a fallback to make sure everything is readable without JS -->
|
||||
<style is:global>
|
||||
:root {
|
||||
--background: #222;
|
||||
--lineBackground: #22222299;
|
||||
--foreground: #fff;
|
||||
--caret: #ffcc00;
|
||||
--selection: rgba(128, 203, 196, 0.5);
|
||||
--selectionMatch: #036dd626;
|
||||
--lineHighlight: #00000050;
|
||||
--gutterBackground: transparent;
|
||||
--gutterForeground: #8a919966;
|
||||
}
|
||||
</style>
|
||||
{pwaInfo && <Fragment set:html={pwaInfo.webManifest.linkTag} />}
|
||||
|
||||
<script>
|
||||
// https://medium.com/quick-code/100vh-problem-with-ios-safari-92ab23c852a8
|
||||
const appHeight = () => {
|
||||
const doc = document.documentElement;
|
||||
doc.style.setProperty('--app-height', `${window.innerHeight - 1}px`);
|
||||
};
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('resize', appHeight);
|
||||
appHeight();
|
||||
}
|
||||
</script>
|
||||
38
website/src/docs/Icon.jsx
Normal file
38
website/src/docs/Icon.jsx
Normal file
@ -0,0 +1,38 @@
|
||||
export function Icon({ type }) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
{
|
||||
{
|
||||
refresh: (
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
),
|
||||
play: (
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
),
|
||||
pause: (
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
),
|
||||
stop: (
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M2 10a8 8 0 1116 0 8 8 0 01-16 0zm5-2.25A.75.75 0 017.75 7h4.5a.75.75 0 01.75.75v4.5a.75.75 0 01-.75.75h-4.5a.75.75 0 01-.75-.75v-4.5z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
),
|
||||
}[type]
|
||||
}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
151
website/src/docs/MicroRepl.jsx
Normal file
151
website/src/docs/MicroRepl.jsx
Normal file
@ -0,0 +1,151 @@
|
||||
import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
|
||||
import { Icon } from './Icon';
|
||||
import { silence, getPunchcardPainter } from '@strudel.cycles/core';
|
||||
import { transpiler } from '@strudel.cycles/transpiler';
|
||||
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||
import { StrudelMirror } from '@strudel/codemirror';
|
||||
import { prebake } from '@strudel/repl';
|
||||
|
||||
export function MicroRepl({
|
||||
code,
|
||||
hideHeader = false,
|
||||
canvasHeight = 100,
|
||||
onTrigger,
|
||||
onPaint,
|
||||
punchcard,
|
||||
punchcardLabels = true,
|
||||
}) {
|
||||
const id = useMemo(() => s4(), []);
|
||||
const canvasId = useMemo(() => `canvas-${id}`, [id]);
|
||||
const shouldDraw = !!punchcard;
|
||||
|
||||
const init = useCallback(({ code, shouldDraw }) => {
|
||||
const drawTime = [0, 4];
|
||||
const drawContext = shouldDraw ? document.querySelector('#' + canvasId)?.getContext('2d') : null;
|
||||
let onDraw;
|
||||
if (shouldDraw) {
|
||||
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 });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const editor = new StrudelMirror({
|
||||
id,
|
||||
defaultOutput: webaudioOutput,
|
||||
getTime: () => getAudioContext().currentTime,
|
||||
transpiler,
|
||||
autodraw: !!shouldDraw,
|
||||
root: containerRef.current,
|
||||
initialCode: '// LOADING',
|
||||
pattern: silence,
|
||||
drawTime,
|
||||
onDraw,
|
||||
editPattern: (pat, id) => {
|
||||
if (onTrigger) {
|
||||
pat = pat.onTrigger(onTrigger, false);
|
||||
}
|
||||
if (onPaint) {
|
||||
editor.painters.push(onPaint);
|
||||
} else if (punchcard) {
|
||||
editor.painters.push(getPunchcardPainter({ labels: !!punchcardLabels }));
|
||||
}
|
||||
return pat;
|
||||
},
|
||||
prebake,
|
||||
onUpdateState: (state) => {
|
||||
setReplState({ ...state });
|
||||
},
|
||||
});
|
||||
// init settings
|
||||
editor.setCode(code);
|
||||
editorRef.current = editor;
|
||||
}, []);
|
||||
|
||||
const [replState, setReplState] = useState({});
|
||||
const { started, isDirty, error } = replState;
|
||||
const editorRef = useRef();
|
||||
const containerRef = useRef();
|
||||
const [client, setClient] = useState(false);
|
||||
useEffect(() => {
|
||||
setClient(true);
|
||||
if (!editorRef.current) {
|
||||
setTimeout(() => {
|
||||
init({ code, shouldDraw });
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
editor.clear();
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!client) {
|
||||
return <pre>{code}</pre>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-t-md bg-background border border-lineHighlight">
|
||||
{!hideHeader && (
|
||||
<div className="flex justify-between bg-lineHighlight">
|
||||
<div className="flex">
|
||||
<button
|
||||
className={cx(
|
||||
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background',
|
||||
started ? 'animate-pulse' : '',
|
||||
)}
|
||||
onClick={() => editorRef.current?.toggle()}
|
||||
>
|
||||
<Icon type={started ? 'stop' : 'play'} />
|
||||
</button>
|
||||
<button
|
||||
className={cx(
|
||||
'w-16 flex items-center justify-center p-1 text-foreground border-lineHighlight bg-lineHighlight',
|
||||
isDirty ? 'text-foreground hover:bg-background cursor-pointer' : 'opacity-50 cursor-not-allowed',
|
||||
)}
|
||||
onClick={() => editorRef.current?.evaluate()}
|
||||
>
|
||||
<Icon type="refresh" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="overflow-auto relative p-1">
|
||||
<div ref={containerRef}></div>
|
||||
{error && <div className="text-right p-1 text-md text-red-200">{error.message}</div>}
|
||||
</div>
|
||||
{shouldDraw && (
|
||||
<canvas
|
||||
id={canvasId}
|
||||
className="w-full pointer-events-none border-t border-lineHighlight"
|
||||
height={canvasHeight}
|
||||
ref={(el) => {
|
||||
if (el && el.width !== el.clientWidth) {
|
||||
el.width = el.clientWidth;
|
||||
}
|
||||
}}
|
||||
></canvas>
|
||||
)}
|
||||
{/* !!log.length && (
|
||||
<div className="bg-gray-800 rounded-md p-2">
|
||||
{log.map(({ message }, i) => (
|
||||
<div key={i}>{message}</div>
|
||||
))}
|
||||
</div>
|
||||
) */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function cx(...classes) {
|
||||
// : Array<string | undefined>
|
||||
return classes.filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
}
|
||||
312
website/src/pages/recipes/recipes-next.mdx
Normal file
312
website/src/pages/recipes/recipes-next.mdx
Normal file
@ -0,0 +1,312 @@
|
||||
---
|
||||
title: Recipes
|
||||
layout: ../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MicroRepl } from '../../docs/MicroRepl';
|
||||
|
||||
# Recipes
|
||||
|
||||
This page shows possible ways to achieve common (or not so common) musical goals.
|
||||
There are often many ways to do a thing and there is no right or wrong.
|
||||
The fun part is that each representation will give you different impulses when improvising.
|
||||
|
||||
## Arpeggios
|
||||
|
||||
An arpeggio is when the notes of a chord are played in sequence.
|
||||
We can either write the notes by hand:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`note("c eb g c4")
|
||||
.clip(2).s("gm_electric_guitar_clean")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
...or use scales:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`n("0 2 4 7").scale("C:minor")
|
||||
.clip(2).s("gm_electric_guitar_clean")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
...or chord symbols:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`n("0 1 2 3").chord("Cm").mode("above:c3").voicing()
|
||||
.clip(2).s("gm_electric_guitar_clean")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
...using off:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`"0"
|
||||
.off(1/3, add(2))
|
||||
.off(1/2, add(4))
|
||||
.n()
|
||||
.scale("C:minor")
|
||||
.s("gm_electric_guitar_clean")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
## Chopping Breaks
|
||||
|
||||
A sample can be looped and chopped like this:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`await samples('github:yaxu/clean-breaks/main')
|
||||
s("amen/8").fit().chop(16)`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
This fits the break into 8 cycles + chops it in 16 pieces.
|
||||
The chops are not audible yet, because we're not doing any manipulation.
|
||||
Let's add randmized doubling + reversing:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`await samples('github:yaxu/clean-breaks/main')
|
||||
s("amen/8").fit().chop(16).cut(1)
|
||||
.sometimesBy(.5, ply(2))
|
||||
.sometimesBy(.25, mul(speed(-1)))`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
If we want to specify the order of samples, we can replace `chop` with `slice`:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`await samples('github:yaxu/clean-breaks/main')
|
||||
s("amen/8").fit()
|
||||
.slice(8, "<0 1 2 3 4*2 5 6 [6 7]>")
|
||||
.cut(1).rarely(ply(2))`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
If we use `splice` instead of `slice`, the speed adjusts to the duration of the event:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`await samples('github:yaxu/clean-breaks/main')
|
||||
s("amen")
|
||||
.splice(8, "<0 1 2 3 4*2 5 6 [6 7]>")
|
||||
.cut(1).rarely(ply(2))`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
Note that we don't need `fit`, because `splice` will do that by itself.
|
||||
|
||||
## Filter Envelopes
|
||||
|
||||
A minimal filter envelope looks like this:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`note("g1 bb1 <c2 eb2> d2")
|
||||
.s("sawtooth")
|
||||
.lpf(400).lpa(.2).lpenv(4)
|
||||
.scope()`}
|
||||
/>
|
||||
|
||||
We can flip the envelope by setting `lpenv` negative + add some resonance `lpq`:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`note("g1 bb1 <c2 eb2> d2")
|
||||
.s("sawtooth").lpq(8)
|
||||
.lpf(400).lpa(.2).lpenv(-4)
|
||||
.scope()`}
|
||||
/>
|
||||
|
||||
## Layering Sounds
|
||||
|
||||
We can layer sounds by separating them with ",":
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`note("<g1 bb1 d2 f1>")
|
||||
.s("sawtooth, square") // <------
|
||||
.scope()`}
|
||||
/>
|
||||
|
||||
We can control the gain of individual sounds like this:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`note("<g1 bb1 d2 f1>")
|
||||
.s("sawtooth, square:0:.5") // <--- "name:number:gain"
|
||||
.scope()`}
|
||||
/>
|
||||
|
||||
For more control over each voice, we can use `layer`:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`note("<g1 bb1 d2 f1>").layer(
|
||||
x=>x.s("sawtooth").vib(4),
|
||||
x=>x.s("square").add(note(12))
|
||||
).scope()`}
|
||||
/>
|
||||
|
||||
Here, we give the sawtooth a vibrato and the square is moved an octave up.
|
||||
With `layer`, you can use any pattern method available on each voice, so sky is the limit..
|
||||
|
||||
## Oscillator Detune
|
||||
|
||||
We can fatten a sound by adding a detuned version to itself:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`note("<g1 bb1 d2 f1>")
|
||||
.add(note("0,.1")) // <------ chorus
|
||||
.s("sawtooth").scope()`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
Try out different values, or add another voice!
|
||||
|
||||
## Polyrhythms
|
||||
|
||||
Here is a simple example of a polyrhythm:
|
||||
|
||||
<MicroRepl client:visible code={`s("bd*2,hh*3")`} punchcard />
|
||||
|
||||
A polyrhythm is when 2 different tempos happen at the same time.
|
||||
|
||||
## Polymeter
|
||||
|
||||
This is a polymeter:
|
||||
|
||||
<MicroRepl client:visible code={`s("<bd rim>,<hh hh oh>").fast(2)`} punchcard />
|
||||
|
||||
A polymeter is when 2 different bar lengths play at the same tempo.
|
||||
|
||||
## Phasing
|
||||
|
||||
This is a phasing:
|
||||
|
||||
<MicroRepl client:visible code={`note("<C D G A Bb D C A G D Bb A>*[6,6.1]").piano()`} punchcard />
|
||||
|
||||
Phasing happens when the same sequence plays at slightly different tempos.
|
||||
|
||||
## Running through samples
|
||||
|
||||
Using `run` with `n`, we can rush through a sample bank:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`await samples('github:Bubobubobubobubo/Dough-Fox/main')
|
||||
n(run(8)).s("ftabla")`}
|
||||
punchcard
|
||||
/>
|
||||
|
||||
This works great with sample banks that contain similar sounds, like in this case different recordings of a tabla.
|
||||
Often times, you'll hear the beginning of the phrase not where the pattern begins.
|
||||
In this case, I hear the beginning at the third sample, which can be accounted for with `early`.
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`await samples('github:Bubobubobubobubo/Dough-Fox/main')
|
||||
n(run(8)).s("ftabla").early(2/8)`}
|
||||
/>
|
||||
|
||||
Let's add some randomness:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`await samples('github:Bubobubobubobubo/Dough-Fox/main')
|
||||
n(run(8)).s("ftabla").early(2/8)
|
||||
.sometimes(mul(speed(1.5)))`}
|
||||
/>
|
||||
|
||||
## Tape Warble
|
||||
|
||||
We can emulate a pitch warbling effect like this:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`note("c4 bb f eb")
|
||||
.add(note(perlin.range(0,.5))) // <------ warble
|
||||
.clip(2).s("gm_electric_guitar_clean")`}
|
||||
/>
|
||||
|
||||
## Sound Duration
|
||||
|
||||
There are a number of ways to change the sound duration. Using clip:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`note("f ab bb c")
|
||||
.clip("<2 1 .5 .25>/2")`}
|
||||
/>
|
||||
|
||||
The value of clip is relative to the duration of each event.
|
||||
We can also create overlaps using release:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`note("f ab bb c")
|
||||
.release("<2 1 .5 .002>/2")`}
|
||||
/>
|
||||
|
||||
This will smoothly fade out each sound for the given number of seconds.
|
||||
We could also make the notes shorter with decay / sustain:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`note("f ab bb c")
|
||||
.decay("<.2 .1 .02>/2").sustain(0)`}
|
||||
/>
|
||||
|
||||
For now, there is a limitation where decay values that exceed the event duration may cause little cracks, so use higher numbers with caution..
|
||||
|
||||
When using samples, we also have `.end` to cut relative to the sample length:
|
||||
|
||||
<MicroRepl client:visible code={`s("oh*4").end("<1 .5 .25 .1>")`} />
|
||||
|
||||
Compare that to clip:
|
||||
|
||||
<MicroRepl client:visible code={`s("oh*4").clip("<1 .5 .25 .1>")`} />
|
||||
|
||||
or decay / sustain
|
||||
|
||||
<MicroRepl client:visible code={`s("oh*4").decay("<.2 .12 .06 .01>").sustain(0)`} />
|
||||
|
||||
## Wavetable Synthesis
|
||||
|
||||
You can loop a sample with `loop` / `loopEnd`:
|
||||
|
||||
<MicroRepl client:visible code={`note("<c eb g f>").s("bd").loop(1).loopEnd(.05).gain(.2)`} />
|
||||
|
||||
This allows us to play the first 5% of the bass drum as a synth!
|
||||
To simplify loading wavetables, any sample that starts with `wt_` will be looped automatically:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`await samples('github:bubobubobubobubo/dough-waveforms/main')
|
||||
note("c eb g bb").s("wt_dbass").clip(2)`}
|
||||
/>
|
||||
|
||||
Running through different wavetables can also give interesting variations:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`await samples('github:bubobubobubobubo/dough-waveforms/main')
|
||||
note("c2*8").s("wt_dbass").n(run(8))`}
|
||||
/>
|
||||
|
||||
...adding a filter envelope + reverb:
|
||||
|
||||
<MicroRepl
|
||||
client:visible
|
||||
code={`await samples('github:bubobubobubobubo/dough-waveforms/main')
|
||||
note("c2*8").s("wt_dbass").n(run(8))
|
||||
.lpf(perlin.range(200,2000).slow(8))
|
||||
.lpenv(-3).lpa(.1).room(.5)`}
|
||||
/>
|
||||
@ -1,10 +1,5 @@
|
||||
---
|
||||
import HeadCommonNew from '../../components/HeadCommonNew.astro';
|
||||
---
|
||||
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<HeadCommonNew />
|
||||
<title>Strudel Vanilla REPL</title>
|
||||
</head>
|
||||
<body class="h-app-height">
|
||||
@ -90,7 +85,8 @@ import HeadCommonNew from '../../components/HeadCommonNew.astro';
|
||||
<!-- <label><input type="checkbox" name="isTooltipEnabled" />isTooltipEnabled</label> -->
|
||||
</form>
|
||||
</div>
|
||||
<div id="code"></div>
|
||||
<strudel-editor id="editor"></strudel-editor>
|
||||
<script src="../../repl/vanilla/vanilla.mjs"></script>
|
||||
<script src="@strudel/repl"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -12,7 +12,7 @@ select {
|
||||
|
||||
html,
|
||||
body,
|
||||
#code,
|
||||
#editor,
|
||||
.cm-editor,
|
||||
.cm-scroller {
|
||||
padding: 0;
|
||||
|
||||
@ -1,94 +1,12 @@
|
||||
import { logger, getDrawContext, silence, controls, evalScope, hash2code, code2hash } from '@strudel.cycles/core';
|
||||
import { StrudelMirror, initTheme, activateTheme } from '@strudel/codemirror';
|
||||
import { transpiler } from '@strudel.cycles/transpiler';
|
||||
import {
|
||||
getAudioContext,
|
||||
webaudioOutput,
|
||||
registerSynthSounds,
|
||||
registerZZFXSounds,
|
||||
samples,
|
||||
} from '@strudel.cycles/webaudio';
|
||||
import { hash2code, logger } from '@strudel.cycles/core';
|
||||
import { codemirrorSettings, defaultSettings } from '@strudel/codemirror';
|
||||
import './vanilla.css';
|
||||
|
||||
let editor;
|
||||
const initialSettings = {
|
||||
keybindings: 'codemirror',
|
||||
isLineNumbersDisplayed: true,
|
||||
isActiveLineHighlighted: true,
|
||||
isAutoCompletionEnabled: false,
|
||||
isPatternHighlightingEnabled: true,
|
||||
isFlashEnabled: true,
|
||||
isTooltipEnabled: false,
|
||||
isLineWrappingEnabled: false,
|
||||
theme: 'teletext',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 18,
|
||||
};
|
||||
initTheme(initialSettings.theme);
|
||||
|
||||
async function run() {
|
||||
const container = document.getElementById('code');
|
||||
if (!container) {
|
||||
console.warn('could not init: no container found');
|
||||
return;
|
||||
}
|
||||
|
||||
const drawContext = getDrawContext();
|
||||
const drawTime = [-2, 2];
|
||||
editor = new StrudelMirror({
|
||||
defaultOutput: webaudioOutput,
|
||||
getTime: () => getAudioContext().currentTime,
|
||||
transpiler,
|
||||
root: container,
|
||||
initialCode: '// LOADING',
|
||||
pattern: silence,
|
||||
settings: initialSettings,
|
||||
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: async () => {
|
||||
// populate scope / lazy load modules
|
||||
const modulesLoading = evalScope(
|
||||
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'),
|
||||
/* import('@strudel.cycles/midi'), */
|
||||
// import('@strudel.cycles/osc'),
|
||||
controls, // sadly, this cannot be exported from core directly (yet)
|
||||
);
|
||||
// load samples
|
||||
const ds = 'https://raw.githubusercontent.com/felixroos/dough-samples/main/';
|
||||
await Promise.all([
|
||||
modulesLoading,
|
||||
registerSynthSounds(),
|
||||
registerZZFXSounds(),
|
||||
samples(`${ds}/tidal-drum-machines.json`),
|
||||
samples(`${ds}/piano.json`),
|
||||
samples(`${ds}/Dirt-Samples.json`),
|
||||
samples(`${ds}/EmuSP12.json`),
|
||||
samples(`${ds}/vcsl.json`),
|
||||
]);
|
||||
},
|
||||
afterEval: ({ code }) => {
|
||||
window.location.hash = '#' + code2hash(code);
|
||||
},
|
||||
});
|
||||
|
||||
// init settings
|
||||
editor.updateSettings(initialSettings);
|
||||
|
||||
const repl = document.getElementById('editor');
|
||||
editor = repl.editor;
|
||||
logger(`Welcome to Strudel! Click into the editor and then hit ctrl+enter to run the code!`, 'highlight');
|
||||
const codeParam = window.location.href.split('#')[1] || '';
|
||||
|
||||
@ -192,11 +110,8 @@ function setFormValues(form, values) {
|
||||
}
|
||||
|
||||
const form = document.querySelector('form[name=settings]');
|
||||
setFormValues(form, initialSettings);
|
||||
setFormValues(form, codemirrorSettings.get());
|
||||
form.addEventListener('change', () => {
|
||||
const values = getFormValues(form, initialSettings);
|
||||
// console.log('values', values);
|
||||
editor.updateSettings(values);
|
||||
// TODO: only activateTheme when it changes
|
||||
activateTheme(values.theme);
|
||||
const values = getFormValues(form, defaultSettings);
|
||||
editor?.updateSettings(values);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user