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
29 changed files with 995 additions and 771 deletions
|
|
@ -10,8 +10,9 @@ import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core';
|
||||||
import { flash, isFlashEnabled } from './flash.mjs';
|
import { flash, isFlashEnabled } from './flash.mjs';
|
||||||
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
|
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
|
||||||
import { keybindings } from './keybindings.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 { updateWidgets, sliderPlugin } from './slider.mjs';
|
||||||
|
import { persistentAtom } from '@nanostores/persistent';
|
||||||
|
|
||||||
const extensions = {
|
const extensions = {
|
||||||
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
|
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
|
||||||
|
|
@ -25,11 +26,32 @@ const extensions = {
|
||||||
};
|
};
|
||||||
const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
|
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/
|
// 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) =>
|
const initialSettings = Object.keys(compartments).map((key) =>
|
||||||
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
|
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
|
||||||
);
|
);
|
||||||
|
initTheme(settings.theme);
|
||||||
let state = EditorState.create({
|
let state = EditorState.create({
|
||||||
doc: initialCode,
|
doc: initialCode,
|
||||||
extensions: [
|
extensions: [
|
||||||
|
|
@ -86,7 +108,7 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, set
|
||||||
|
|
||||||
export class StrudelMirror {
|
export class StrudelMirror {
|
||||||
constructor(options) {
|
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.code = initialCode;
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.miniLocations = [];
|
this.miniLocations = [];
|
||||||
|
|
@ -94,6 +116,7 @@ export class StrudelMirror {
|
||||||
this.painters = [];
|
this.painters = [];
|
||||||
this.onDraw = onDraw;
|
this.onDraw = onDraw;
|
||||||
const self = this;
|
const self = this;
|
||||||
|
this.id = id || s4();
|
||||||
|
|
||||||
this.drawer = new Drawer((haps, time) => {
|
this.drawer = new Drawer((haps, time) => {
|
||||||
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
|
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);
|
this.onDraw?.(haps, time, currentFrame, this.painters);
|
||||||
}, drawTime);
|
}, 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) {
|
Pattern.prototype.onPaint = function (onPaint) {
|
||||||
self.painters.push(onPaint);
|
self.painters.push(onPaint);
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.prebaked = prebake();
|
this.prebaked = prebake();
|
||||||
// this.drawFirstFrame();
|
autodraw && this.drawFirstFrame();
|
||||||
|
|
||||||
this.repl = repl({
|
this.repl = repl({
|
||||||
...replOptions,
|
...replOptions,
|
||||||
|
|
@ -116,6 +140,12 @@ export class StrudelMirror {
|
||||||
replOptions?.onToggle?.(started);
|
replOptions?.onToggle?.(started);
|
||||||
if (started) {
|
if (started) {
|
||||||
this.drawer.start(this.repl.scheduler);
|
this.drawer.start(this.repl.scheduler);
|
||||||
|
// stop other repls when this one is started
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent('start-repl', {
|
||||||
|
detail: this.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.drawer.stop();
|
this.drawer.stop();
|
||||||
updateMiniLocations(this.editor, []);
|
updateMiniLocations(this.editor, []);
|
||||||
|
|
@ -140,13 +170,11 @@ export class StrudelMirror {
|
||||||
});
|
});
|
||||||
this.editor = initEditor({
|
this.editor = initEditor({
|
||||||
root,
|
root,
|
||||||
settings,
|
|
||||||
initialCode,
|
initialCode,
|
||||||
onChange: (v) => {
|
onChange: (v) => {
|
||||||
if (v.docChanged) {
|
if (v.docChanged) {
|
||||||
this.code = v.state.doc.toString();
|
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(),
|
onEvaluate: () => this.evaluate(),
|
||||||
|
|
@ -154,9 +182,17 @@ export class StrudelMirror {
|
||||||
});
|
});
|
||||||
const cmEditor = this.root.querySelector('.cm-editor');
|
const cmEditor = this.root.querySelector('.cm-editor');
|
||||||
if (cmEditor) {
|
if (cmEditor) {
|
||||||
|
this.root.style.display = 'block';
|
||||||
this.root.style.backgroundColor = 'var(--background)';
|
this.root.style.backgroundColor = 'var(--background)';
|
||||||
cmEditor.style.backgroundColor = 'transparent';
|
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() {
|
async drawFirstFrame() {
|
||||||
if (!this.onDraw) {
|
if (!this.onDraw) {
|
||||||
|
|
@ -166,8 +202,9 @@ export class StrudelMirror {
|
||||||
await this.prebaked;
|
await this.prebaked;
|
||||||
try {
|
try {
|
||||||
await this.repl.evaluate(this.code, false);
|
await this.repl.evaluate(this.code, false);
|
||||||
this.drawer.invalidate(this.repl.scheduler);
|
this.drawer.invalidate(this.repl.scheduler, -0.001);
|
||||||
this.onDraw?.(this.drawer.visibleHaps, 0, []);
|
// 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) {
|
} catch (err) {
|
||||||
console.warn('first frame could not be painted');
|
console.warn('first frame could not be painted');
|
||||||
}
|
}
|
||||||
|
|
@ -181,7 +218,7 @@ export class StrudelMirror {
|
||||||
}
|
}
|
||||||
async toggle() {
|
async toggle() {
|
||||||
if (this.repl.scheduler.started) {
|
if (this.repl.scheduler.started) {
|
||||||
this.repl.scheduler.stop();
|
this.repl.stop();
|
||||||
} else {
|
} else {
|
||||||
this.evaluate();
|
this.evaluate();
|
||||||
}
|
}
|
||||||
|
|
@ -212,6 +249,9 @@ export class StrudelMirror {
|
||||||
this.editor.dispatch({
|
this.editor.dispatch({
|
||||||
effects: compartments[key].reconfigure(newValue),
|
effects: compartments[key].reconfigure(newValue),
|
||||||
});
|
});
|
||||||
|
if (key === 'theme') {
|
||||||
|
activateTheme(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setLineWrappingEnabled(enabled) {
|
setLineWrappingEnabled(enabled) {
|
||||||
this.reconfigureExtension('isLineWrappingEnabled', enabled);
|
this.reconfigureExtension('isLineWrappingEnabled', enabled);
|
||||||
|
|
@ -231,6 +271,8 @@ export class StrudelMirror {
|
||||||
for (let key in extensions) {
|
for (let key in extensions) {
|
||||||
this.reconfigureExtension(key, settings[key]);
|
this.reconfigureExtension(key, settings[key]);
|
||||||
}
|
}
|
||||||
|
const updated = { ...codemirrorSettings.get(), ...settings };
|
||||||
|
codemirrorSettings.set(updated);
|
||||||
}
|
}
|
||||||
changeSetting(key, value) {
|
changeSetting(key, value) {
|
||||||
if (extensions[key]) {
|
if (extensions[key]) {
|
||||||
|
|
@ -246,8 +288,18 @@ export class StrudelMirror {
|
||||||
const changes = { from: 0, to: this.editor.state.doc.length, insert: code };
|
const changes = { from: 0, to: this.editor.state.doc.length, insert: code };
|
||||||
this.editor.dispatch({ changes });
|
this.editor.dispatch({ changes });
|
||||||
}
|
}
|
||||||
|
clear() {
|
||||||
|
this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseBooleans(value) {
|
function parseBooleans(value) {
|
||||||
return { true: true, false: false }[value] ?? 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:*",
|
"@strudel.cycles/core": "workspace:*",
|
||||||
"@uiw/codemirror-themes": "^4.19.16",
|
"@uiw/codemirror-themes": "^4.19.16",
|
||||||
"@uiw/codemirror-themes-all": "^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": {
|
"devDependencies": {
|
||||||
"vite": "^4.3.3"
|
"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);
|
return () => styleSheet.deleteRule(ruleIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentTheme, resetThemeStyle, themeStyle;
|
let currentTheme,
|
||||||
|
resetThemeStyle,
|
||||||
|
themeStyle,
|
||||||
|
styleID = 'strudel-theme-vars';
|
||||||
export function initTheme(theme) {
|
export function initTheme(theme) {
|
||||||
themeStyle = document.createElement('style');
|
if (!document.getElementById(styleID)) {
|
||||||
themeStyle.id = 'strudel-theme';
|
themeStyle = document.createElement('style');
|
||||||
document.head.append(themeStyle);
|
themeStyle.id = styleID;
|
||||||
|
document.head.append(themeStyle);
|
||||||
|
}
|
||||||
activateTheme(theme);
|
activateTheme(theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -496,6 +501,7 @@ export function activateTheme(name) {
|
||||||
if (currentTheme === name) {
|
if (currentTheme === name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
currentTheme = name;
|
||||||
if (!settings[name]) {
|
if (!settings[name]) {
|
||||||
console.warn('theme', name, 'has no settings.. defaulting to strudelTheme settings');
|
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) {
|
if (!scheduler) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// TODO: scheduler.now() seems to move even when it's stopped, this hints at a bug...
|
||||||
|
t = t ?? scheduler.now();
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
const t = scheduler.now();
|
|
||||||
let [_, lookahead] = this.drawTime;
|
let [_, lookahead] = this.drawTime;
|
||||||
const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1];
|
const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1];
|
||||||
// remove all future haps
|
// remove all future haps
|
||||||
|
|
|
||||||
|
|
@ -256,10 +256,13 @@ export function getDrawOptions(drawTime, options = {}) {
|
||||||
return { fold: 1, ...options, cycles, playhead };
|
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) {
|
Pattern.prototype.punchcard = function (options) {
|
||||||
return this.onPaint((ctx, time, haps, drawTime, paintOptions = {}) =>
|
return this.onPaint(getPunchcardPainter(options));
|
||||||
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, { ...paintOptions, ...options }) }),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,36 @@ export function repl({
|
||||||
transpiler,
|
transpiler,
|
||||||
onToggle,
|
onToggle,
|
||||||
editPattern,
|
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({
|
const scheduler = new Cyclist({
|
||||||
interval,
|
interval,
|
||||||
onTrigger: getTrigger({ defaultOutput, getTime }),
|
onTrigger: getTrigger({ defaultOutput, getTime }),
|
||||||
onError: onSchedulerError,
|
onError: onSchedulerError,
|
||||||
getTime,
|
getTime,
|
||||||
onToggle,
|
onToggle: (started) => {
|
||||||
|
updateState({ started });
|
||||||
|
onToggle?.(started);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
let pPatterns = {};
|
let pPatterns = {};
|
||||||
let allTransform;
|
let allTransform;
|
||||||
|
|
@ -43,6 +66,7 @@ export function repl({
|
||||||
throw new Error('no code to evaluate');
|
throw new Error('no code to evaluate');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
updateState({ code, pending: true });
|
||||||
await beforeEval?.({ code });
|
await beforeEval?.({ code });
|
||||||
shouldHush && hush();
|
shouldHush && hush();
|
||||||
let { pattern, meta } = await _evaluate(code, transpiler);
|
let { pattern, meta } = await _evaluate(code, transpiler);
|
||||||
|
|
@ -58,17 +82,28 @@ export function repl({
|
||||||
}
|
}
|
||||||
logger(`[eval] code updated`);
|
logger(`[eval] code updated`);
|
||||||
setPattern(pattern, autostart);
|
setPattern(pattern, autostart);
|
||||||
|
updateState({
|
||||||
|
miniLocations: meta?.miniLocations || [],
|
||||||
|
widgets: meta?.widgets || [],
|
||||||
|
activeCode: code,
|
||||||
|
pattern,
|
||||||
|
evalError: undefined,
|
||||||
|
schedulerError: undefined,
|
||||||
|
pending: false,
|
||||||
|
});
|
||||||
afterEval?.({ code, pattern, meta });
|
afterEval?.({ code, pattern, meta });
|
||||||
return pattern;
|
return pattern;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.warn(`[repl] eval error: ${err.message}`);
|
// console.warn(`[repl] eval error: ${err.message}`);
|
||||||
logger(`[eval] error: ${err.message}`, 'error');
|
logger(`[eval] error: ${err.message}`, 'error');
|
||||||
|
updateState({ evalError: err, pending: false });
|
||||||
onEvalError?.(err);
|
onEvalError?.(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const stop = () => scheduler.stop();
|
const stop = () => scheduler.stop();
|
||||||
const start = () => scheduler.start();
|
const start = () => scheduler.start();
|
||||||
const pause = () => scheduler.pause();
|
const pause = () => scheduler.pause();
|
||||||
|
const toggle = () => scheduler.toggle();
|
||||||
const setCps = (cps) => scheduler.setCps(cps);
|
const setCps = (cps) => scheduler.setCps(cps);
|
||||||
const setCpm = (cpm) => scheduler.setCps(cpm / 60);
|
const setCpm = (cpm) => scheduler.setCps(cpm / 60);
|
||||||
|
|
||||||
|
|
@ -127,8 +162,8 @@ export function repl({
|
||||||
setCpm,
|
setCpm,
|
||||||
setcpm: setCpm,
|
setcpm: setCpm,
|
||||||
});
|
});
|
||||||
|
const setCode = (code) => updateState({ code });
|
||||||
return { scheduler, evaluate, start, stop, pause, setCps, setPattern };
|
return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle, state };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTrigger =
|
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':
|
'@lezer/highlight':
|
||||||
specifier: ^1.1.4
|
specifier: ^1.1.4
|
||||||
version: 1.1.4
|
version: 1.1.4
|
||||||
|
'@nanostores/persistent':
|
||||||
|
specifier: ^0.8.0
|
||||||
|
version: 0.8.0(nanostores@0.8.1)
|
||||||
'@replit/codemirror-emacs':
|
'@replit/codemirror-emacs':
|
||||||
specifier: ^6.0.1
|
specifier: ^6.0.1
|
||||||
version: 6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
version: 6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
||||||
|
|
@ -117,6 +120,9 @@ importers:
|
||||||
'@uiw/codemirror-themes-all':
|
'@uiw/codemirror-themes-all':
|
||||||
specifier: ^4.19.16
|
specifier: ^4.19.16
|
||||||
version: 4.19.16(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
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:
|
react-dom:
|
||||||
specifier: ^18.2.0
|
specifier: ^18.2.0
|
||||||
version: 18.2.0(react@18.2.0)
|
version: 18.2.0(react@18.2.0)
|
||||||
|
|
@ -125,52 +131,6 @@ importers:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 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:
|
packages/core:
|
||||||
dependencies:
|
dependencies:
|
||||||
fraction.js:
|
fraction.js:
|
||||||
|
|
@ -468,6 +428,46 @@ importers:
|
||||||
specifier: ^4.3.3
|
specifier: ^4.3.3
|
||||||
version: 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:
|
packages/serial:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@strudel.cycles/core':
|
'@strudel.cycles/core':
|
||||||
|
|
@ -702,6 +702,9 @@ importers:
|
||||||
'@strudel/hydra':
|
'@strudel/hydra':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../packages/hydra
|
version: link:../packages/hydra
|
||||||
|
'@strudel/repl':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../packages/repl
|
||||||
'@supabase/supabase-js':
|
'@supabase/supabase-js':
|
||||||
specifier: ^2.21.0
|
specifier: ^2.21.0
|
||||||
version: 2.21.0
|
version: 2.21.0
|
||||||
|
|
@ -750,6 +753,9 @@ importers:
|
||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^18.2.0
|
specifier: ^18.2.0
|
||||||
version: 18.2.0(react@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:
|
rehype-autolink-headings:
|
||||||
specifier: ^6.1.1
|
specifier: ^6.1.1
|
||||||
version: 6.1.1
|
version: 6.1.1
|
||||||
|
|
@ -4115,6 +4121,19 @@ packages:
|
||||||
rollup: 2.79.1
|
rollup: 2.79.1
|
||||||
dev: true
|
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):
|
/@rollup/pluginutils@3.1.0(rollup@2.79.1):
|
||||||
resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==}
|
resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==}
|
||||||
engines: {node: '>= 8.0.0'}
|
engines: {node: '>= 8.0.0'}
|
||||||
|
|
@ -4127,109 +4146,19 @@ packages:
|
||||||
rollup: 2.79.1
|
rollup: 2.79.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@rollup/rollup-android-arm-eabi@4.9.0:
|
/@rollup/pluginutils@5.1.0:
|
||||||
resolution: {integrity: sha512-+1ge/xmaJpm1KVBuIH38Z94zj9fBD+hp+/5WLaHgyY8XLq1ibxk/zj6dTXaqM2cAbYKq8jYlhHd6k05If1W5xA==}
|
resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
|
||||||
cpu: [arm]
|
engines: {node: '>=14.0.0'}
|
||||||
os: [android]
|
peerDependencies:
|
||||||
requiresBuild: true
|
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
|
||||||
dev: true
|
peerDependenciesMeta:
|
||||||
optional: true
|
rollup:
|
||||||
|
optional: true
|
||||||
/@rollup/rollup-android-arm64@4.9.0:
|
dependencies:
|
||||||
resolution: {integrity: sha512-im6hUEyQ7ZfoZdNvtwgEJvBWZYauC9KVKq1w58LG2Zfz6zMd8gRrbN+xCVoqA2hv/v6fm9lp5LFGJ3za8EQH3A==}
|
'@types/estree': 1.0.0
|
||||||
cpu: [arm64]
|
estree-walker: 2.0.2
|
||||||
os: [android]
|
picomatch: 2.3.1
|
||||||
requiresBuild: true
|
dev: false
|
||||||
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
|
|
||||||
|
|
||||||
/@sigstore/protobuf-specs@0.1.0:
|
/@sigstore/protobuf-specs@0.1.0:
|
||||||
resolution: {integrity: sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ==}
|
resolution: {integrity: sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ==}
|
||||||
|
|
@ -6234,7 +6163,6 @@ packages:
|
||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
strip-ansi: 6.0.1
|
strip-ansi: 6.0.1
|
||||||
wrap-ansi: 7.0.0
|
wrap-ansi: 7.0.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/clone-buffer@1.0.0:
|
/clone-buffer@1.0.0:
|
||||||
resolution: {integrity: sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==}
|
resolution: {integrity: sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==}
|
||||||
|
|
@ -6739,7 +6667,6 @@ packages:
|
||||||
/define-lazy-prop@2.0.0:
|
/define-lazy-prop@2.0.0:
|
||||||
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
|
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/define-properties@1.1.4:
|
/define-properties@1.1.4:
|
||||||
resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
|
resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
|
||||||
|
|
@ -7547,6 +7474,10 @@ packages:
|
||||||
resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==}
|
resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/estree-walker@2.0.2:
|
||||||
|
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/estree-walker@3.0.3:
|
/estree-walker@3.0.3:
|
||||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -8003,7 +7934,6 @@ packages:
|
||||||
/get-caller-file@2.0.5:
|
/get-caller-file@2.0.5:
|
||||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||||
engines: {node: 6.* || 8.* || >= 10.*}
|
engines: {node: 6.* || 8.* || >= 10.*}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/get-func-name@2.0.0:
|
/get-func-name@2.0.0:
|
||||||
resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
|
resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
|
||||||
|
|
@ -8898,7 +8828,6 @@ packages:
|
||||||
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
|
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-docker@3.0.0:
|
/is-docker@3.0.0:
|
||||||
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
|
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
|
||||||
|
|
@ -9125,7 +9054,6 @@ packages:
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dependencies:
|
dependencies:
|
||||||
is-docker: 2.2.1
|
is-docker: 2.2.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-wsl@3.1.0:
|
/is-wsl@3.1.0:
|
||||||
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
|
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
|
||||||
|
|
@ -11259,7 +11187,6 @@ packages:
|
||||||
define-lazy-prop: 2.0.0
|
define-lazy-prop: 2.0.0
|
||||||
is-docker: 2.2.1
|
is-docker: 2.2.1
|
||||||
is-wsl: 2.2.0
|
is-wsl: 2.2.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/optionator@0.8.3:
|
/optionator@0.8.3:
|
||||||
resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==}
|
resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==}
|
||||||
|
|
@ -12525,7 +12452,6 @@ packages:
|
||||||
/require-directory@2.1.1:
|
/require-directory@2.1.1:
|
||||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/require-from-string@2.0.2:
|
/require-from-string@2.0.2:
|
||||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||||
|
|
@ -12697,7 +12623,6 @@ packages:
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
source-map: 0.7.4
|
source-map: 0.7.4
|
||||||
yargs: 17.6.2
|
yargs: 17.6.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/rollup@2.79.1:
|
/rollup@2.79.1:
|
||||||
resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==}
|
resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==}
|
||||||
|
|
@ -12722,27 +12647,6 @@ packages:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
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:
|
/run-async@2.4.1:
|
||||||
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
|
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
|
||||||
engines: {node: '>=0.12.0'}
|
engines: {node: '>=0.12.0'}
|
||||||
|
|
@ -14331,6 +14235,41 @@ packages:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
dev: true
|
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):
|
/vite@4.5.0(@types/node@18.16.3):
|
||||||
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
|
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
|
|
@ -14366,41 +14305,6 @@ packages:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
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):
|
/vitefu@0.2.4(vite@4.5.0):
|
||||||
resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==}
|
resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -14815,7 +14719,6 @@ packages:
|
||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
strip-ansi: 6.0.1
|
strip-ansi: 6.0.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/wrap-ansi@8.1.0:
|
/wrap-ansi@8.1.0:
|
||||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||||
|
|
@ -14898,7 +14801,6 @@ packages:
|
||||||
/y18n@5.0.8:
|
/y18n@5.0.8:
|
||||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/yaeti@0.0.6:
|
/yaeti@0.0.6:
|
||||||
resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==}
|
resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==}
|
||||||
|
|
@ -14958,7 +14860,6 @@ packages:
|
||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
y18n: 5.0.8
|
y18n: 5.0.8
|
||||||
yargs-parser: 21.1.1
|
yargs-parser: 21.1.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/yocto-queue@0.1.0:
|
/yocto-queue@0.1.0:
|
||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,3 @@ packages:
|
||||||
- "packages/react/examples/nano-repl"
|
- "packages/react/examples/nano-repl"
|
||||||
- "packages/web/examples/repl-example"
|
- "packages/web/examples/repl-example"
|
||||||
- "packages/superdough/example"
|
- "packages/superdough/example"
|
||||||
- "packages/codemirror/examples/strudelmirror"
|
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,10 @@
|
||||||
"@strudel.cycles/transpiler": "workspace:*",
|
"@strudel.cycles/transpiler": "workspace:*",
|
||||||
"@strudel.cycles/webaudio": "workspace:*",
|
"@strudel.cycles/webaudio": "workspace:*",
|
||||||
"@strudel.cycles/xen": "workspace:*",
|
"@strudel.cycles/xen": "workspace:*",
|
||||||
"@strudel/hydra": "workspace:*",
|
|
||||||
"@strudel/codemirror": "workspace:*",
|
"@strudel/codemirror": "workspace:*",
|
||||||
"@strudel/desktopbridge": "workspace:*",
|
"@strudel/desktopbridge": "workspace:*",
|
||||||
|
"@strudel/hydra": "workspace:*",
|
||||||
|
"@strudel/repl": "workspace:*",
|
||||||
"@supabase/supabase-js": "^2.21.0",
|
"@supabase/supabase-js": "^2.21.0",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"@tailwindcss/typography": "^0.5.8",
|
"@tailwindcss/typography": "^0.5.8",
|
||||||
|
|
@ -53,6 +54,7 @@
|
||||||
"nanostores": "^0.8.1",
|
"nanostores": "^0.8.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-hook-inview": "^4.5.0",
|
||||||
"rehype-autolink-headings": "^6.1.1",
|
"rehype-autolink-headings": "^6.1.1",
|
||||||
"rehype-slug": "^5.0.1",
|
"rehype-slug": "^5.0.1",
|
||||||
"rehype-urls": "^1.1.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">
|
<html lang="en" class="dark">
|
||||||
<head>
|
<head>
|
||||||
<HeadCommonNew />
|
|
||||||
<title>Strudel Vanilla REPL</title>
|
<title>Strudel Vanilla REPL</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="h-app-height">
|
<body class="h-app-height">
|
||||||
|
|
@ -90,7 +85,8 @@ import HeadCommonNew from '../../components/HeadCommonNew.astro';
|
||||||
<!-- <label><input type="checkbox" name="isTooltipEnabled" />isTooltipEnabled</label> -->
|
<!-- <label><input type="checkbox" name="isTooltipEnabled" />isTooltipEnabled</label> -->
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="code"></div>
|
<strudel-editor id="editor"></strudel-editor>
|
||||||
<script src="../../repl/vanilla/vanilla.mjs"></script>
|
<script src="../../repl/vanilla/vanilla.mjs"></script>
|
||||||
|
<script src="@strudel/repl"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ select {
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#code,
|
#editor,
|
||||||
.cm-editor,
|
.cm-editor,
|
||||||
.cm-scroller {
|
.cm-scroller {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
|
||||||
|
|
@ -1,94 +1,12 @@
|
||||||
import { logger, getDrawContext, silence, controls, evalScope, hash2code, code2hash } from '@strudel.cycles/core';
|
import { hash2code, logger } from '@strudel.cycles/core';
|
||||||
import { StrudelMirror, initTheme, activateTheme } from '@strudel/codemirror';
|
import { codemirrorSettings, defaultSettings } from '@strudel/codemirror';
|
||||||
import { transpiler } from '@strudel.cycles/transpiler';
|
|
||||||
import {
|
|
||||||
getAudioContext,
|
|
||||||
webaudioOutput,
|
|
||||||
registerSynthSounds,
|
|
||||||
registerZZFXSounds,
|
|
||||||
samples,
|
|
||||||
} from '@strudel.cycles/webaudio';
|
|
||||||
import './vanilla.css';
|
import './vanilla.css';
|
||||||
|
|
||||||
let editor;
|
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() {
|
async function run() {
|
||||||
const container = document.getElementById('code');
|
const repl = document.getElementById('editor');
|
||||||
if (!container) {
|
editor = repl.editor;
|
||||||
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');
|
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 codeParam = window.location.href.split('#')[1] || '';
|
||||||
|
|
||||||
|
|
@ -192,11 +110,8 @@ function setFormValues(form, values) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const form = document.querySelector('form[name=settings]');
|
const form = document.querySelector('form[name=settings]');
|
||||||
setFormValues(form, initialSettings);
|
setFormValues(form, codemirrorSettings.get());
|
||||||
form.addEventListener('change', () => {
|
form.addEventListener('change', () => {
|
||||||
const values = getFormValues(form, initialSettings);
|
const values = getFormValues(form, defaultSettings);
|
||||||
// console.log('values', values);
|
editor?.updateSettings(values);
|
||||||
editor.updateSettings(values);
|
|
||||||
// TODO: only activateTheme when it changes
|
|
||||||
activateTheme(values.theme);
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue