mirror of
https://codeberg.org/uzu/strudel
synced 2026-09-05 14:58:03 -04:00
- add Stepper + use it everywhere
- rename files to make more sense - add transpilation slide
This commit is contained in:
@@ -28,7 +28,7 @@ function EventEditor({ code: initialCode }) {
|
||||
await initAudio;
|
||||
json.forEach((value) => {
|
||||
const hap = new Hap(new TimeSpan(0, 1), new TimeSpan(0, 1), value);
|
||||
webaudioOutput(hap, 0.1, 1, 1);
|
||||
webaudioOutput(hap, 0.01, 1, 1);
|
||||
});
|
||||
} else if (e.key === '.') {
|
||||
stop();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import EventEditor from './EventEditor.jsx';
|
||||
import { useState } from 'react';
|
||||
import Stepper from './Stepper.jsx';
|
||||
|
||||
const snippets = [
|
||||
[`{ "s": "cp" }`, 'Samples'],
|
||||
[`{ "s": "cp", "crush": 4 }`, 'Samples'],
|
||||
[`{ "s": "sawtooth", "note": "e3" }`, 'Oscillators'],
|
||||
[
|
||||
`[
|
||||
@@ -24,21 +24,15 @@ const snippets = [
|
||||
];
|
||||
|
||||
function EventsDemo() {
|
||||
const [step, setStep] = useState(1);
|
||||
|
||||
return (
|
||||
<div className="not-prose">
|
||||
{snippets.slice(0, step).map(([code, label], i) => {
|
||||
const isActive = i === step - 1;
|
||||
return (
|
||||
<div key={i} className={`border-l-4 pl-8 border-gray-500 py-4 ${isActive ? 'p-4 border-yellow-500' : ''}`}>
|
||||
<h3 className="pb-4">{label}</h3>
|
||||
<EventEditor code={code} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{step < snippets.length && <button onClick={() => setStep((r) => r + 1)}>next</button>}
|
||||
</div>
|
||||
<Stepper
|
||||
steps={snippets.map(([code, label]) => (
|
||||
<>
|
||||
<h3 className="pb-4">{label}</h3>
|
||||
<EventEditor code={code} />
|
||||
</>
|
||||
))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
//import {Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
//import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
//import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
import { atomOneDark } from 'react-syntax-highlighter/dist/esm/styles/hljs';
|
||||
|
||||
Object.assign(atomOneDark.hljs, { padding: '10px' });
|
||||
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import js from 'react-syntax-highlighter/dist/esm/languages/hljs/javascript';
|
||||
import hs from 'react-syntax-highlighter/dist/esm/languages/hljs/haskell';
|
||||
SyntaxHighlighter.registerLanguage('javascript', js);
|
||||
SyntaxHighlighter.registerLanguage('haskell', hs);
|
||||
|
||||
function Highlight({ code, language }) {
|
||||
return (
|
||||
<div className="mb-4 text-[32px] leading-10 rounded-xl overflow-hidden">
|
||||
<SyntaxHighlighter language={language} style={atomOneDark}>
|
||||
<SyntaxHighlighter language={language} style={atomOneDark} customStyle={{ padding: '8px 10px' }}>
|
||||
{code}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { SlideRepl } from './SlideRepl.jsx';
|
||||
import Stepper from './Stepper.jsx';
|
||||
|
||||
const snippets = [
|
||||
[`sound("bd [hh sd]")`, `sound(seq("bd", ["hh", "sd"]))`, 'Nested Sequences'], //
|
||||
@@ -11,7 +11,6 @@ const snippets = [
|
||||
];
|
||||
|
||||
function MiniComparison() {
|
||||
const [step, setStep] = useState(1);
|
||||
return (
|
||||
<>
|
||||
<h1>
|
||||
@@ -19,25 +18,21 @@ function MiniComparison() {
|
||||
<br />
|
||||
<small>External DSL</small>
|
||||
</h1>
|
||||
<div className="not-prose">
|
||||
{snippets.slice(0, step).map(([a, b, label], i) => {
|
||||
const isActive = i === step - 1;
|
||||
return (
|
||||
<div key={i} className={`border-l-4 pl-8 border-gray-500 py-4 ${isActive ? 'p-4 border-yellow-500' : ''}`}>
|
||||
<h3 className="pb-4">{label}</h3>
|
||||
<div className="flex space-x-2">
|
||||
<img src="./img/tidalcycles.svg" className={`h-10 mt-2`} />
|
||||
<SlideRepl tune={a} hideHeader />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<img src="./img/js.jpg" className={`h-10 mt-2`} />
|
||||
<SlideRepl tune={b} hideHeader />
|
||||
</div>
|
||||
<Stepper
|
||||
steps={snippets.map(([a, b, label]) => (
|
||||
<>
|
||||
<h3 className="pb-4">{label}</h3>
|
||||
<div className="flex space-x-2">
|
||||
<img src="./img/tidalcycles.svg" className={`h-10 mt-2`} />
|
||||
<SlideRepl tune={a} hideHeader />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{step < snippets.length && <button onClick={() => setStep((r) => r + 1)}>next</button>}
|
||||
<div className="flex space-x-2">
|
||||
<img src="./img/js.jpg" className={`h-10 mt-2`} />
|
||||
<SlideRepl tune={b} hideHeader />
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import { SlideRepl } from './SlideRepl.jsx';
|
||||
import Highlight from './Highlight.jsx';
|
||||
import { useState } from 'react';
|
||||
import Stepper from './Stepper.jsx';
|
||||
|
||||
const snippets = [
|
||||
[<SlideRepl tune={`sound("bd [hh sd]").crush(4).log()`} hideHeader />, 'Logging Events'],
|
||||
[<SlideRepl tune={`s("bd [hh sd]").crush(4).log()`} hideHeader />, 'Logging Events'],
|
||||
[
|
||||
<>
|
||||
<Highlight
|
||||
code={`sound("bd [hh sd]").crush(4) // pattern
|
||||
.queryArc(0, 1)`}
|
||||
code={`let pattern = s("bd [hh sd]").crush(4);
|
||||
let events = pattern.queryArc(0, 1);
|
||||
console.log(events.map(e => e.show()))`}
|
||||
language="javascript"
|
||||
/>
|
||||
<Highlight
|
||||
language="json"
|
||||
code={`[
|
||||
"0/1 -> 1/2 | sound:bd",
|
||||
"1/2 -> 3/4 | sound:hh",
|
||||
"3/4 -> 1/1 | sound:sd"
|
||||
"0/1 -> 1/2 | s:bd crush:4",
|
||||
"1/2 -> 3/4 | s:hh crush:4",
|
||||
"3/4 -> 1/1 | s:sd crush:4"
|
||||
]`}
|
||||
/>
|
||||
</>,
|
||||
@@ -25,20 +26,15 @@ const snippets = [
|
||||
];
|
||||
|
||||
function QueryDemo() {
|
||||
const [step, setStep] = useState(1);
|
||||
return (
|
||||
<div className="not-prose">
|
||||
{snippets.slice(0, step).map(([snippet, label], i) => {
|
||||
const isActive = i === step - 1;
|
||||
return (
|
||||
<div key={i} className={`border-l-4 pl-8 border-gray-500 py-4 ${isActive ? 'p-4 border-yellow-500' : ''}`}>
|
||||
<h3 className="pb-4">{label}</h3>
|
||||
{snippet}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{step < snippets.length && <button onClick={() => setStep((r) => r + 1)}>next</button>}
|
||||
</div>
|
||||
<Stepper
|
||||
steps={snippets.map(([snippet, label]) => (
|
||||
<>
|
||||
<h3 className="pb-4">{label}</h3>
|
||||
{snippet}
|
||||
</>
|
||||
))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,18 @@ const loadedMDXFiles = await Promise.all(
|
||||
}),
|
||||
);
|
||||
|
||||
const order = ['01', '02-0', '02-1', '02-2', 'mini-comparison', 'patterns-events', 'events-audio', '04'];
|
||||
const order = [
|
||||
'cover',
|
||||
'whatIsStrudel',
|
||||
'history',
|
||||
'hs2js',
|
||||
'mini-comparison',
|
||||
'patterns-events',
|
||||
'events-audio',
|
||||
'transpilation',
|
||||
'replflow',
|
||||
'systems',
|
||||
];
|
||||
|
||||
const slideEntries = order.map((name) => loadedMDXFiles.find(([file]) => file === name));
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
function Stepper({ steps }) {
|
||||
const [step, setStep] = useState(1);
|
||||
return (
|
||||
<div className="not-prose">
|
||||
{steps.slice(0, step).map((snippet, i) => {
|
||||
const isActive = i === step - 1;
|
||||
return (
|
||||
<div key={i} className={`border-l-4 pl-8 border-gray-500 py-4 ${isActive ? 'p-4 border-yellow-500' : ''}`}>
|
||||
{snippet}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{step < steps.length && <button onClick={() => setStep((r) => r + 1)}>next</button>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Stepper;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { SlideRepl } from './SlideRepl.jsx';
|
||||
import Highlight from './Highlight.jsx';
|
||||
import Stepper from './Stepper.jsx';
|
||||
|
||||
const snippets = [
|
||||
[`sound "bd ~ [sd cp]"`, [`sound("bd ~ [sd cp]")`], 'Mini Notation'], //
|
||||
@@ -40,7 +40,6 @@ const snippets = [
|
||||
];
|
||||
|
||||
function SyntaxComparison() {
|
||||
const [step, setStep] = useState(1);
|
||||
return (
|
||||
<>
|
||||
<h1>
|
||||
@@ -48,35 +47,27 @@ function SyntaxComparison() {
|
||||
<br />
|
||||
<small>Internal DSL / Fluent Interface</small>
|
||||
</h1>
|
||||
<div className=" not-prose justify-start items-start">
|
||||
{/* <div className="grid grid-cols-2">
|
||||
<div>Tidal</div>
|
||||
<div>Strudel</div>
|
||||
</div> */}
|
||||
{snippets.slice(0, step).map(([hs, js, label], i) => {
|
||||
const isActive = i === step - 1;
|
||||
return (
|
||||
<div key={i} className={`border-l-4 pl-8 border-gray-500 py-4 ${isActive ? 'p-4 border-yellow-500' : ''}`}>
|
||||
<h3 className="pb-4">{label}</h3>
|
||||
<Stepper
|
||||
steps={snippets.map(([hs, js, label]) => (
|
||||
<>
|
||||
<h3 className="pb-4">{label}</h3>
|
||||
<div>
|
||||
<div className="flex space-x-4">
|
||||
<img src="./img/haskell.png" className="h-10 mt-2" />
|
||||
<Highlight language="haskell" code={hs} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex space-x-4">
|
||||
<img src="./img/haskell.png" className="h-10 mt-2" />
|
||||
<Highlight language="haskell" code={hs} />
|
||||
</div>
|
||||
<div>
|
||||
{js.map((c, j) => (
|
||||
<div className="flex space-x-4" key={j}>
|
||||
<img src="./img/js.jpg" className={`h-10 mt-2`} />
|
||||
<SlideRepl tune={c} hideHeader />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{js.map((c, j) => (
|
||||
<div className="flex space-x-4" key={j}>
|
||||
<img src="./img/js.jpg" className={`h-10 mt-2`} />
|
||||
<SlideRepl tune={c} hideHeader />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{step < snippets.length && <button onClick={() => setStep((r) => r + 1)}>next</button>}
|
||||
</>
|
||||
))}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { atomone } from '@uiw/codemirror-themes-all';
|
||||
import { CodeMirror, flash, useKeydown } from '@strudel.cycles/react';
|
||||
import { transpiler } from '@strudel.cycles/transpiler';
|
||||
import Highlight from './Highlight';
|
||||
import Stepper from './Stepper';
|
||||
import escodegen from 'escodegen';
|
||||
|
||||
function getTranspiled(code) {
|
||||
return transpiler(code, {
|
||||
addReturn: false,
|
||||
codegenOptions: {
|
||||
format: escodegen.FORMAT_MINIFY,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function TranspilationEditor({ code: initialCode }) {
|
||||
const [code, setCode] = useState(initialCode);
|
||||
const [transpiled, setTranspiled] = useState(getTranspiled(initialCode));
|
||||
const [view, setView] = useState();
|
||||
useKeydown(
|
||||
useCallback(
|
||||
async (e) => {
|
||||
if (view?.hasFocus) {
|
||||
if (e.ctrlKey || e.altKey) {
|
||||
if (e.code === 'Enter') {
|
||||
/* if (getAudioContext().state !== 'running') {
|
||||
alert('please click play to initialize the audio. you can use shortcuts after that!');
|
||||
return;
|
||||
} */
|
||||
e.preventDefault();
|
||||
flash(view);
|
||||
const t = getTranspiled(code);
|
||||
setTranspiled(t);
|
||||
console.log('transpiled', t);
|
||||
} else if (e.key === '.') {
|
||||
stop();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[code, view],
|
||||
),
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<CodeMirror
|
||||
value={code}
|
||||
onChange={(v) => {
|
||||
setCode(v);
|
||||
}}
|
||||
theme={atomone}
|
||||
onViewChanged={(v) => setView(v)}
|
||||
fontSize={32}
|
||||
/>
|
||||
<span>
|
||||
⬇️ transpiles to ⬇️ <small>via acorn / estree-walker / escodegen</small>
|
||||
</span>
|
||||
<Highlight language="javascript" code={transpiled} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TranspilationDemo() {
|
||||
return (
|
||||
<div className="not-prose space-y-8">
|
||||
<Stepper
|
||||
steps={[
|
||||
<TranspilationEditor code={`"bd [hh sd]".s()`} />,
|
||||
<>
|
||||
<span>⬇️ calls ⬇️</span> <small>via peggy</small>
|
||||
<Highlight
|
||||
code={`seq(
|
||||
reify('bd').withLocation([1,1,1], [1,4,4]),
|
||||
seq(
|
||||
reify('hh').withLocation([1,5,5], [1,8,8]),
|
||||
reify('sd').withLocation([1,8,8], [1,10,10]),
|
||||
)
|
||||
).withMiniLocation([1,0,0],[1,12,12]).s()`}
|
||||
/>
|
||||
<span>➡️ location can be used for highlighting</span>
|
||||
</>,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TranspilationDemo;
|
||||
@@ -0,0 +1,5 @@
|
||||
import TranspilationDemo from '../TranspilationDemo';
|
||||
|
||||
# Transpilation
|
||||
|
||||
<TranspilationDemo />
|
||||
@@ -4,7 +4,7 @@ import { walk } from 'estree-walker';
|
||||
import { isNoteWithOctave } from '@strudel.cycles/core';
|
||||
|
||||
export function transpiler(input, options = {}) {
|
||||
const { wrapAsync = false, addReturn = true, simpleLocs = false } = options;
|
||||
const { wrapAsync = false, addReturn = true, simpleLocs = false, codegenOptions = {} } = options;
|
||||
|
||||
let ast = parse(input, {
|
||||
ecmaVersion: 2022,
|
||||
@@ -47,7 +47,7 @@ export function transpiler(input, options = {}) {
|
||||
argument: expression,
|
||||
};
|
||||
}
|
||||
const output = escodegen.generate(ast);
|
||||
const output = escodegen.generate(ast, codegenOptions);
|
||||
if (wrapAsync) {
|
||||
return `(async ()=>{${output}})()`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user