Compare commits

..

1 Commits

Author SHA1 Message Date
Felix Roos e99e53ff6d experiment: render audio with web-audio-api-rs 2023-01-11 19:56:36 +01:00
316 changed files with 53336 additions and 34676 deletions
-3
View File
@@ -15,6 +15,3 @@ vite.config.js
!**/*.mjs
**/*.tsx
**/*.ts
**/*.json
**/dev-dist
**/dist
+2 -3
View File
@@ -9,9 +9,8 @@
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["import"],
"plugins": [],
"rules": {
"no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }],
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}]
"no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }]
}
}
+3 -6
View File
@@ -22,18 +22,15 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
with:
version: 7
- uses: actions/setup-node@v3
with:
node-version: 18
cache: "pnpm"
cache: "npm"
- name: Install Dependencies
run: pnpm install
run: npm ci && cd website && npm ci
- name: Build
run: pnpm build
run: npm run build
- name: Setup Pages
uses: actions/configure-pages@v2
+5 -8
View File
@@ -11,14 +11,11 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
with:
version: 7
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: pnpm install
- run: pnpm run format-check
- run: pnpm run lint
- run: pnpm test
cache: "npm"
- run: npm install
- run: npm run format-check
- run: npm run lint
- run: npm test
+1 -3
View File
@@ -37,6 +37,4 @@ talk/public/EmuSP12
talk/public/samples
server/samples/old
repl/stats.html
coverage
public/icons/apple-splash-*
dev-dist
coverage
+1 -4
View File
@@ -6,7 +6,4 @@
**/dist
packages/mini/krill-parser.js
packages/xen/tunejs.js
paper
pnpm-lock.yaml
pnpm-workspace.yaml
**/dev-dist
paper
+1 -1
View File
@@ -7,7 +7,7 @@
"jsxSingleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"bracketSameLine": false,
"jsxBracketSameLine": false,
"arrowParens": "always",
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
+12 -42
View File
@@ -58,22 +58,23 @@ To fix a bug that has been reported,
## Write Tests
There are still many tests that have not been written yet! Reading and writing tests is a great opportunity to get familiar with the codebase.
You can find the tests in each package in the `test` folder. To run all tests, run `pnpm test` from the root folder.
You can find the tests in each package in the `test` folder. To run all tests, run `npm test` from the root folder.
## Project Setup
To get the project up and running for development, make sure you have installed:
- [git](https://git-scm.com/)
- [node](https://nodejs.org/en/) >= 18
- [pnpm](https://pnpm.io/) (`npm i pnpm -g`)
- git
- node, preferably v16
then, do the following:
```sh
git clone https://github.com/tidalcycles/strudel.git && cd strudel
pnpm i # install at root to symlink packages
pnpm start # start repl
npm i # install at root to symlink packages
npx lerna bootstrap # install all dependencies in packages
cd repl && npm i # install repl dependencies
npm run start # start repl
```
Those commands might look slightly different for your OS.
@@ -82,10 +83,6 @@ Please report any problems you've had with the setup instructions!
## Code Style
To make sure the code changes only where it should, we are using prettier to unify the code style.
- You can format all files at once by running `pnpm prettier` from the project root
- Run `pnpm format-check` from the project root to check if all files are well formatted
If you use VSCode, you can
1. install [the prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
@@ -93,29 +90,12 @@ If you use VSCode, you can
3. Choose "Configure Default Formatter..."
4. Select prettier
## ESLint
To prevent unwanted runtime errors, this project uses [eslint](https://eslint.org/).
- You can check for lint errors by running `pnpm lint`
There are also eslint extensions / plugins for most editors.
## Running Tests
- Run all tests with `pnpm test`
- Run all tests with UI using `pnpm test-ui`
## Running all CI Checks
When opening a PR, the CI runner will automatically check the code style and eslint, as well as run all tests.
You can run the same check with `pnpm check`
## Package Workflow
The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning.
When you run `pnpm i` on the root folder, [pnpm workspaces](https://pnpm.io/workspaces) will install all dependencies of all subpackages. This will allow any js file to import `@strudel.cycles/<package-name>` to get the local version,
allowing to develop multiple packages at the same time.
When you run `npm i` on the root folder, [npm workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces) will symlink all packages
in the `node_modules` folder. This will allow any js file to import `@strudel.cycles/<package-name>` to get the local version,
which allows developing multiple packages at the same time
## Package Publishing
@@ -123,25 +103,15 @@ To publish all packages that have been changed since the last release, run:
```sh
npm login
# this will increment all the versions in package.json files of non private packages to selected versions
npx lerna version --no-private
# publish all packages inside /packages using pnpm! don't use lerna to publish!!
pnpm --filter "./packages/**" publish --dry-run
# the last command was only a dry-run, make sure everything looks ok, if yes, run the same command without flag
npx lerna publish
```
To manually publish a single package, increase the version in the `package.json`, then run `pnpm publish`.
Important: Always publish with `pnpm`, as `npm` does not support overriding main files in `publishConfig`, which is done in all the packages.
### New Packages
To add a new package, you have to publish it manually the first time, using:
```sh
cd packages/<package-name> && pnpm publish --access public
cd packages/<package-name> && npm publish --access public
```
## Have Fun
+2 -3
View File
@@ -7,15 +7,14 @@ An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using w
- Try it here: <https://strudel.tidalcycles.org/>
- Docs: <https://strudel.tidalcycles.org/learn/>
- Technical Blog Post: <https://loophole-letters.vercel.app/strudel>
- 1 Year of Strudel Blog Post: <https://loophole-letters.vercel.app/strudel1year>
## Running Locally
After cloning the project, you can run the REPL locally:
```bash
pnpm run setup
pnpm run repl
npm run setup
npm run repl
```
## Using Strudel In Your Project
-26
View File
@@ -1,26 +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?
public/samples
-22
View File
@@ -1,22 +0,0 @@
# Usage
```sh
cd iclc2023/slides
pnpm i
pnpm dev
```
- ctrl+click or ctrl+(left|right) to switch slides
## Samples
To make sure all the samples are available, clone:
```sh
cd public/samples
git clone https://github.com/ritchse/tidal-drum-machines.git
git clone https://github.com/tidalcycles/Dirt-Samples.git
git clone https://github.com/felixroos/webaudiofontdata.git
```
or use symlinks..
-15
View File
@@ -1,15 +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>Slides</title>
</head>
<body style="background-color: black">
<div id="root">
<span style="color: white">loading... </span>
</div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
-40
View File
@@ -1,40 +0,0 @@
{
"name": "slides",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@mdx-js/rollup": "^2.3.0",
"@nanostores/persistent": "^0.7.0",
"@nanostores/react": "^0.4.1",
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/react": "workspace:*",
"@strudel.cycles/soundfonts": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/csound": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@uiw/codemirror-themes-all": "^4.19.8",
"escodegen": "^2.0.0",
"nanostores": "^0.7.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-syntax-highlighter": "^15.5.0",
"remark-gfm": "^3.0.1",
"vite-plugin-top-level-await": "^1.3.0"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.8",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@vitejs/plugin-react": "^3.1.0",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.21",
"tailwindcss": "^3.2.7",
"vite": "^4.2.0"
}
}
-6
View File
@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

File diff suppressed because it is too large Load Diff
-46
View File
@@ -1,46 +0,0 @@
import { useState, useCallback } from 'react';
import { atomone } from '@uiw/codemirror-themes-all';
import { CodeMirror, flash, useKeydown } from '@strudel.cycles/react';
import { initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio';
import { TimeSpan } from '@strudel.cycles/core';
const initAudio = initAudioOnFirstClick();
function EventEditor({ code: initialCode }) {
const [code, setCode] = useState(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);
let json = JSON.parse(code);
if (!Array.isArray(json)) {
json = [json];
}
await initAudio;
json.forEach((value) => {
const hap = new Hap(new TimeSpan(0, 1), new TimeSpan(0, 1), value);
webaudioOutput(hap, 0.01, 1, 1);
});
} else if (e.key === '.') {
stop();
e.preventDefault();
}
}
}
},
[code, view],
),
);
return <CodeMirror value={code} onChange={setCode} theme={atomone} onViewChanged={(v) => setView(v)} fontSize={32} />;
}
export default EventEditor;
-39
View File
@@ -1,39 +0,0 @@
import EventEditor from './EventEditor.jsx';
import Stepper from './Stepper.jsx';
const snippets = [
[`{ "s": "cp", "crush": 4 }`, 'Samples'],
[`{ "s": "sawtooth", "note": "e3" }`, 'Oscillators'],
[
`[
{ "s": "gm_epiano2", "note": "c4" },
{ "s": "gm_epiano2", "note": "eb4" },
{ "s": "gm_epiano2", "note": "g4" }
]`,
'Soundfonts',
],
[
`{
"s": "toys",
"cutoff": 2000,
"delay": 0.5,
"crush": 4
}`,
'Effects',
],
];
function EventsDemo() {
return (
<Stepper
steps={snippets.map(([code, label]) => (
<div className="py-4 space-y-4">
<h3>{label}</h3>
<EventEditor code={code} />
</div>
))}
/>
);
}
export default EventsDemo;
-21
View File
@@ -1,21 +0,0 @@
//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';
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 = 'javascript' }) {
return (
<div className="text-[32px] leading-10 rounded-xl overflow-hidden">
<SyntaxHighlighter language={language} style={atomOneDark} customStyle={{ padding: '8px 10px' }}>
{code}
</SyntaxHighlighter>
</div>
);
}
export default Highlight;
-38
View File
@@ -1,38 +0,0 @@
import { SlideRepl } from './SlideRepl.jsx';
import Stepper from './Stepper.jsx';
const snippets = [
[`sound("bd [hh sd]")`, `sound(seq("bd", ["hh", "sd"]))`, 'Nested Sequences'], //
[`sound("<bd sd>")`, `sound(cat("bd", "sd"))`, 'Cyclewise Sequences'], //
[`sound("bd,hh")`, `sound(stack("bd", "hh"))`, 'Stack'], //
[`sound("bd@3 sd@1")`, `sound(timeCat([3, "bd"], [1, "sd"]))`, 'Weighted Sequence'], //
[`sound("{lt ht mt, bd sd}")`, `sound(polymeter(["lt","ht","mt"], ["bd", "sd"]))`, 'Polymeter'], //
[`sound("{bd hh sd ht}%2")`, `sound(polymeterSteps(2, ["bd", "hh", "sd", "ht"]))`, 'Steps per Cycle'],
];
function MiniComparison() {
return (
<>
<h1>User Code: Mini Notation -&gt; JavaScript</h1>
<Stepper
steps={snippets.map(([a, b, label]) => (
<div className="py-4">
<h3 className="pb-4">{label}</h3>
<div className="space-y-2">
<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>
</div>
</div>
))}
/>
</>
);
}
export default MiniComparison;
-50
View File
@@ -1,50 +0,0 @@
import { SlideRepl } from './SlideRepl.jsx';
import Highlight from './Highlight.jsx';
import Stepper from './Stepper.jsx';
const snippets = [
[
<>
{' '}
<SlideRepl tune={`s("bd [hh sd]").crush(4).log()`} hideHeader />
</>,
'Logging Events',
],
[
<>
<Highlight
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 | s:bd crush:4",
"1/2 -> 3/4 | s:hh crush:4",
"3/4 -> 1/1 | s:sd crush:4"
]`}
/>
</>,
'Querying Events Manually',
],
];
function QueryDemo() {
return (
<>
<img src="./img/queryflow.png" className="w-[800px]" />
<Stepper
steps={snippets.map(([snippet, label]) => (
<div className="py-4 space-y-4">
<h3>{label}</h3>
{snippet}
</div>
))}
/>
</>
);
}
export default QueryDemo;
-49
View File
@@ -1,49 +0,0 @@
import { evalScope, controls } from '@strudel.cycles/core';
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import { MiniRepl as _MiniRepl } from '@strudel.cycles/react';
import { prebake } from './prebake';
import { atomone } from '@uiw/codemirror-themes-all';
import blackscreen from '@strudel.cycles/react/src/themes/blackscreen';
if (typeof window !== 'undefined') {
await evalScope(
controls,
import('@strudel.cycles/core'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/webaudio'),
import('@strudel.cycles/soundfonts'),
import('@strudel.cycles/csound'),
);
}
if (typeof window !== 'undefined') {
initAudioOnFirstClick();
prebake();
}
export function SlideRepl({
tune,
drawTime,
punchcard,
canvasHeight = 100,
hideHeader = false,
fontSize = 32,
disabled = false,
}) {
return (
<div className="not-prose rounded-xl overflow-hidden">
<_MiniRepl
hideHeader={hideHeader}
tune={tune}
hideOutsideView={true}
drawTime={drawTime}
punchcard={punchcard}
canvasHeight={canvasHeight}
fontSize={fontSize}
theme={disabled ? blackscreen : atomone}
// theme={themes[theme]}
/>
</div>
);
}
-125
View File
@@ -1,125 +0,0 @@
import { persistentAtom } from '@nanostores/persistent';
import { useStore } from '@nanostores/react'; // or '@nanostores/preact'
import useEvent from './useEvent';
function cx(...classes) {
return classes.filter(Boolean).join(' ');
}
// https://vitejs.dev/guide/features.html#glob-import
const slideImports = import.meta.glob('./slides/*.mdx');
//const slideImports = import.meta.glob('./slides/*.mdx', { eager: true });
console.log('load slides...', slideImports);
const loadedMDXFiles = await Promise.all(
Object.entries(slideImports).map(async ([path, load]) => {
//Object.entries(slideImports).map(async ([path, m]) => {
const segments = path.split('/');
const filename = segments[segments.length - 1].slice(0, -4); // expects .mdx at the end
return [filename, (await load()).default];
//return [filename, m.default];
}),
);
console.log('load done!');
const order = [
'cover',
'whatIsStrudel',
'history',
'hs2js',
'usage',
'swatch',
'repl',
//'repl-features',
'viz',
'replflow',
// User Code
'transpilation',
'mininotation',
// 'mini-comparison', // remove?
// Patterns
'patterns-events', // remove?
// scheduler?
// Output
'events-audio', // remove?
'outputs',
// 'csound', // remove?
//
// pattern alignment?
'bad',
'good',
'future',
'end',
];
// examples swatch
// draw examples!!
const slideEntries = order.map((name) => loadedMDXFiles.find(([file]) => file === name));
// current slide index is persisted, so it's safe to refresh the browser
export const slideIndex = persistentAtom('slideIndex', '0');
export const prev = () => slideIndex.set((parseInt(slideIndex.get()) - 1 + slideEntries.length) % slideEntries.length);
export const next = () => slideIndex.set((parseInt(slideIndex.get()) + 1) % slideEntries.length);
function Slides() {
const activeIndex = parseInt(useStore(slideIndex));
/*useEvent('click', (e) => {
if (!e.ctrlKey) {
return;
}
const leftHalf = e.clientX / document.body.clientWidth < 0.5;
if (leftHalf) {
prev();
} else {
next();
}
});*/
/*useEvent('keydown', (e) => {
if (e.key === 'Home') {
prev();
} else if (e.key === 'End') {
next();
}
});*/
return (
<div className="w-screen h-screen overflow-hidden relative">
{slideEntries.map(([path, Slide], i) => (
<div
key={i}
className={cx(
`bg-slate-900 w-full h-full flex justify-center absolute top-0 transition-all ease-in-out duration-500 overflow-auto`,
i < activeIndex && '-translate-x-full',
i > activeIndex && 'translate-x-full',
)}
>
<div className="prose prose-invert w-[1400px] p-12 max-w-full" style={{ fontSize: '2em' }}>
{Math.abs(i - activeIndex) <= 1 && <Slide />}
</div>
</div>
))}
<div className="text-white text-2xl text-bold cursor-pointer select-none">
<div className="fixed left-0 p-4 h-full items-start flex hover:bg-[#00000050]" onClick={() => prev()}>
&lt;
</div>
<div className="fixed right-0 p-4 h-full items-start flex hover:bg-[#00000050]" onClick={() => next()}>
&gt;
</div>
</div>
<div className="absolute top-0 w-full">
<div className="h-[5px] bg-slate-500 w-full z-100">
<div
style={{ width: ((activeIndex / (slideEntries.length - 1)) * 100).toFixed(0) + '%' }}
className="h-full bg-yellow-500"
/>
</div>
</div>
</div>
);
}
export default Slides;
-20
View File
@@ -1,20 +0,0 @@
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 ${isActive ? 'border-yellow-500' : ''}`}>
{snippet}
</div>
);
})}
{step < steps.length && <button onClick={() => setStep((r) => r + 1)}>next</button>}
</div>
);
}
export default Stepper;
-73
View File
@@ -1,73 +0,0 @@
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'], //
[`sound "bd ~ [sd cp]" # speed "1 2"`, [`sound("bd ~ [sd cp]").speed("1 2")`], 'Composing Patterns'],
[
`fast 2 $ sound "bd ~ [sd cp]"`,
[`fast(2, sound("bd ~ [sd cp]"))`, `sound("bd ~ [sd cp]").fast(2)`],
'Patterns Transformations',
],
[
`speed "1 2 3" + "4 5" # s "bd"`,
['speed("1 2 3".add("4 5")).s("bd")', '"1 2 3".add("4 5").speed().s("bd")'],
'Pattern Arithmetic',
],
[
`jux rev
$ every 3 (fast 2)
$ sound "bd sd"`,
[
`sound("bd sd")
.every(3, fast("2"))
.jux(rev)`,
],
'Higher order transformations (with partial application)',
],
[
`jux rev
$ every "<3 5>" (fast "1 2")
$ sound "bd sd cp mt"`,
[
`sound("bd sd cp mt")
.every("<3 5>", fast("1 2"))
.jux(rev)`,
],
'Patterns all the way down',
],
];
function SyntaxComparison() {
return (
<>
<h1>Haskell -&gt; JavaScript</h1>
<Stepper
steps={snippets
.map(([hs, js, label]) => (
<div className="py-4 space-y-4">
<h3>{label}</h3>
<div className="space-y-2">
<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>
</div>
</div>
))
.concat([<>Fluent Interface: Just chain all the things!</>])}
/>
</>
);
}
export default SyntaxComparison;
-84
View File
@@ -1,84 +0,0 @@
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 (
<div className="space-y-2 py-2">
<CodeMirror
value={code}
onChange={(v) => {
setCode(v);
}}
theme={atomone}
onViewChanged={(v) => setView(v)}
fontSize={32}
/>
<span> transpiles to</span>
<Highlight language="javascript" code={transpiled} />
</div>
);
}
function TranspilationDemo() {
return (
<div className="not-prose space-y-8">
<Stepper
steps={[
<>
<img src="./img/transpileflow.png" className="h-[500px]" />
<ul className="list-decimal pl-8">
<li>Parse JS Code with acorn to get AST</li>
<li>Modify AST with estree-walker</li>
<li>Convert modified AST back to JS with escodegen</li>
</ul>
</>,
<TranspilationEditor code={`"bd [hh sd]"`} />,
]}
/>
</div>
);
}
export default TranspilationDemo;
-21
View File
@@ -1,21 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.cm-activeLine {
background-color: transparent !important;
}
.cm-theme {
background-color: var(--background);
border: 1px solid var(--lineHighlight);
padding: 2px;
}
.cm-gutters {
display: none !important;
}
img {
@apply rounded-xl overflow-hidden;
}
-10
View File
@@ -1,10 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import Slides from './Slides';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Slides />
</React.StrictMode>,
);
-22
View File
@@ -1,22 +0,0 @@
import { registerSynthSounds, samples } from '@strudel.cycles/webaudio';
import { registerSoundfonts } from '@strudel.cycles/soundfonts';
export async function prebake() {
// https://archive.org/details/SalamanderGrandPianoV3
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
await Promise.all([
registerSynthSounds(),
registerSoundfonts(`./samples/webaudiofontdata/sound/`),
/* samples(`./piano.json`, `./piano/`, { prebake: true }),
samples(`./vcsl.json`, 'github:sgossner/VCSL/master/', { prebake: true }),
*/
//samples(`./tidal-drum-machines.json`, 'github:ritchse/tidal-drum-machines/main/machines/', {
samples(`./tidal-drum-machines.json`, './samples/tidal-drum-machines/machines/', {
prebake: true,
tag: 'drum-machines',
}) /*
samples(`./EmuSP12.json`, `./EmuSP12/`, { prebake: true, tag: 'drum-machines' }), */,
// samples('github:tidalcycles/Dirt-Samples/master'),
samples('./Dirt-Samples.json', './samples/Dirt-Samples/'),
]);
}
-6
View File
@@ -1,6 +0,0 @@
# What's bad
- Dynamic Typing in JS
- Fractions and Hot Paths
- Verbose function composition
- Method-chaining tradeoffs
-7
View File
@@ -1,7 +0,0 @@
<div className="flex flex-col justify-between h-full overflow-hidden max-h-screen space-y-8">
<h2 className="m-0">Strudel: live coding patterns on the Web</h2>
<div className="justify-center flex overflow-hidden not-prose">
<img src="./img/strudel_icon.png" className="h-full" />
</div>
<span>Felix Roos, Alex McLean</span>
</div>
-65
View File
@@ -1,65 +0,0 @@
import { SlideRepl } from '../SlideRepl.jsx';
# CSound Example
## Loading ORC instruments
<SlideRepl
tune={`// livecode.orc by Steven Yi
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
stack(
note("<C^7 A7 Dm7 Fm7>/2".voicings('lefthand'))
.cutoff(sine.range(500,2000).round().slow(16))
.euclidLegato(3,8).csound('FM1'),
note("<C2 A1 D2 F2>/2").ply(8).csound('Bass')
.gain("1 4 1 4"),
note("0 7 [4 3] 2".fast(2/3)
.off(".25 .125", add("<2 4 -3 -1>"))
.slow(2).scale('A4 minor'))
.legato(.25).csound('SynHarp'),
s("bd*2,[~ hh]*2,~ cp").bank('RolandTR909')
)`}
hideHeader
/>
## Synthesis
<SlideRepl
tune={`await loadCsound\`
instr CoolSynth
iduration = p3
ifreq = p4
igain = p5
ioct = octcps(ifreq)
kpwm = oscili(.05, 8)
asig = vco2(igain, ifreq, 4, .5 + kpwm)
asig += vco2(igain, ifreq * 2)
idepth = 2
acut = transegr:a(0, .005, 0, idepth, .06, -4.2, 0.001, .01, -4.2, 0) ; filter envelope
asig = zdf_2pole(asig, cpsoct(ioct + acut + 2), 0.5)
iattack = .01
isustain = .5
idecay = .1
irelease = .1
asig *= linsegr:a(0, iattack, 1, idecay, isustain, iduration, isustain, irelease, 0)
out(asig, asig)
endin\`
"<0 2 [4 6](3,4,2) 3\*2>"
.off(1/4, add(2))
.off(1/2, add(6))
.scale('D minor')
.note()
//.pianoroll()
.csound('CoolSynth')`}
hideHeader
/>
-1
View File
@@ -1 +0,0 @@
# Strudel Docs
-4
View File
@@ -1,4 +0,0 @@
# Thanks
- ICLC organizers
- Tidal Community
@@ -1,7 +0,0 @@
import { SlideRepl } from '../SlideRepl.jsx';
import Highlight from '../Highlight.jsx';
import EventsDemo from '../EventsDemo.jsx';
# Webaudio Output: Values -> Sound
<EventsDemo />
-15
View File
@@ -1,15 +0,0 @@
import { SlideRepl } from '../SlideRepl.jsx';
# Pattern Example
<SlideRepl
tune={`"<0 2 [4 6](3,4,1) 3>"
.off(1/4, add(2))
.off(1/2, add(6))
.scale('D minor')
.legato(.25)
.note().s("sawtooth square")
.delay(.8).delaytime(.125)`}
hideHeader
punchcard
/>
-10
View File
@@ -1,10 +0,0 @@
# Future Outlook
- (slow) community growth
- block based evaluation
- live collaboration
- more audio backends
- interface experiments, e.g.:
- alternative syntax, visual notations
- time scrubbing
- ui controls
-9
View File
@@ -1,9 +0,0 @@
# What's good
- Zero-install, multi-platform
- works (surprisingly) well on mobile devices
- visual feedback good for teaching + learning
- loads of potential contributors
- instant pattern sharing
- two-way flow of features between strudel and tidal
- full mini-notation in hydra and gibber
-3
View File
@@ -1,3 +0,0 @@
# The journey
![ports](./img/ports.png)
-3
View File
@@ -1,3 +0,0 @@
import SyntaxComparison from '../SyntaxComparison.jsx';
<SyntaxComparison />
@@ -1,5 +0,0 @@
import { SlideRepl } from '../SlideRepl.jsx';
import Highlight from '../Highlight.jsx';
import MiniComparison from '../MiniComparison.jsx';
<MiniComparison />
@@ -1,29 +0,0 @@
import Highlight from '../Highlight';
import Stepper from '../Stepper';
# User Code: Mini Notation
<img src="./img/pegflow.png" />
<Stepper
steps={[
<ul className="list-decimal pl-8">
<li>Parse Mini Notation to get AST</li>
<li>walk AST and construct function calls</li>
</ul>,
<div className="space-y-2 py-2">
<Highlight code={`mini('bd [hh sd]')`} />
<span>⬇️ resulting calls</span>
<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]),
)
)`}
/>
<span>➡️ location can be used for highlighting</span>
</div>,
]}
/>
-7
View File
@@ -1,7 +0,0 @@
# What can I pattern?
- Default: Web Audio API
- OSC via osc-js, e.g. SuperDirt
- Csound via the Csound WebAssembly build
- MIDI via WebMIDI
- Serial via WebSerial, e.g. for robot dancing
@@ -1,5 +0,0 @@
import QueryDemo from '../QueryDemo.jsx';
# Querying: From Patterns to Events
<QueryDemo />
@@ -1,12 +0,0 @@
# REPL: Features
- Visual Feedback
- Example shuffler (Hydra-style)
- Tutorial + reference
- Shareable links
- Sound Browser
- Offline Support (+ installable PWA)
- Outputs: Web Audio, MIDI, OSC, CSound, Serial, ..
- Themes
![illegible](./img/illegible.png)
-7
View File
@@ -1,7 +0,0 @@
# REPL - live coding editor
[strudel.tidalcycles.org](https://strudel.tidalcycles.org/)
{/* <iframe className="w-full h-[600px]" src="https://strudel.tidalcycles.org/#c2FtcGxlcyh7CiAgYmQ6IFsnYmQvQlQwQUFEQS53YXYnLCdiZC9CVDBBQUQwLndhdicsJ2JkL0JUMEEwREEud2F2JywnYmQvQlQwQTBEMy53YXYnLCdiZC9CVDBBMEQwLndhdicsJ2JkL0JUMEEwQTcud2F2J10sCiAgc2Q6IFsnc2Qvcnl0bS0wMS1jbGFzc2ljLndhdicsJ3NkL3J5dG0tMDAtaGFyZC53YXYnXSwKICBoaDogWydoaDI3LzAwMF9oaDI3Y2xvc2VkaGgud2F2JywnaGgvMDAwX2hoM2Nsb3NlZGhoLndhdiddLAp9LCAnZ2l0aHViOnRpZGFsY3ljbGVzL0RpcnQtU2FtcGxlcy9tYXN0ZXIvJyk7CnN0YWNrKAogIHMoImJkLFt%2BIDxzZCEzIHNkKDMsNCwyKT5dLGhoKjQiKSAvLyBkcnVtcwogIC5zcGVlZChwZXJsaW4ucmFuZ2UoLjcsMSkpIC8vIHJhbmRvbSBzYW1wbGUgc3BlZWQgdmFyaWF0aW9uCiAgLy8uaHVzaCgpCiAgLCI8YTEgYjEqMiBhMSgzLDgpIGUyPiIgLy8gYmFzc2xpbmUKICAub2ZmKDEvOCx4PT54LmFkZCgxMikuZGVncmFkZUJ5KC41KSkgLy8gcmFuZG9tIG9jdGF2ZSBqdW1wcwogIC5hZGQocGVybGluLnJhbmdlKDAsLjUpKSAvLyByYW5kb20gcGl0Y2ggdmFyaWF0aW9uCiAgLnN1cGVyaW1wb3NlKGFkZCguMDUpKSAvLyBhZGQgc2Vjb25kLCBzbGlnaHRseSBkZXR1bmVkIHZvaWNlCiAgLm4oKSAvLyB3cmFwIGluICJuIgogIC5kZWNheSguMTUpLnN1c3RhaW4oMCkgLy8gbWFrZSBlYWNoIG5vdGUgb2YgZXF1YWwgbGVuZ3RoCiAgLnMoJ3Nhd3Rvb3RoJykgLy8gd2F2ZWZvcm0KICAuZ2FpbiguNCkgLy8gdHVybiBkb3duCiAgLmN1dG9mZihzaW5lLnNsb3coNykucmFuZ2UoMzAwLDUwMDApKSAvLyBhdXRvbWF0ZSBjdXRvZmYKICAvLy5odXNoKCkKICAsIjxBbTchMyA8RW03IEU3YjEzIEVtNyBFYm03YjU%2BPiIudm9pY2luZ3MoJ2xlZnRoYW5kJykgLy8gY2hvcmRzCiAgLnN1cGVyaW1wb3NlKHg9PnguYWRkKC4wNCkpIC8vIGFkZCBzZWNvbmQsIHNsaWdodGx5IGRldHVuZWQgdm9pY2UKICAuYWRkKHBlcmxpbi5yYW5nZSgwLC41KSkgLy8gcmFuZG9tIHBpdGNoIHZhcmlhdGlvbgogIC5uKCkgLy8gd3JhcCBpbiAibiIKICAucygnc2F3dG9vdGgnKSAvLyB3YXZlZm9ybQogIC5nYWluKC4xNikgLy8gdHVybiBkb3duCiAgLmN1dG9mZig1MDApIC8vIGZpeGVkIGN1dG9mZgogIC5hdHRhY2soMSkgLy8gc2xvd2x5IGZhZGUgaW4KICAvLy5odXNoKCkKICAsImE0IGM1IDxlNiBhNj4iLnN0cnVjdCgieCg1LDgpIikKICAuc3VwZXJpbXBvc2UoeD0%2BeC5hZGQoLjA0KSkgLy8gYWRkIHNlY29uZCwgc2xpZ2h0bHkgZGV0dW5lZCB2b2ljZQogIC5hZGQocGVybGluLnJhbmdlKDAsLjUpKSAvLyByYW5kb20gcGl0Y2ggdmFyaWF0aW9uCiAgLm4oKSAvLyB3cmFwIGluICJuIgogIC5kZWNheSguMSkuc3VzdGFpbigwKSAvLyBtYWtlIG5vdGVzIHNob3J0CiAgLnMoJ3RyaWFuZ2xlJykgLy8gd2F2ZWZvcm0KICAuZGVncmFkZUJ5KHBlcmxpbi5yYW5nZSgwLC41KSkgLy8gcmFuZG9tbHkgY29udHJvbGxlZCByYW5kb20gcmVtb3ZhbCA6KQogIC5lY2hvV2l0aCg0LC4xMjUsKHgsbik9PnguZ2FpbiguMTUqMS8obisxKSkpIC8vIGVjaG8gbm90ZXMKICAvLy5odXNoKCkKKQogIC5zbG93KDEuNCk%3D" /> */}
![repl](./img/repl.png)
-5
View File
@@ -1,5 +0,0 @@
# REPL: System Overview
<center>
<img src="./img/strudelflow.png" className="w-[900px]" />
</center>
@@ -1,3 +0,0 @@
# Strudel Docs
![docs](./img/docs.png)
-5
View File
@@ -1,5 +0,0 @@
# Pattern Swatches
fork strudel - publish your own patterns with a fixed version
![swatch](./img/swatch.png)
@@ -1,5 +0,0 @@
import TranspilationDemo from '../TranspilationDemo';
# User Code: Transpilation
<TranspilationDemo />
-14
View File
@@ -1,14 +0,0 @@
import Highlight from '../Highlight.jsx';
# Ways to use Strudel
- "Strudel REPL" at [strudel.tidalcycles.org](https://strudel.tidalcycles.org)
- strudel on [flok.cc](https://flok.cc/) for collaboration
- `@strudel.cycles/*` npm packages for your own projects
- Embeddable [Web Component](https://github.com/tidalcycles/strudel/tree/main/packages/embed#strudelcyclesembed)
- Browser Plugin for [Mastodon](https://github.com/mortuosplango/topfenstrudel) + Discourse Plugin:
<div className="flex">
<img src="./img/topfenstrudel.png" />
<img src="./img/discourse.png" />
</div>
-20
View File
@@ -1,20 +0,0 @@
import { SlideRepl } from '../SlideRepl.jsx';
# REPL: Visual Feedback
Highlighting + Piano Roll
<SlideRepl
hideHeader
punchcard
canvasHeight={300}
fontSize={24}
tune={`// Steve Reich - Piano Phase (1967)
note("E4 F#4 B4 C#5 D5 F#4 E4 C#5 B4 F#4 D5 C#5")
.layer(
x=>x.color('#00ff0050').pan(0).fast(1), // left speaker
x=>x.color('#ff00ff50').pan(1).fast(1.01) // right speaker
)
.slow(2).room(.5)
`}
/>
@@ -1,28 +0,0 @@
import { SlideRepl } from '../SlideRepl.jsx';
# What is Strudel
- TidalCycles faithfully ported to JavaScript
- zero-install live coding environment for the browser
- free & open source, modular, hackable, sharable, embeddable:
<SlideRepl
hideHeader
fontSize={24}
tune={`// froos - freakuency - 2023/03/29
stack(
stack(
s("bd, ~ <rim cp>,~ hh*2").off(1/8, x=>x.speed(2).gain(.5)).fast(2),
s("oh*6").end(.01).speed(.5).room(.5).fast("1 2")
).bank('RolandTR909'),
//
note("[c a f e](<3 5>,8,<0 1>)".add("0,.1").sub(24)).s('sawtooth').lpf(500),
//
"[~ <Cm7 Fm7 Bb7 Db7>]*2".voicings('lefthand').note().legato(.2)
.echoWith(6, .1, (p,n)=>p.add(note(n*.6)))
.lpf(sine.range(300,1000).slow(8)).lpq(6)
.add(note(perlin.range(0,.8))).s('gm_epiano2')
.mask("<0 1>")
).restart("<1@3 1*[4 [8 16]]>")
.slow(2)`}
/>
-12
View File
@@ -1,12 +0,0 @@
import { useEffect } from 'react';
function useEvent(name, onTrigger, useCapture = false) {
useEffect(() => {
document.addEventListener(name, onTrigger, useCapture);
return () => {
document.removeEventListener(name, onTrigger, useCapture);
};
}, [onTrigger]);
}
export default useEvent;
-26
View File
@@ -1,26 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./index.html',
'./src/**/*.{js,ts,jsx,tsx,mdx}',
'../../packages/react/src/**/*.{html,js,jsx,md,mdx,ts,tsx}',
],
theme: {
extend: {
colors: {
// codemirror-theme settings
background: 'var(--background)',
lineBackground: 'var(--lineBackground)',
foreground: 'var(--foreground)',
caret: 'var(--caret)',
selection: 'var(--selection)',
selectionMatch: 'var(--selectionMatch)',
gutterBackground: 'var(--gutterBackground)',
gutterForeground: 'var(--gutterForeground)',
gutterBorder: 'var(--gutterBorder)',
lineHighlight: 'var(--lineHighlight)',
},
},
},
plugins: [require('@tailwindcss/typography')],
};
-28
View File
@@ -1,28 +0,0 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import mdx from '@mdx-js/rollup';
import gfm from 'remark-gfm';
import topLevelAwait from 'vite-plugin-top-level-await';
const options = {
// See https://mdxjs.com/advanced/plugins
remarkPlugins: [gfm],
rehypePlugins: [],
};
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
mdx(options),
topLevelAwait({
// The export name of top-level await promise for each chunk module
promiseExportName: '__tla',
// The function to generate import names of top-level await promise in each chunk module
promiseImportName: (i) => `__tla_${i}`,
}),
],
build: {
target: 'esnext', //browsers can handle the latest ES features
},
});
-17
View File
@@ -1,17 +0,0 @@
// this barrel export is currently only used to find undocumented exports
export * from './packages/core/index.mjs';
export * from './packages/csound/index.mjs';
export * from './packages/embed/index.mjs';
export * from './packages/eval/index.mjs';
export * from './packages/midi/index.mjs';
export * from './packages/mini/index.mjs';
export * from './packages/osc/index.mjs';
export * from './packages/react/index.mjs';
export * from './packages/serial/index.mjs';
export * from './packages/soundfonts/index.mjs';
export * from './packages/tonal/index.mjs';
export * from './packages/tone/index.mjs';
export * from './packages/transpiler/index.mjs';
export * from './packages/webaudio/index.mjs';
export * from './packages/webdirt/index.mjs';
export * from './packages/xen/index.mjs';
+1 -3
View File
@@ -2,7 +2,5 @@
"packages": [
"packages/*"
],
"version": "independent",
"npmClient": "pnpm",
"useWorkspaces": true
"version": "independent"
}
+10 -49
View File
@@ -3,57 +3,18 @@
This directory can be used to save your own patterns, which then get
made into a pattern swatch.
Example: <https://felixroos.github.io/strudel/swatch/>
0. fork and clone the strudel repository
1. run `npm run setup` in the strudel folder
1. Save one or more .txt files in this folder
2. run `npm run repl` in the top-level strudel folder
3. open `http://localhost:3000/swatch/` !
## deploy
### 1. fork the [strudel repo on github](https://github.com/tidalcycles/strudel.git)
### 2. clone your fork to your machine `git clone https://github.com/<your-username>/strudel.git strudel && cd strudel`
### 3. create a separate branch like `git branch patternuary && git checkout patternuary`
### 4. save one or more .txt files in the my-patterns folder
### 5. edit `website/public/CNAME` to contain `<your-username>.github.io/strudel`
### 6. edit `website/astro.config.mjs` to use site: `https://<your-username>.github.io` and base `/strudel`, like this
```js
const site = 'https://<your-username>.github.io';
const base = '/strudel';
```
### 7. commit & push the changes
```sh
git add . && git commit -m "site config" && git push --set-upstream origin
```
### 8. deploy to github pages
- go to settings -> pages and select "Github Actions" as source
- go to settings -> environments -> github-pages and press the edit button next to `main` and type in `patternuary` (under "Deployment branches")
- go to Actions -> `Build and Deploy` and click `Run workflow` with branch `patternuary`
### 9. view your patterns at `<your-username>.github.io/strudel/swatch/`
1. in your fork, go to settings -> pages and select "Github Actions" as source
2. edit `website/public/CNAME` to contain `<your-username>.github.io/strudel`
3. edit `website/astro.config.mjs` to use site: `https://<your-username>.github.io` and base `/strudel`
4. go to Actions -> `Build and Deploy` and click `Run workflow`
5. view your patterns at `<your-username>.github.io/strudel/swatch/`
Alternatively, github pages allows you to use a custom domain, like https://mycooldomain.org/swatch/. [See their documentation for details](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site).
### 10. optional: automatic deployment
If you want to automatically deploy your site on push, go to `deploy.yml` and change `workflow_dispatch` to `push`.
## running locally
- install dependencies with `npm run setup`
- run dev server with `npm run repl` and open `http://localhost:3000/strudel/swatch/`
## tests fail?
Your tests might fail if the code does not follow prettiers format.
In that case, run `npm run codeformat`. To disable that, remove `npm run format-check` from `test.yml`
## updating your fork
To update your fork, you can pull the main branch and merge it into your `patternuary` branch.
+20806
View File
File diff suppressed because it is too large Load Diff
+15 -26
View File
@@ -4,30 +4,30 @@
"private": true,
"description": "Port of tidalcycles to javascript",
"scripts": {
"setup": "pnpm i",
"pretest": "npm run jsdoc-json",
"prebuild": "npm run jsdoc-json",
"prestart": "npm run jsdoc-json",
"test": "npm run pretest && vitest run --version",
"test-ui": "npm run pretest && vitest --ui",
"test-coverage": "npm run pretest && vitest --coverage",
"snapshot": "npm run pretest && vitest run -u --silent",
"repl": "npm run prestart && cd website && npm run dev",
"start": "npm run prestart && cd website && npm run dev",
"dev": "npm run prestart && cd website && npm run dev",
"build": "npm run prebuild && cd website && npm run build",
"preview": "cd website && npm run preview",
"test": "vitest run --version",
"test-ui": "vitest --ui",
"test-coverage": "vitest --coverage",
"bootstrap": "lerna bootstrap",
"setup": "npm i && npm run jsdoc-json && npm run bootstrap && cd website && npm i",
"snapshot": "vitest run -u --silent",
"repl": "cd website && npm run dev",
"osc": "cd packages/osc && npm run server",
"build": "cd website && npm run build",
"preview": "cd website && npm run preview",
"deploy": "NODE_DEBUG=gh-pages gh-pages -d out",
"jsdoc": "jsdoc packages/ -c jsdoc.config.json",
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json",
"lint": "eslint . --ext mjs,js --quiet",
"codeformat": "prettier --write .",
"format-check": "prettier --check .",
"report-undocumented": "npm run jsdoc-json && node undocumented.mjs > undocumented.json",
"check": "npm run format-check && npm run lint && npm run test",
"iclc": "cd paper && pandoc --template=pandoc/iclc.html --citeproc --number-sections iclc2023.md -o iclc2023.html && pandoc --template=pandoc/iclc.latex --citeproc --number-sections iclc2023.md -o iclc2023.pdf"
},
"workspaces": [
"packages/*"
],
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
@@ -45,30 +45,19 @@
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://strudel.tidalcycles.org",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/xen": "workspace:*",
"acorn": "^8.8.1",
"dependency-tree": "^9.0.0",
"vitest": "^0.25.7"
},
"devDependencies": {
"@vitest/ui": "^0.25.7",
"c8": "^7.12.0",
"canvas": "^2.11.0",
"eslint": "^8.28.0",
"eslint-plugin-import": "^2.27.5",
"events": "^3.3.0",
"gh-pages": "^4.0.0",
"jsdoc": "^3.6.10",
"jsdoc-json": "^2.0.2",
"jsdoc-to-markdown": "^7.1.1",
"lerna": "^6.5.1",
"lerna": "^4.0.0",
"prettier": "^2.8.1",
"rollup-plugin-visualizer": "^5.8.1"
"rollup-plugin-visualizer": "^5.8.1",
"vitest": "^0.25.7"
}
}
+3 -4
View File
@@ -33,7 +33,6 @@ b: 3/2 - 7/4
c: 7/4 - 2
```
- [play with @strudel.cycles/core on codesandbox](https://codesandbox.io/s/strudel-core-test-forked-9ywhv7?file=/src/index.js).
- [open color pattern example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/canvas.html)
- [open minimal repl example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/vanilla.html)
- [open minimal vite example](./examples/vite-vanilla-repl/)
- [play with @strudel.cycles/core on codesandbox](https://codesandbox.io/s/strudel-core-test-qmz6qr?file=/src/index.js).
- [open color pattern example](https://raw.githack.com/tidalcycles/strudel/package-examples/packages/core/examples/canvas.html)
- [open minimal repl example](https://raw.githack.com/tidalcycles/strudel/package-examples/packages/core/examples/metro.html)
+4 -4
View File
@@ -22,11 +22,11 @@ Pattern.prototype.animate = function ({ callback, sync = false, smear = 0.5 } =
ctx.fillStyle = clearColor;
ctx.fillRect(0, 0, ww, wh);
frame.forEach((f) => {
let { x, y, w, h, s, r, angle = 0, fill = 'darkseagreen' } = f.value;
let { x, y, w, h, s, r, a = 0, fill = 'darkseagreen' } = f.value;
w *= ww;
h *= wh;
if (r !== undefined && angle !== undefined) {
const radians = angle * 2 * Math.PI;
if (r !== undefined && a !== undefined) {
const radians = a * 2 * Math.PI;
const [cx, cy] = [(ww - w) / 2, (wh - h) / 2];
x = cx + Math.cos(radians) * r * cx;
y = cy + Math.sin(radians) * r * cy;
@@ -51,7 +51,7 @@ Pattern.prototype.animate = function ({ callback, sync = false, smear = 0.5 } =
return silence;
};
export const { x, y, w, h, angle, r, fill, smear } = createParams('x', 'y', 'w', 'h', 'angle', 'r', 'fill', 'smear');
export const { x, y, w, h, a, r, fill, smear } = createParams('x', 'y', 'w', 'h', 'a', 'r', 'fill', 'smear');
export const rescale = register('rescale', function (f, pat) {
return pat.mul(x(f).w(f).y(f).h(f));
+342 -319
View File
File diff suppressed because it is too large Load Diff
+21 -24
View File
@@ -8,51 +8,49 @@ import createClock from './zyklus.mjs';
import { logger } from './logger.mjs';
export class Cyclist {
worker;
pattern;
started = false;
cps = 1; // TODO
getTime;
phase = 0;
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) {
this.started = false;
this.cps = 1;
this.lastTick = 0; // absolute time when last tick (clock callback) happened
this.lastBegin = 0; // query begin of last tick
this.lastEnd = 0; // query end of last tick
this.getTime = getTime; // get absolute time
this.getTime = getTime;
this.onToggle = onToggle;
this.latency = latency; // fixed trigger time offset
this.latency = latency;
const round = (x) => Math.round(x * 1000) / 1000;
this.clock = createClock(
getTime,
// called slightly before each cycle
(phase, duration, tick) => {
if (tick === 0) {
this.origin = phase;
}
const begin = round(phase - this.origin);
this.phase = begin - latency;
const end = round(begin + duration);
const time = getTime();
try {
const time = getTime();
const begin = this.lastEnd;
this.lastBegin = begin;
const end = round(begin + duration * this.cps);
this.lastEnd = end;
const haps = this.pattern.queryArc(begin, end);
const tickdeadline = phase - time; // time left till phase begins
this.lastTick = time + tickdeadline;
const haps = this.pattern.queryArc(begin, end); // get Haps
haps.forEach((hap) => {
if (hap.part.begin.equals(hap.whole.begin)) {
const deadline = (hap.whole.begin - begin) / this.cps + tickdeadline + latency;
const duration = hap.duration / this.cps;
onTrigger?.(hap, deadline, duration, this.cps);
const deadline = hap.whole.begin + this.origin - time + latency;
const duration = hap.duration * 1;
onTrigger?.(hap, deadline, duration);
}
});
} catch (e) {
logger(`[cyclist] error: ${e.message}`);
onError?.(e);
}
},
}, // called slightly before each cycle
interval, // duration of each cycle
);
}
getPhase() {
return this.getTime() - this.origin - this.latency;
}
now() {
const secondsSinceLastTick = this.getTime() - this.lastTick - this.clock.duration;
return this.lastBegin + secondsSinceLastTick * this.cps; // + this.clock.minLatency;
return this.getTime() - this.origin + this.clock.minLatency;
}
setStarted(v) {
this.started = v;
@@ -74,7 +72,6 @@ export class Cyclist {
stop() {
logger('[cyclist] stop');
this.clock.stop();
this.lastEnd = 0;
this.setStarted(false);
}
setPattern(pat, autostart = false) {
-6
View File
@@ -59,9 +59,3 @@ export const cleanupDraw = (clearScreen = true) => {
clearInterval(window.strudelScheduler);
}
};
Pattern.prototype.onPaint = function (onPaint) {
// this is evil! TODO: add pattern.context
this.context = { onPaint };
return this;
};
+8 -2
View File
@@ -11,9 +11,15 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import { Pattern, timeCat, register, silence } from './pattern.mjs';
import { rotate, flatten, splitAt, zipWith } from './util.mjs';
import { rotate, flatten } from './util.mjs';
import Fraction from './fraction.mjs';
const splitAt = function (index, value) {
return [value.slice(0, index), value.slice(index)];
};
const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i]));
const left = function (n, x) {
const [ons, offs] = n;
const [xs, ys] = x;
@@ -40,7 +46,7 @@ const _bjork = function (n, x) {
return Math.min(ons, offs) <= 1 ? [n, x] : _bjork(...(ons > offs ? left(n, x) : right(n, x)));
};
export const bjork = function (ons, steps) {
const bjork = function (ons, steps) {
const offs = steps - ons;
const x = Array(ons).fill([1]);
const y = Array(offs).fill([0]);
+8
View File
@@ -6,7 +6,12 @@ This program is free software: you can redistribute it and/or modify it under th
import { isPattern } from './index.mjs';
let scoped = false;
export const evalScope = async (...args) => {
if (scoped) {
console.warn('evalScope was called more than once.');
}
scoped = true;
const results = await Promise.allSettled(args);
const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value);
results.forEach((result, i) => {
@@ -37,6 +42,9 @@ function safeEval(str, options = {}) {
}
export const evaluate = async (code, transpiler) => {
if (!scoped) {
await evalScope(); // at least scope Pattern.prototype.boostrap
}
if (transpiler) {
code = transpiler(code); // transform syntactically correct js code to semantically usable code
}
+2 -2
View File
@@ -1,13 +1,13 @@
<input
type="text"
id="text"
value="seq('a', ['b', 'c'])"
value="cat('a', 'b')"
style="width: 100%; font-size: 2em; outline: none; margin-bottom: 10px"
spellcheck="false"
/>
<div id="output"></div>
<script type="module">
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8');
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.0.2');
Object.assign(window, strudel); // assign all strudel functions to global scope to use with eval
const input = document.getElementById('text');
const getEvents = () => {
+2 -2
View File
@@ -2,13 +2,13 @@
<input
type="text"
id="text"
value="seq('tomato', 'indigo', ['white', 'steelblue']).fast(4)"
value="cat('orange', 'indigo')"
style="width: 100%; font-size: 2em; background: black; color: white; outline: none; position: absolute; top: 0"
spellcheck="false"
/>
<canvas id="canvas"></canvas>
<script type="module">
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8');
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.0.2');
// this adds all strudel functions to the global scope, to be used by eval
Object.assign(window, strudel);
// setup elements
+90
View File
@@ -0,0 +1,90 @@
<div style="position: absolute; bottom: 0; right: 0; padding: 4px; width: 100vw; display: flex">
<button id="start" style="font-size: 2em">start</button>
<button id="stop" style="font-size: 2em">stop</button>
<button id="slower" style="font-size: 2em">slower</button>
<button id="faster" style="font-size: 2em">faster</button>
</div>
<textarea
style="font-size: 2em; background: #052b49; color: #fff; height: 100%; width: 100%; outline: none; border: 0"
id="text"
spellcheck="false"
>
Loading...</textarea
>
<script type="module">
document.body.style = 'margin: 0';
import * as strudel from '@strudel.cycles/core';
const { cat, State, TimeSpan, Scheduler, getPlayableNoteValue, getFreq } = strudel;
Object.assign(window, strudel); // add strudel to eval scope
const ctx = new AudioContext();
const scheduler = new Scheduler({
// audioContext: getAudioContext(),
interval: 0.1,
onTrigger: (hap, time, duration) => {
const freq = getFrequency(hap);
const osc = ctx.createOscillator();
const gain = 0.2;
osc.frequency.value = freq;
osc.type = 'triangle';
const onset = ctx.currentTime + time;
const offset = onset + duration;
const attack = 0.05;
const release = 0.05;
osc.start(onset);
osc.stop(offset + release);
const g = ctx.createGain();
g.gain.setValueAtTime(gain, onset);
g.gain.linearRampToValueAtTime(gain, onset + attack);
g.gain.setValueAtTime(gain, offset - release);
g.gain.linearRampToValueAtTime(0, offset);
osc.connect(g);
g.connect(ctx.destination);
},
});
let initialCode = `stack('c4','e4',cat('g4','a4','b4','a4'))
.add(cat(0,1,2,3,4,3,2,1).slow(8))
.fast(2)
.cps(tri.range(1,8).slow(32))`;
try {
const base64 = decodeURIComponent(window.location.href.split('#')[1]);
initialCode = atob(base64);
} catch (err) {
console.warn('failed to decode', err);
}
const input = document.getElementById('text');
input.value = initialCode;
const evaluate = () => {
try {
const pattern = eval(input.value);
scheduler.setPattern(pattern);
window.location.hash = '#' + encodeURIComponent(btoa(input.value)); // update url hash
} catch (err) {
console.warn(err);
}
};
evaluate();
input.addEventListener('input', () => evaluate());
document.getElementById('start').addEventListener('click', async () => {
await ctx.resume();
scheduler.start();
});
document.getElementById('stop').addEventListener('click', () => scheduler.stop());
document.getElementById('slower').addEventListener('click', () => scheduler.setCps(scheduler.cps - 0.1));
document.getElementById('faster').addEventListener('click', () => scheduler.setCps(scheduler.cps + 0.1));
</script>
<!--
sequence(1,2).mul(55/2) // frequencies
.mul(slowcat(1,2))
.mul(slowcat(1,3/2,4/3,5/3).slow(8))
.fast(3)
.freq()
.velocity(.5)
.s('sawtooth')
.cutoff(800)
.out()
-->
+44
View File
@@ -0,0 +1,44 @@
<input
type="text"
id="text"
value="seq('c3','eb3','g3').note().s('sawtooth').out()"
style="width: 100%; font-size: 2em; outline: none; margin-bottom: 10px"
spellcheck="false"
/>
<button id="start">play</button>
<div id="output"></div>
<script type="module">
const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@latest');
const controls = await import('https://cdn.skypack.dev/@strudel.cycles/core@latest/controls.mjs');
const { getAudioContext, Scheduler } = await import('https://cdn.skypack.dev/@strudel.cycles/webaudio@latest');
let scheduler;
const audioContext = getAudioContext();
const latency = 0.2;
Object.assign(window, strudel);
Object.assign(window, controls.default);
scheduler = new Scheduler({
audioContext,
interval: 0.1,
latency,
onEvent: (hap) => {
if (!hap.context.onTrigger) {
console.warn('no output chosen. use one of .out() .webdirt() .osc()');
}
},
});
let started;
document.getElementById('start').addEventListener('click', async () => {
const code = document.getElementById('text').value;
const pattern = eval(code);
const events = pattern._firstCycleValues;
console.log(code, '->', events);
scheduler.setPattern(pattern);
if (!started) {
scheduler.start();
started = true;
}
});
</script>
+29 -16
View File
@@ -6,36 +6,46 @@
<title>Buildless Vanilla Strudel REPL</title>
</head>
<body style="margin: 0; background: #222">
<div style="display: grid; height: 100vh; grid-template-rows: 32px auto">
<button id="start" style="width: 100vw; height: 32px">evaluate</button>
<div style="display: grid; height: 100vh">
<textarea
id="text"
style="font-size: 2em; border: 0; color: white; background: transparent; outline: none; padding: 20px"
spellcheck="false"
></textarea>
</div>
<button
id="start"
style="
position: absolute;
border-radius: 10px;
top: 20px;
right: 20px;
padding: 20px;
border: 2px solid white;
background: transparent;
color: white;
cursor: pointer;
"
>
evaluate
</button>
<div id="output"></div>
<script type="module">
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel.cycles/core@0.6.8';
import { mini } from 'https://cdn.skypack.dev/@strudel.cycles/mini@0.6.0';
import { transpiler } from 'https://cdn.skypack.dev/@strudel.cycles/transpiler@0.6.0';
import {
getAudioContext,
webaudioOutput,
initAudioOnFirstClick,
} from 'https://cdn.skypack.dev/@strudel.cycles/webaudio@0.6.0';
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel.cycles/core@0.3.2';
import { mini } from 'https://cdn.skypack.dev/@strudel.cycles/mini@0.3.2';
import { transpiler } from 'https://cdn.skypack.dev/@strudel.cycles/transpiler@0.3.2';
import { getAudioContext, webaudioOutput } from 'https://cdn.skypack.dev/@strudel.cycles/webaudio@0.3.3';
initAudioOnFirstClick();
const ctx = getAudioContext();
const input = document.getElementById('text');
input.innerHTML = getTune();
evalScope(
controls,
import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8'),
import('https://cdn.skypack.dev/@strudel.cycles/mini@0.6.0'),
import('https://cdn.skypack.dev/@strudel.cycles/tonal@0.6.0'),
import('https://cdn.skypack.dev/@strudel.cycles/webaudio@0.6.0'),
import('https://cdn.skypack.dev/@strudel.cycles/core@0.3.2'),
import('https://cdn.skypack.dev/@strudel.cycles/mini@0.3.2'),
import('https://cdn.skypack.dev/@strudel.cycles/tonal@0.3.3'),
import('https://cdn.skypack.dev/@strudel.cycles/webaudio@0.3.3'),
);
const { evaluate } = repl({
@@ -43,7 +53,10 @@
getTime: () => ctx.currentTime,
transpiler,
});
document.getElementById('start').addEventListener('click', () => evaluate(input.value));
document.getElementById('start').addEventListener('click', () => {
ctx.resume();
evaluate(input.value);
});
function getTune() {
return `await samples('github:tidalcycles/Dirt-Samples/master')
@@ -1,12 +1,12 @@
import { controls, repl, evalScope } from '@strudel.cycles/core';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import { transpiler } from '@strudel.cycles/transpiler';
import { controls, repl, evalScope, setStringParser } from '@strudel.cycles/core';
import { mini } from '@strudel.cycles/mini';
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
// import { transpiler } from '@strudel.cycles/transpiler';
import tune from './tune.mjs';
const ctx = getAudioContext();
const input = document.getElementById('text');
input.innerHTML = tune;
initAudioOnFirstClick();
evalScope(
controls,
@@ -16,13 +16,14 @@ evalScope(
import('@strudel.cycles/tonal'),
);
setStringParser(mini);
const { evaluate } = repl({
defaultOutput: webaudioOutput,
getTime: () => ctx.currentTime,
transpiler,
// transpiler,
});
document.getElementById('start').addEventListener('click', () => {
ctx.resume();
console.log('eval', input.value);
evaluate(input.value);
});
+885
View File
@@ -0,0 +1,885 @@
{
"name": "vite-vanilla-repl",
"version": "0.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "vite-vanilla-repl",
"version": "0.0.0",
"devDependencies": {
"vite": "^3.2.0"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz",
"integrity": "sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz",
"integrity": "sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.13.tgz",
"integrity": "sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.15.13",
"@esbuild/linux-loong64": "0.15.13",
"esbuild-android-64": "0.15.13",
"esbuild-android-arm64": "0.15.13",
"esbuild-darwin-64": "0.15.13",
"esbuild-darwin-arm64": "0.15.13",
"esbuild-freebsd-64": "0.15.13",
"esbuild-freebsd-arm64": "0.15.13",
"esbuild-linux-32": "0.15.13",
"esbuild-linux-64": "0.15.13",
"esbuild-linux-arm": "0.15.13",
"esbuild-linux-arm64": "0.15.13",
"esbuild-linux-mips64le": "0.15.13",
"esbuild-linux-ppc64le": "0.15.13",
"esbuild-linux-riscv64": "0.15.13",
"esbuild-linux-s390x": "0.15.13",
"esbuild-netbsd-64": "0.15.13",
"esbuild-openbsd-64": "0.15.13",
"esbuild-sunos-64": "0.15.13",
"esbuild-windows-32": "0.15.13",
"esbuild-windows-64": "0.15.13",
"esbuild-windows-arm64": "0.15.13"
}
},
"node_modules/esbuild-android-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.13.tgz",
"integrity": "sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-android-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.13.tgz",
"integrity": "sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-darwin-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.13.tgz",
"integrity": "sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-darwin-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.13.tgz",
"integrity": "sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-freebsd-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.13.tgz",
"integrity": "sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-freebsd-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.13.tgz",
"integrity": "sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-32": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.13.tgz",
"integrity": "sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.13.tgz",
"integrity": "sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-arm": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.13.tgz",
"integrity": "sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.13.tgz",
"integrity": "sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-mips64le": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.13.tgz",
"integrity": "sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-ppc64le": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.13.tgz",
"integrity": "sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-riscv64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.13.tgz",
"integrity": "sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-linux-s390x": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.13.tgz",
"integrity": "sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-netbsd-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.13.tgz",
"integrity": "sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-openbsd-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.13.tgz",
"integrity": "sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-sunos-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.13.tgz",
"integrity": "sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-windows-32": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.13.tgz",
"integrity": "sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-windows-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.13.tgz",
"integrity": "sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/esbuild-windows-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.13.tgz",
"integrity": "sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/is-core-module": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
"dev": true,
"dependencies": {
"has": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"node_modules/postcss": {
"version": "8.4.18",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
}
],
"dependencies": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
"dev": true,
"dependencies": {
"is-core-module": "^2.9.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/rollup": {
"version": "2.79.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=10.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/vite": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.2.tgz",
"integrity": "sha512-pLrhatFFOWO9kS19bQ658CnRYzv0WLbsPih6R+iFeEEhDOuYgYCX2rztUViMz/uy/V8cLCJvLFeiOK7RJEzHcw==",
"dev": true,
"dependencies": {
"esbuild": "^0.15.9",
"postcss": "^8.4.18",
"resolve": "^1.22.1",
"rollup": "^2.79.1"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
},
"peerDependencies": {
"less": "*",
"sass": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"less": {
"optional": true
},
"sass": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
}
},
"dependencies": {
"@esbuild/android-arm": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz",
"integrity": "sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==",
"dev": true,
"optional": true
},
"@esbuild/linux-loong64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz",
"integrity": "sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==",
"dev": true,
"optional": true
},
"esbuild": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.13.tgz",
"integrity": "sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==",
"dev": true,
"requires": {
"@esbuild/android-arm": "0.15.13",
"@esbuild/linux-loong64": "0.15.13",
"esbuild-android-64": "0.15.13",
"esbuild-android-arm64": "0.15.13",
"esbuild-darwin-64": "0.15.13",
"esbuild-darwin-arm64": "0.15.13",
"esbuild-freebsd-64": "0.15.13",
"esbuild-freebsd-arm64": "0.15.13",
"esbuild-linux-32": "0.15.13",
"esbuild-linux-64": "0.15.13",
"esbuild-linux-arm": "0.15.13",
"esbuild-linux-arm64": "0.15.13",
"esbuild-linux-mips64le": "0.15.13",
"esbuild-linux-ppc64le": "0.15.13",
"esbuild-linux-riscv64": "0.15.13",
"esbuild-linux-s390x": "0.15.13",
"esbuild-netbsd-64": "0.15.13",
"esbuild-openbsd-64": "0.15.13",
"esbuild-sunos-64": "0.15.13",
"esbuild-windows-32": "0.15.13",
"esbuild-windows-64": "0.15.13",
"esbuild-windows-arm64": "0.15.13"
}
},
"esbuild-android-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.13.tgz",
"integrity": "sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==",
"dev": true,
"optional": true
},
"esbuild-android-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.13.tgz",
"integrity": "sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==",
"dev": true,
"optional": true
},
"esbuild-darwin-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.13.tgz",
"integrity": "sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==",
"dev": true,
"optional": true
},
"esbuild-darwin-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.13.tgz",
"integrity": "sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==",
"dev": true,
"optional": true
},
"esbuild-freebsd-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.13.tgz",
"integrity": "sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==",
"dev": true,
"optional": true
},
"esbuild-freebsd-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.13.tgz",
"integrity": "sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==",
"dev": true,
"optional": true
},
"esbuild-linux-32": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.13.tgz",
"integrity": "sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==",
"dev": true,
"optional": true
},
"esbuild-linux-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.13.tgz",
"integrity": "sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==",
"dev": true,
"optional": true
},
"esbuild-linux-arm": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.13.tgz",
"integrity": "sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==",
"dev": true,
"optional": true
},
"esbuild-linux-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.13.tgz",
"integrity": "sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==",
"dev": true,
"optional": true
},
"esbuild-linux-mips64le": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.13.tgz",
"integrity": "sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==",
"dev": true,
"optional": true
},
"esbuild-linux-ppc64le": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.13.tgz",
"integrity": "sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==",
"dev": true,
"optional": true
},
"esbuild-linux-riscv64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.13.tgz",
"integrity": "sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==",
"dev": true,
"optional": true
},
"esbuild-linux-s390x": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.13.tgz",
"integrity": "sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==",
"dev": true,
"optional": true
},
"esbuild-netbsd-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.13.tgz",
"integrity": "sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==",
"dev": true,
"optional": true
},
"esbuild-openbsd-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.13.tgz",
"integrity": "sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==",
"dev": true,
"optional": true
},
"esbuild-sunos-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.13.tgz",
"integrity": "sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==",
"dev": true,
"optional": true
},
"esbuild-windows-32": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.13.tgz",
"integrity": "sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==",
"dev": true,
"optional": true
},
"esbuild-windows-64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.13.tgz",
"integrity": "sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==",
"dev": true,
"optional": true
},
"esbuild-windows-arm64": {
"version": "0.15.13",
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.13.tgz",
"integrity": "sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==",
"dev": true,
"optional": true
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1"
}
},
"is-core-module": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
"dev": true,
"requires": {
"has": "^1.0.3"
}
},
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true
},
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"postcss": {
"version": "8.4.18",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
"dev": true,
"requires": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
},
"resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
"dev": true,
"requires": {
"is-core-module": "^2.9.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
}
},
"rollup": {
"version": "2.79.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
"dev": true,
"requires": {
"fsevents": "~2.3.2"
}
},
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true
},
"supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true
},
"vite": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.2.tgz",
"integrity": "sha512-pLrhatFFOWO9kS19bQ658CnRYzv0WLbsPih6R+iFeEEhDOuYgYCX2rztUViMz/uy/V8cLCJvLFeiOK7RJEzHcw==",
"dev": true,
"requires": {
"esbuild": "^0.15.9",
"fsevents": "~2.3.2",
"postcss": "^8.4.18",
"resolve": "^1.22.1",
"rollup": "^2.79.1"
}
}
}
}
@@ -11,12 +11,5 @@
},
"devDependencies": {
"vite": "^3.2.0"
},
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/tonal": "workspace:*"
}
}
@@ -1,4 +1,4 @@
export default `await samples('github:tidalcycles/Dirt-Samples/master')
/* export default `await samples('github:tidalcycles/Dirt-Samples/master')
stack(
// amen
@@ -29,3 +29,11 @@ stack(
,
n("0 1").s("east").delay(.5).degradeBy(.8).speed(rand.range(.5,1.5))
).reset("<x@7 x(5,8)>")`;
*/
export default `stack(
n("c3 [eb3,g3]")
.delay("<0 .5>")
.delaytime(".16 | .33")
.delayfeedback(".6 | .8")
).sometimes(x=>x.speed("-1"))`;
+65
View File
@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Buildless Vanilla Strudel REPL</title>
</head>
<body style="margin: 0; background: #222">
<div style="display: grid; height: 100vh">
<textarea
id="text"
style="font-size: 2em; border: 0; color: white; background: transparent; outline: none; padding: 20px"
spellcheck="false"
>
// LOADING</textarea
>
</div>
<button
id="start"
style="
position: absolute;
border-radius: 10px;
top: 20px;
right: 20px;
padding: 20px;
border: 2px solid white;
background: transparent;
color: white;
cursor: pointer;
"
>
evaluate
</button>
<div id="output"></div>
<script type="module">
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel.cycles/core@0.4.1';
import { transpiler } from 'https://cdn.skypack.dev/@strudel.cycles/transpiler@0.4.1';
const modules = [
import('https://cdn.skypack.dev/@strudel.cycles/core@0.4.1'),
import('https://cdn.skypack.dev/@strudel.cycles/mini@0.4.1'),
];
const input = document.getElementById('text');
Promise.all(modules).then(() => {
input.innerHTML = `note("<c3 [d3 e3]>").cutoff(1000)`;
document.getElementById('start').addEventListener('click', () => {
evaluate(input.value);
});
});
evalScope(controls, ...modules);
const { evaluate } = repl({
defaultOutput: (hap, deadline, duration) => {
console.log(deadline, duration, hap.value);
},
getTime: () => Date.now() / 1000,
transpiler,
beforeEval: (code) => console.log('evaluate', code),
afterEval: (code) => {},
});
</script>
</body>
</html>
-13
View File
@@ -126,19 +126,6 @@ export class Hap {
setContext(context) {
return new Hap(this.whole, this.part, this.value, context);
}
ensureObjectValue() {
/* if (isNote(hap.value)) {
// supports primitive hap values that look like notes
hap.value = { note: hap.value };
} */
if (typeof this.value !== 'object') {
throw new Error(
`expected hap.value to be an object, but got "${this.value}". Hint: append .note() or .s() to the end`,
'error',
);
}
}
}
export default Hap;
+1639
View File
File diff suppressed because it is too large Load Diff
+3 -13
View File
@@ -1,17 +1,11 @@
{
"name": "@strudel.cycles/core",
"version": "0.7.2",
"version": "0.5.0",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"type": "module",
"publishConfig": {
"main": "dist/index.js",
"module": "dist/index.mjs"
},
"scripts": {
"test": "vitest run",
"build": "vite build",
"prepublishOnly": "pnpm build"
"test": "vitest run"
},
"repository": {
"type": "git",
@@ -33,9 +27,5 @@
"dependencies": {
"fraction.js": "^4.2.0"
},
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
"devDependencies": {
"vite": "^3.2.2",
"vitest": "^0.25.7"
}
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2"
}
+91 -417
View File
File diff suppressed because it is too large Load Diff
+2 -14
View File
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern, noteToMidi, getDrawContext, freqToMidi, isNote } from './index.mjs';
import { Pattern, toMidi, getDrawContext, freqToMidi, isNote } from './index.mjs';
const scale = (normalized, min, max) => normalized * (max - min) + min;
const getValue = (e) => {
@@ -18,7 +18,7 @@ const getValue = (e) => {
}
note = note ?? n;
if (typeof note === 'string') {
return noteToMidi(note);
return toMidi(note);
}
if (typeof note === 'number') {
return note;
@@ -283,15 +283,3 @@ export function pianoroll({
ctx.stroke();
return this;
}
function getOptions(drawTime, options = {}) {
let [lookbehind, lookahead] = drawTime;
lookbehind = Math.abs(lookbehind);
const cycles = lookahead + lookbehind;
const playhead = lookbehind / cycles;
return { fold: 1, ...options, cycles, playhead };
}
Pattern.prototype.punchcard = function (options) {
return this.onPaint((ctx, time, haps, drawTime) => pianoroll({ ctx, time, haps, ...getOptions(drawTime, options) }));
};
+8 -20
View File
@@ -2,7 +2,6 @@ import { Cyclist } from './cyclist.mjs';
import { evaluate as _evaluate } from './evaluate.mjs';
import { logger } from './logger.mjs';
import { setTime } from './time.mjs';
import { evalScope } from './evaluate.mjs';
export function repl({
interval,
@@ -13,18 +12,18 @@ export function repl({
afterEval,
getTime,
transpiler,
onToggle,
editPattern,
onToggle,
}) {
const id = s4();
const scheduler = new Cyclist({
interval,
onTrigger: async (hap, deadline, duration, cps) => {
onTrigger: async (hap, deadline, duration) => {
try {
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
await defaultOutput(hap, deadline, duration, cps);
await defaultOutput(hap, deadline, duration);
}
if (hap.context.onTrigger) {
const cps = 1;
// call signature of output / onTrigger is different...
await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps);
}
@@ -36,17 +35,17 @@ export function repl({
getTime,
onToggle,
});
setTime(() => scheduler.now()); // TODO: refactor?
setTime(() => scheduler.getPhase()); // TODO: refactor?
const evaluate = async (code, autostart = true) => {
if (!code) {
throw new Error('no code to evaluate');
}
try {
await beforeEval?.({ code });
beforeEval?.({ code });
let { pattern } = await _evaluate(code, transpiler);
logger(`[eval] code updated`);
pattern = editPattern?.(pattern, id) || pattern;
pattern = editPattern?.(pattern) || pattern;
scheduler.setPattern(pattern, autostart);
afterEval?.({ code, pattern });
return pattern;
@@ -59,16 +58,5 @@ export function repl({
const stop = () => scheduler.stop();
const start = () => scheduler.start();
const pause = () => scheduler.pause();
const setCps = (cps) => scheduler.setCps(cps);
evalScope({
setCps,
setcps: setCps,
});
return { scheduler, evaluate, start, stop, pause, setCps, id };
}
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
return { scheduler, evaluate, start, stop, pause };
}
-8
View File
@@ -114,14 +114,6 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
*
*/
/**
* A discrete pattern of numbers from 0 to n-1
* @example
* run(4).scale('C4 major').note()
* // "0 1 2 3".scale('C4 major').note()
*/
export const run = (n) => saw.range(0, n).floor().segment(n);
/**
* A continuous pattern of random numbers, between 0 and 1.
*
-28
View File
@@ -1,28 +0,0 @@
/*
controls.test.mjs - <short description TODO>
Copyright (C) 2023 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/controls.test.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import controls from '../controls.mjs';
import { mini } from '../../mini/mini.mjs';
import { describe, it, expect } from 'vitest';
describe('controls', () => {
it('should support controls', () => {
expect(controls.s('bd').firstCycleValues).toEqual([{ s: 'bd' }]);
});
it('should support compound controls', () => {
expect(controls.s(mini('bd:3')).firstCycleValues).toEqual([{ s: 'bd', n: 3 }]);
expect(controls.s(mini('bd:3 sd:4:1.4')).firstCycleValues).toEqual([
{ s: 'bd', n: 3 },
{ s: 'sd', n: 4, gain: 1.4 },
]);
});
it('should support ignore extra elements in compound controls', () => {
expect(controls.s(mini('bd:3:0.4 sd:4:0.5:3:17')).firstCycleValues).toEqual([
{ s: 'bd', n: 3, gain: 0.4 },
{ s: 'sd', n: 4, gain: 0.5 },
]);
});
});

Some files were not shown because too many files have changed in this diff Show More