mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-23 21:47:16 -04:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d6114beb6 | |||
| 82380a0f4e | |||
| c030f456c3 | |||
| 694e44dc8a | |||
| 351da196b9 | |||
| e27ee03517 | |||
| 19db419c53 | |||
| ff60a89668 | |||
| 7cc17fefcb | |||
| 76ee9485f4 | |||
| 1d65f09f3c | |||
| cc97f6395d | |||
| 33eef619e4 | |||
| e9e3b97621 | |||
| 1f765e54ca | |||
| 2b81cc5d0c | |||
| ca054dbd74 | |||
| a8433f61a4 | |||
| 5850106eef | |||
| 95a9ab6182 | |||
| ba19c4ddce | |||
| b49b1c98ae | |||
| 7c00aa1a36 | |||
| 07d6bb3c44 | |||
| 3f2e2554be | |||
| 9ebb7edde9 | |||
| a89091cbc7 | |||
| 0d1a99deb2 | |||
| b71f8a33d7 |
@@ -21,4 +21,3 @@ vite.config.js
|
|||||||
/src-tauri/target/**/*
|
/src-tauri/target/**/*
|
||||||
reverbGen.mjs
|
reverbGen.mjs
|
||||||
hydra.mjs
|
hydra.mjs
|
||||||
jsdoc-synonyms.js
|
|
||||||
@@ -21,13 +21,13 @@ jobs:
|
|||||||
name: github-pages
|
name: github-pages
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
version: 8.11.0
|
version: 7
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 18
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [20]
|
node-version: [18]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
version: 8.11.0
|
version: 7
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|||||||
-83
@@ -44,86 +44,3 @@ Dirt-Samples
|
|||||||
tidal-drum-machines
|
tidal-drum-machines
|
||||||
webaudiofontdata
|
webaudiofontdata
|
||||||
src-tauri/target
|
src-tauri/target
|
||||||
|
|
||||||
# BEGIN JetBrains -> END JetBrains
|
|
||||||
|
|
||||||
# for JetBrains IDE users, e.g. WebStorm. Source: https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# AWS User-specific
|
|
||||||
.idea/**/aws.xml
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# SonarLint plugin
|
|
||||||
.idea/sonarlint/
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
# END JetBrains -> BEGIN JetBrains
|
|
||||||
|
|||||||
@@ -10,4 +10,3 @@ paper
|
|||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
pnpm-workspace.yaml
|
pnpm-workspace.yaml
|
||||||
**/dev-dist
|
**/dev-dist
|
||||||
website/.astro
|
|
||||||
|
|||||||
+1
-1
@@ -66,7 +66,7 @@ To get the project up and running for development, make sure you have installed:
|
|||||||
|
|
||||||
- [git](https://git-scm.com/)
|
- [git](https://git-scm.com/)
|
||||||
- [node](https://nodejs.org/en/) >= 18
|
- [node](https://nodejs.org/en/) >= 18
|
||||||
- [pnpm](https://pnpm.io/) (`curl -fsSL https://get.pnpm.io/install.sh | env PNPM_VERSION=8.11.0 sh -`)
|
- [pnpm](https://pnpm.io/) (`npm i pnpm -g`)
|
||||||
|
|
||||||
then, do the following:
|
then, do the following:
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ There are multiple npm packages you can use to use strudel, or only parts of it,
|
|||||||
- [`midi`](./packages/midi): webmidi bindings
|
- [`midi`](./packages/midi): webmidi bindings
|
||||||
- [`serial`](./packages/serial): webserial bindings
|
- [`serial`](./packages/serial): webserial bindings
|
||||||
- [`tonal`](./packages/tonal): tonal functions
|
- [`tonal`](./packages/tonal): tonal functions
|
||||||
|
- [`xen`](./packages/xen): microtonal / xenharmonic functions
|
||||||
- ... [and there are more](./packages/)
|
- ... [and there are more](./packages/)
|
||||||
|
|
||||||
Click on the package names to find out more about each one.
|
Click on the package names to find out more about each one.
|
||||||
|
|||||||
+1806
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 150 KiB |
@@ -1,3 +0,0 @@
|
|||||||
# examples
|
|
||||||
|
|
||||||
This folder contains usage examples for different scenarios.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# buildless examples
|
|
||||||
|
|
||||||
These examples show you how strudel can be used in a regular html file, without the need for a build tool.
|
|
||||||
|
|
||||||
Most examples are using [skypack](https://www.skypack.dev/)
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<script src="https://unpkg.com/@strudel/repl@0.9.4"></script>
|
|
||||||
<strudel-editor>
|
|
||||||
<!--
|
|
||||||
// @date 23-08-15
|
|
||||||
// "golf rolf" @by froos @license CC BY-NC-SA 4.0
|
|
||||||
|
|
||||||
setcps(1)
|
|
||||||
stack(
|
|
||||||
s("bd*2, ~ rim*<1!3 2>, hh*4").bank('RolandTR909')
|
|
||||||
.off(-1/8, set(speed("1.5").gain(.25)))
|
|
||||||
.mask("<0!16 1!64>")
|
|
||||||
,
|
|
||||||
note("g1(3,8)")
|
|
||||||
.s("gm_synth_bass_2:<0 2>")
|
|
||||||
.delay(".8:.25:.25")
|
|
||||||
.clip("<.5!16 2!32>")
|
|
||||||
.off(1/8, add(note("12?0.7")))
|
|
||||||
.lpf(sine.range(500,2000).slow(32)).lpq(8)
|
|
||||||
.add(note("0,.05"))
|
|
||||||
.mask("<0!8 1!32>")
|
|
||||||
,
|
|
||||||
n("<0 1 2 3 4>*8").scale('G4 minor')
|
|
||||||
.s("gm_lead_6_voice")
|
|
||||||
.clip(sine.range(.2,.8).slow(8))
|
|
||||||
.jux(rev)
|
|
||||||
.room(2)
|
|
||||||
.sometimes(add(note("12")))
|
|
||||||
.lpf(perlin.range(200,20000).slow(4))
|
|
||||||
).reset("<x@15 x(5,8)>")
|
|
||||||
-->
|
|
||||||
</strudel-editor>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
# codemirror-repl example
|
|
||||||
|
|
||||||
This folder demonstrates how to set up a full strudel repl with the `@strudel/codemirror` package. Run it using:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pnpm i
|
|
||||||
pnpm dev
|
|
||||||
```
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# headless-repl demo
|
|
||||||
|
|
||||||
This demo shows how to use strudel in "headless mode".
|
|
||||||
Buttons A / B / C will switch between different patterns.
|
|
||||||
It showcases the usage of the `@strudel/web` package, using [vite](https://vitejs.dev/) as the dev server.
|
|
||||||
|
|
||||||
## Running
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pnpm i && cd examples/headless-repl
|
|
||||||
pnpm dev
|
|
||||||
# open http://localhost:5173/
|
|
||||||
```
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
dist
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# minimal repl
|
|
||||||
|
|
||||||
This folder demonstrates how to set up a minimal strudel repl using vite and vanilla JS. Run it using:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm i
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're looking for a more feature rich alternative, have a look at the [../codemirror-repl](codemirror-repl example)
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# superdough demo
|
|
||||||
|
|
||||||
This demo shows how to use [superdough](https://www.npmjs.com/package/superdough) with [vite](https://vitejs.dev/).
|
|
||||||
|
|
||||||
## Running
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pnpm i && cd examples/headless-repl
|
|
||||||
pnpm dev
|
|
||||||
# open http://localhost:5173/
|
|
||||||
```
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Superdough Example</title>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<button id="play">PLAAAAAAAY</button>
|
|
||||||
<script type="module">
|
|
||||||
import { superdough, samples, initAudioOnFirstClick, registerSynthSounds } from 'superdough';
|
|
||||||
|
|
||||||
const init = Promise.all([
|
|
||||||
initAudioOnFirstClick(),
|
|
||||||
samples('github:tidalcycles/Dirt-Samples/master'),
|
|
||||||
registerSynthSounds(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const loop = (t = 0) => {
|
|
||||||
// superdough(value, time, duration)
|
|
||||||
superdough({ s: 'bd', delay: 0.5 }, t);
|
|
||||||
superdough({ note: 'g1', s: 'sawtooth', cutoff: 600, resonance: 8 }, t, 0.125);
|
|
||||||
superdough({ note: 'g2', s: 'sawtooth', cutoff: 600, resonance: 8 }, t + 0.25, 0.125);
|
|
||||||
superdough({ s: 'hh' }, t + 0.25);
|
|
||||||
superdough({ s: 'sd', room: 0.5 }, t + 0.5);
|
|
||||||
superdough({ s: 'hh' }, t + 0.75);
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById('play').addEventListener('click', async () => {
|
|
||||||
await init;
|
|
||||||
let t = 0.1;
|
|
||||||
while (t < 16) {
|
|
||||||
loop(t++);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"includePattern": ".+\\.(js(doc|x)?|mjs)$",
|
"includePattern": ".+\\.(js(doc|x)?|mjs)$",
|
||||||
"excludePattern": "node_modules|shift-parser|shift-reducer|shift-traverser|dist"
|
"excludePattern": "node_modules|shift-parser|shift-reducer|shift-traverser|dist"
|
||||||
},
|
},
|
||||||
"plugins": ["plugins/markdown", "jsdoc/jsdoc-synonyms"],
|
"plugins": ["plugins/markdown"],
|
||||||
"opts": {
|
"opts": {
|
||||||
"destination": "./out/",
|
"destination": "./out/",
|
||||||
"recurse": true
|
"recurse": true
|
||||||
+4
-2
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"packages": ["packages/*"],
|
"packages": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
"version": "independent",
|
"version": "independent",
|
||||||
"npmClient": "pnpm",
|
"npmClient": "pnpm",
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
"useWorkspaces": true
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-13
@@ -18,12 +18,12 @@
|
|||||||
"build": "npm run prebuild && cd website && npm run build",
|
"build": "npm run prebuild && cd website && npm run build",
|
||||||
"preview": "cd website && npm run preview",
|
"preview": "cd website && npm run preview",
|
||||||
"osc": "cd packages/osc && npm run server",
|
"osc": "cd packages/osc && npm run server",
|
||||||
"jsdoc": "jsdoc packages/ -c jsdoc/jsdoc.config.json",
|
"jsdoc": "jsdoc packages/ -c jsdoc.config.json",
|
||||||
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc/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",
|
"lint": "eslint . --ext mjs,js --quiet",
|
||||||
"codeformat": "prettier --write .",
|
"codeformat": "prettier --write .",
|
||||||
"format-check": "prettier --check .",
|
"format-check": "prettier --check .",
|
||||||
"report-undocumented": "npm run jsdoc-json && node jsdoc/undocumented.mjs > undocumented.json",
|
"report-undocumented": "npm run jsdoc-json && node undocumented.mjs > undocumented.json",
|
||||||
"check": "npm run format-check && npm run lint && npm run test",
|
"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"
|
"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"
|
||||||
},
|
},
|
||||||
@@ -50,22 +50,23 @@
|
|||||||
"@strudel.cycles/tonal": "workspace:*",
|
"@strudel.cycles/tonal": "workspace:*",
|
||||||
"@strudel.cycles/transpiler": "workspace:*",
|
"@strudel.cycles/transpiler": "workspace:*",
|
||||||
"@strudel.cycles/webaudio": "workspace:*",
|
"@strudel.cycles/webaudio": "workspace:*",
|
||||||
"@strudel.cycles/xen": "workspace:*"
|
"@strudel.cycles/xen": "workspace:*",
|
||||||
|
"acorn": "^8.8.1",
|
||||||
|
"dependency-tree": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dependency-tree": "^10.0.9",
|
"@tauri-apps/cli": "^1.4.0",
|
||||||
"@tauri-apps/cli": "^1.5.9",
|
"@vitest/ui": "^0.28.0",
|
||||||
"@vitest/ui": "^1.1.0",
|
|
||||||
"canvas": "^2.11.2",
|
"canvas": "^2.11.2",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.39.0",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.27.5",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"jsdoc": "^4.0.2",
|
"jsdoc": "^4.0.2",
|
||||||
"jsdoc-json": "^2.0.2",
|
"jsdoc-json": "^2.0.2",
|
||||||
"jsdoc-to-markdown": "^8.0.0",
|
"jsdoc-to-markdown": "^8.0.0",
|
||||||
"lerna": "^8.0.1",
|
"lerna": "^6.6.1",
|
||||||
"prettier": "^3.1.1",
|
"prettier": "^2.8.8",
|
||||||
"rollup-plugin-visualizer": "^5.12.0",
|
"rollup-plugin-visualizer": "^5.8.1",
|
||||||
"vitest": "^1.1.0"
|
"vitest": "^0.33.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,3 +3,13 @@
|
|||||||
Each folder represents one of the @strudel.cycles/* packages [published to npm](https://www.npmjs.com/org/strudel.cycles).
|
Each folder represents one of the @strudel.cycles/* packages [published to npm](https://www.npmjs.com/org/strudel.cycles).
|
||||||
|
|
||||||
To understand how those pieces connect, refer to the [Technical Manual](https://github.com/tidalcycles/strudel/wiki/Technical-Manual) or the individual READMEs.
|
To understand how those pieces connect, refer to the [Technical Manual](https://github.com/tidalcycles/strudel/wiki/Technical-Manual) or the individual READMEs.
|
||||||
|
|
||||||
|
This is a graphical view of all the packages: [full screen](https://raw.githubusercontent.com/tidalcycles/strudel/main/dependencies.svg)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Generated with
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx depcruise --include-only "^packages" -X "node_modules" --output-type dot packages | dot -T svg > dependencygraph.svg
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
import jsdoc from '../../doc.json';
|
import jsdoc from '../../doc.json';
|
||||||
// import { javascriptLanguage } from '@codemirror/lang-javascript';
|
// import { javascriptLanguage } from '@codemirror/lang-javascript';
|
||||||
import { autocompletion } from '@codemirror/autocomplete';
|
import { autocompletion } from '@codemirror/autocomplete';
|
||||||
import { h } from './html';
|
|
||||||
|
|
||||||
function plaintext(str) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.innerText = str;
|
|
||||||
return div.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDocLabel = (doc) => doc.name || doc.longname;
|
const getDocLabel = (doc) => doc.name || doc.longname;
|
||||||
const getInnerText = (html) => {
|
const getInnerText = (html) => {
|
||||||
@@ -16,32 +10,36 @@ const getInnerText = (html) => {
|
|||||||
return div.textContent || div.innerText || '';
|
return div.textContent || div.innerText || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Autocomplete({ doc, label }) {
|
export function Autocomplete({ doc }) {
|
||||||
return h`<div class="prose dark:prose-invert max-h-[400px] overflow-auto p-2">
|
return (
|
||||||
<h1 class="pt-0 mt-0">${label || getDocLabel(doc)}</h1>
|
<div className="prose dark:prose-invert max-h-[400px] overflow-auto">
|
||||||
${doc.description}
|
<h3 className="pt-0 mt-0">{getDocLabel(doc)}</h3>
|
||||||
<ul>
|
<div dangerouslySetInnerHTML={{ __html: doc.description }} />
|
||||||
${doc.params?.map(
|
<ul>
|
||||||
({ name, type, description }) =>
|
{doc.params?.map(({ name, type, description }, i) => (
|
||||||
`<li>${name} : ${type.names?.join(' | ')} ${description ? ` - ${getInnerText(description)}` : ''}</li>`,
|
<li key={i}>
|
||||||
)}
|
{name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)}</> : ''}
|
||||||
</ul>
|
</li>
|
||||||
<div>
|
))}
|
||||||
${doc.examples?.map((example) => `<div><pre>${plaintext(example)}</pre></div>`)}
|
</ul>
|
||||||
</div>
|
<div>
|
||||||
</div>`[0];
|
{doc.examples?.map((example, i) => (
|
||||||
/*
|
<div key={i}>
|
||||||
<pre
|
<pre
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
console.log('ola!');
|
console.log('ola!');
|
||||||
navigator.clipboard.writeText(example);
|
navigator.clipboard.writeText(example);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{example}
|
{example}
|
||||||
</pre>
|
</pre>
|
||||||
*/
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsdocCompletions = jsdoc.docs
|
const jsdocCompletions = jsdoc.docs
|
||||||
@@ -56,7 +54,13 @@ const jsdocCompletions = jsdoc.docs
|
|||||||
.map((doc) /*: Completion */ => ({
|
.map((doc) /*: Completion */ => ({
|
||||||
label: getDocLabel(doc),
|
label: getDocLabel(doc),
|
||||||
// detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
|
// detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
|
||||||
info: () => Autocomplete({ doc }),
|
info: () => {
|
||||||
|
const node = document.createElement('div');
|
||||||
|
// if Autocomplete is non-interactive, it could also be rendered at build time..
|
||||||
|
// .. using renderToStaticMarkup
|
||||||
|
createRoot(node).render(<Autocomplete doc={doc} />);
|
||||||
|
return node;
|
||||||
|
},
|
||||||
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
|
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -3,64 +3,32 @@ import { closeBrackets } from '@codemirror/autocomplete';
|
|||||||
import { history } from '@codemirror/commands';
|
import { history } from '@codemirror/commands';
|
||||||
import { javascript } from '@codemirror/lang-javascript';
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||||
import { Compartment, EditorState, Prec } from '@codemirror/state';
|
import { Compartment, EditorState } from '@codemirror/state';
|
||||||
import {
|
import { EditorView, highlightActiveLineGutter, highlightActiveLine, keymap, lineNumbers } from '@codemirror/view';
|
||||||
EditorView,
|
|
||||||
highlightActiveLineGutter,
|
|
||||||
highlightActiveLine,
|
|
||||||
keymap,
|
|
||||||
lineNumbers,
|
|
||||||
drawSelection,
|
|
||||||
} from '@codemirror/view';
|
|
||||||
import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core';
|
import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core';
|
||||||
import { isAutoCompletionEnabled } from './autocomplete.mjs';
|
import { isAutoCompletionEnabled } from './Autocomplete';
|
||||||
import { isTooltipEnabled } from './tooltip.mjs';
|
|
||||||
import { flash, isFlashEnabled } from './flash.mjs';
|
import { flash, isFlashEnabled } from './flash.mjs';
|
||||||
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
|
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
|
||||||
import { keybindings } from './keybindings.mjs';
|
import { keybindings } from './keybindings.mjs';
|
||||||
import { initTheme, activateTheme, theme } from './themes.mjs';
|
import { theme } from './themes.mjs';
|
||||||
import { updateWidgets, sliderPlugin } from './slider.mjs';
|
import { updateWidgets, sliderPlugin } from './slider.mjs';
|
||||||
import { persistentAtom } from '@nanostores/persistent';
|
|
||||||
|
|
||||||
const extensions = {
|
const extensions = {
|
||||||
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
|
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
|
||||||
isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []),
|
isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []),
|
||||||
theme,
|
theme,
|
||||||
isAutoCompletionEnabled,
|
isAutoCompletionEnabled,
|
||||||
isTooltipEnabled,
|
|
||||||
isPatternHighlightingEnabled,
|
isPatternHighlightingEnabled,
|
||||||
isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []),
|
|
||||||
isFlashEnabled,
|
isFlashEnabled,
|
||||||
keybindings,
|
keybindings,
|
||||||
};
|
};
|
||||||
const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
|
const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
|
||||||
|
|
||||||
export const defaultSettings = {
|
|
||||||
keybindings: 'codemirror',
|
|
||||||
isLineNumbersDisplayed: true,
|
|
||||||
isActiveLineHighlighted: false,
|
|
||||||
isAutoCompletionEnabled: false,
|
|
||||||
isPatternHighlightingEnabled: true,
|
|
||||||
isFlashEnabled: true,
|
|
||||||
isTooltipEnabled: false,
|
|
||||||
isLineWrappingEnabled: false,
|
|
||||||
theme: 'strudelTheme',
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
fontSize: 18,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const codemirrorSettings = persistentAtom('codemirror-settings', defaultSettings, {
|
|
||||||
encode: JSON.stringify,
|
|
||||||
decode: JSON.parse,
|
|
||||||
});
|
|
||||||
|
|
||||||
// https://codemirror.net/docs/guide/
|
// https://codemirror.net/docs/guide/
|
||||||
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, root }) {
|
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, settings, root }) {
|
||||||
const settings = codemirrorSettings.get();
|
|
||||||
const initialSettings = Object.keys(compartments).map((key) =>
|
const initialSettings = Object.keys(compartments).map((key) =>
|
||||||
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
|
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
|
||||||
);
|
);
|
||||||
initTheme(settings.theme);
|
|
||||||
let state = EditorState.create({
|
let state = EditorState.create({
|
||||||
doc: initialCode,
|
doc: initialCode,
|
||||||
extensions: [
|
extensions: [
|
||||||
@@ -72,11 +40,11 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
|||||||
// indentOnInput(), // works without. already brought with javascript extension?
|
// indentOnInput(), // works without. already brought with javascript extension?
|
||||||
// bracketMatching(), // does not do anything
|
// bracketMatching(), // does not do anything
|
||||||
closeBrackets(),
|
closeBrackets(),
|
||||||
|
highlightActiveLineGutter(),
|
||||||
|
highlightActiveLine(),
|
||||||
syntaxHighlighting(defaultHighlightStyle),
|
syntaxHighlighting(defaultHighlightStyle),
|
||||||
history(),
|
history(),
|
||||||
EditorView.updateListener.of((v) => onChange(v)),
|
EditorView.updateListener.of((v) => onChange(v)),
|
||||||
drawSelection({ cursorBlinkRate: 0 }),
|
|
||||||
Prec.highest(
|
|
||||||
keymap.of([
|
keymap.of([
|
||||||
{
|
{
|
||||||
key: 'Ctrl-Enter',
|
key: 'Ctrl-Enter',
|
||||||
@@ -106,7 +74,6 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
|||||||
run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()),
|
run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()),
|
||||||
}, */
|
}, */
|
||||||
]),
|
]),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,26 +85,14 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
|||||||
|
|
||||||
export class StrudelMirror {
|
export class StrudelMirror {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
const {
|
const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, settings, ...replOptions } = options;
|
||||||
root,
|
|
||||||
id,
|
|
||||||
initialCode = '',
|
|
||||||
onDraw,
|
|
||||||
drawTime = [0, 0],
|
|
||||||
autodraw,
|
|
||||||
prebake,
|
|
||||||
bgFill = true,
|
|
||||||
...replOptions
|
|
||||||
} = options;
|
|
||||||
this.code = initialCode;
|
this.code = initialCode;
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.miniLocations = [];
|
this.miniLocations = [];
|
||||||
this.widgets = [];
|
this.widgets = [];
|
||||||
this.painters = [];
|
this.painters = [];
|
||||||
this.drawTime = drawTime;
|
|
||||||
this.onDraw = onDraw;
|
this.onDraw = onDraw;
|
||||||
const self = this;
|
const self = this;
|
||||||
this.id = id || s4();
|
|
||||||
|
|
||||||
this.drawer = new Drawer((haps, time) => {
|
this.drawer = new Drawer((haps, time) => {
|
||||||
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
|
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
|
||||||
@@ -145,29 +100,21 @@ export class StrudelMirror {
|
|||||||
this.onDraw?.(haps, time, currentFrame, this.painters);
|
this.onDraw?.(haps, time, currentFrame, this.painters);
|
||||||
}, drawTime);
|
}, drawTime);
|
||||||
|
|
||||||
// this approach does not work with multiple repls on screen
|
// this approach might not work with multiple repls on screen..
|
||||||
// TODO: refactor onPaint usages + find fix, maybe remove painters here?
|
|
||||||
Pattern.prototype.onPaint = function (onPaint) {
|
Pattern.prototype.onPaint = function (onPaint) {
|
||||||
self.painters.push(onPaint);
|
self.painters.push(onPaint);
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.prebaked = prebake();
|
this.prebaked = prebake();
|
||||||
autodraw && this.drawFirstFrame();
|
// this.drawFirstFrame();
|
||||||
|
|
||||||
this.repl = repl({
|
this.repl = repl({
|
||||||
...replOptions,
|
...replOptions,
|
||||||
onToggle: (started) => {
|
onToggle: (started) => {
|
||||||
replOptions?.onToggle?.(started);
|
replOptions?.onToggle?.(started);
|
||||||
if (started) {
|
if (started) {
|
||||||
this.adjustDrawTime();
|
|
||||||
this.drawer.start(this.repl.scheduler);
|
this.drawer.start(this.repl.scheduler);
|
||||||
// stop other repls when this one is started
|
|
||||||
document.dispatchEvent(
|
|
||||||
new CustomEvent('start-repl', {
|
|
||||||
detail: this.id,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this.drawer.stop();
|
this.drawer.stop();
|
||||||
updateMiniLocations(this.editor, []);
|
updateMiniLocations(this.editor, []);
|
||||||
@@ -187,46 +134,22 @@ export class StrudelMirror {
|
|||||||
updateWidgets(this.editor, this.widgets);
|
updateWidgets(this.editor, this.widgets);
|
||||||
updateMiniLocations(this.editor, this.miniLocations);
|
updateMiniLocations(this.editor, this.miniLocations);
|
||||||
replOptions?.afterEval?.(options);
|
replOptions?.afterEval?.(options);
|
||||||
this.adjustDrawTime();
|
|
||||||
this.drawer.invalidate();
|
this.drawer.invalidate();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.editor = initEditor({
|
this.editor = initEditor({
|
||||||
root,
|
root,
|
||||||
|
settings,
|
||||||
initialCode,
|
initialCode,
|
||||||
onChange: (v) => {
|
onChange: (v) => {
|
||||||
if (v.docChanged) {
|
if (v.docChanged) {
|
||||||
this.code = v.state.doc.toString();
|
this.code = v.state.doc.toString();
|
||||||
this.repl.setCode?.(this.code);
|
this.repl.setCode(this.code);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onEvaluate: () => this.evaluate(),
|
onEvaluate: () => this.evaluate(),
|
||||||
onStop: () => this.stop(),
|
onStop: () => this.stop(),
|
||||||
});
|
});
|
||||||
const cmEditor = this.root.querySelector('.cm-editor');
|
|
||||||
if (cmEditor) {
|
|
||||||
this.root.style.display = 'block';
|
|
||||||
if (bgFill) {
|
|
||||||
this.root.style.backgroundColor = 'var(--background)';
|
|
||||||
}
|
|
||||||
cmEditor.style.backgroundColor = 'transparent';
|
|
||||||
}
|
|
||||||
const settings = codemirrorSettings.get();
|
|
||||||
this.setFontSize(settings.fontSize);
|
|
||||||
this.setFontFamily(settings.fontFamily);
|
|
||||||
|
|
||||||
// stop this repl when another repl is started
|
|
||||||
this.onStartRepl = (e) => {
|
|
||||||
if (e.detail !== this.id) {
|
|
||||||
this.stop();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener('start-repl', this.onStartRepl);
|
|
||||||
}
|
|
||||||
// adjusts draw time depending on if there are painters
|
|
||||||
adjustDrawTime() {
|
|
||||||
// when no painters are set, [0,0] is enough (just highlighting)
|
|
||||||
this.drawer.setDrawTime(this.painters.length ? this.drawTime : [0, 0]);
|
|
||||||
}
|
}
|
||||||
async drawFirstFrame() {
|
async drawFirstFrame() {
|
||||||
if (!this.onDraw) {
|
if (!this.onDraw) {
|
||||||
@@ -236,9 +159,8 @@ export class StrudelMirror {
|
|||||||
await this.prebaked;
|
await this.prebaked;
|
||||||
try {
|
try {
|
||||||
await this.repl.evaluate(this.code, false);
|
await this.repl.evaluate(this.code, false);
|
||||||
this.drawer.invalidate(this.repl.scheduler, -0.001);
|
this.drawer.invalidate(this.repl.scheduler);
|
||||||
// draw at -0.001 to avoid haps at 0 to be visualized as active
|
this.onDraw?.(this.drawer.visibleHaps, 0, []);
|
||||||
this.onDraw?.(this.drawer.visibleHaps, -0.001, [], this.painters);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('first frame could not be painted');
|
console.warn('first frame could not be painted');
|
||||||
}
|
}
|
||||||
@@ -252,7 +174,7 @@ export class StrudelMirror {
|
|||||||
}
|
}
|
||||||
async toggle() {
|
async toggle() {
|
||||||
if (this.repl.scheduler.started) {
|
if (this.repl.scheduler.started) {
|
||||||
this.repl.stop();
|
this.repl.scheduler.stop();
|
||||||
} else {
|
} else {
|
||||||
this.evaluate();
|
this.evaluate();
|
||||||
}
|
}
|
||||||
@@ -268,10 +190,6 @@ export class StrudelMirror {
|
|||||||
}
|
}
|
||||||
setFontFamily(family) {
|
setFontFamily(family) {
|
||||||
this.root.style.fontFamily = family;
|
this.root.style.fontFamily = family;
|
||||||
const scroller = this.root.querySelector('.cm-scroller');
|
|
||||||
if (scroller) {
|
|
||||||
scroller.style.fontFamily = family;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
reconfigureExtension(key, value) {
|
reconfigureExtension(key, value) {
|
||||||
if (!extensions[key]) {
|
if (!extensions[key]) {
|
||||||
@@ -283,9 +201,6 @@ export class StrudelMirror {
|
|||||||
this.editor.dispatch({
|
this.editor.dispatch({
|
||||||
effects: compartments[key].reconfigure(newValue),
|
effects: compartments[key].reconfigure(newValue),
|
||||||
});
|
});
|
||||||
if (key === 'theme') {
|
|
||||||
activateTheme(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setLineWrappingEnabled(enabled) {
|
setLineWrappingEnabled(enabled) {
|
||||||
this.reconfigureExtension('isLineWrappingEnabled', enabled);
|
this.reconfigureExtension('isLineWrappingEnabled', enabled);
|
||||||
@@ -305,8 +220,6 @@ export class StrudelMirror {
|
|||||||
for (let key in extensions) {
|
for (let key in extensions) {
|
||||||
this.reconfigureExtension(key, settings[key]);
|
this.reconfigureExtension(key, settings[key]);
|
||||||
}
|
}
|
||||||
const updated = { ...codemirrorSettings.get(), ...settings };
|
|
||||||
codemirrorSettings.set(updated);
|
|
||||||
}
|
}
|
||||||
changeSetting(key, value) {
|
changeSetting(key, value) {
|
||||||
if (extensions[key]) {
|
if (extensions[key]) {
|
||||||
@@ -322,18 +235,8 @@ export class StrudelMirror {
|
|||||||
const changes = { from: 0, to: this.editor.state.doc.length, insert: code };
|
const changes = { from: 0, to: this.editor.state.doc.length, insert: code };
|
||||||
this.editor.dispatch({ changes });
|
this.editor.dispatch({ changes });
|
||||||
}
|
}
|
||||||
clear() {
|
|
||||||
this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseBooleans(value) {
|
function parseBooleans(value) {
|
||||||
return { true: true, false: false }[value] ?? value;
|
return { true: true, false: false }[value] ?? value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function to generate repl ids
|
|
||||||
function s4() {
|
|
||||||
return Math.floor((1 + Math.random()) * 0x10000)
|
|
||||||
.toString(16)
|
|
||||||
.substring(1);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
const parser = typeof DOMParser !== 'undefined' ? new DOMParser() : null;
|
|
||||||
export let html = (string) => {
|
|
||||||
return parser?.parseFromString(string, 'text/html').querySelectorAll('*');
|
|
||||||
};
|
|
||||||
let parseChunk = (chunk) => {
|
|
||||||
if (Array.isArray(chunk)) return chunk.flat().join('');
|
|
||||||
if (chunk === undefined) return '';
|
|
||||||
return chunk;
|
|
||||||
};
|
|
||||||
export let h = (strings, ...vars) => {
|
|
||||||
let string = '';
|
|
||||||
for (let i in strings) {
|
|
||||||
string += parseChunk(strings[i]);
|
|
||||||
string += parseChunk(vars[i]);
|
|
||||||
}
|
|
||||||
return html(string);
|
|
||||||
};
|
|
||||||
@@ -2,4 +2,3 @@ export * from './codemirror.mjs';
|
|||||||
export * from './highlight.mjs';
|
export * from './highlight.mjs';
|
||||||
export * from './flash.mjs';
|
export * from './flash.mjs';
|
||||||
export * from './slider.mjs';
|
export * from './slider.mjs';
|
||||||
export * from './themes.mjs';
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/codemirror",
|
"name": "@strudel/codemirror",
|
||||||
"version": "0.10.0",
|
"version": "0.9.0",
|
||||||
"description": "Codemirror Extensions for Strudel",
|
"description": "Codemirror Extensions for Strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
@@ -33,24 +33,23 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.11.1",
|
"@codemirror/autocomplete": "^6.6.0",
|
||||||
"@codemirror/commands": "^6.3.3",
|
"@codemirror/commands": "^6.2.4",
|
||||||
"@codemirror/lang-javascript": "^6.2.1",
|
"@codemirror/lang-javascript": "^6.1.7",
|
||||||
"@codemirror/language": "^6.10.0",
|
"@codemirror/language": "^6.6.0",
|
||||||
"@codemirror/search": "^6.5.5",
|
"@codemirror/search": "^6.0.0",
|
||||||
"@codemirror/state": "^6.4.0",
|
"@codemirror/state": "^6.2.0",
|
||||||
"@codemirror/view": "^6.23.0",
|
"@codemirror/view": "^6.10.0",
|
||||||
"@lezer/highlight": "^1.2.0",
|
"@lezer/highlight": "^1.1.4",
|
||||||
"@nanostores/persistent": "^0.9.1",
|
|
||||||
"@replit/codemirror-emacs": "^6.0.1",
|
"@replit/codemirror-emacs": "^6.0.1",
|
||||||
"@replit/codemirror-vim": "^6.1.0",
|
"@replit/codemirror-vim": "^6.0.14",
|
||||||
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
||||||
"@strudel.cycles/core": "workspace:*",
|
"@strudel.cycles/core": "workspace:*",
|
||||||
"@uiw/codemirror-themes": "^4.21.21",
|
"@uiw/codemirror-themes": "^4.19.16",
|
||||||
"@uiw/codemirror-themes-all": "^4.21.21",
|
"@uiw/codemirror-themes-all": "^4.19.16",
|
||||||
"nanostores": "^0.9.5"
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^5.0.10"
|
"vite": "^4.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,15 +111,6 @@ export const sliderPlugin = ViewPlugin.fromClass(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a slider widget to allow the user manipulate a value
|
|
||||||
*
|
|
||||||
* @name slider
|
|
||||||
* @param {number} value Initial value
|
|
||||||
* @param {number} min Minimum value - optional, defaults to 0
|
|
||||||
* @param {number} max Maximum value - optional, defaults to 1
|
|
||||||
* @param {number} step Step size - optional
|
|
||||||
*/
|
|
||||||
export let slider = (value) => {
|
export let slider = (value) => {
|
||||||
console.warn('slider will only work when the transpiler is used... passing value as is');
|
console.warn('slider will only work when the transpiler is used... passing value as is');
|
||||||
return pure(value);
|
return pure(value);
|
||||||
|
|||||||
Vendored
+1
-44
@@ -473,9 +473,6 @@ function stringifySafe(json) {
|
|||||||
return JSON.stringify(json, getCircularReplacer());
|
return JSON.stringify(json, getCircularReplacer());
|
||||||
}
|
}
|
||||||
|
|
||||||
export const theme = (theme) => themes[theme] || themes.strudelTheme;
|
|
||||||
|
|
||||||
// css style injection helpers
|
|
||||||
export function injectStyle(rule) {
|
export function injectStyle(rule) {
|
||||||
const newStyle = document.createElement('style');
|
const newStyle = document.createElement('style');
|
||||||
document.head.appendChild(newStyle);
|
document.head.appendChild(newStyle);
|
||||||
@@ -484,44 +481,4 @@ export function injectStyle(rule) {
|
|||||||
return () => styleSheet.deleteRule(ruleIndex);
|
return () => styleSheet.deleteRule(ruleIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentTheme,
|
export const theme = (theme) => themes[theme] || themes.strudelTheme;
|
||||||
resetThemeStyle,
|
|
||||||
themeStyle,
|
|
||||||
styleID = 'strudel-theme-vars';
|
|
||||||
export function initTheme(theme) {
|
|
||||||
if (!document.getElementById(styleID)) {
|
|
||||||
themeStyle = document.createElement('style');
|
|
||||||
themeStyle.id = styleID;
|
|
||||||
document.head.append(themeStyle);
|
|
||||||
}
|
|
||||||
activateTheme(theme);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function activateTheme(name) {
|
|
||||||
if (currentTheme === name) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentTheme = name;
|
|
||||||
if (!settings[name]) {
|
|
||||||
console.warn('theme', name, 'has no settings.. defaulting to strudelTheme settings');
|
|
||||||
}
|
|
||||||
const themeSettings = settings[name] || settings.strudelTheme;
|
|
||||||
// set css variables
|
|
||||||
themeStyle.innerHTML = `:root {
|
|
||||||
${Object.entries(themeSettings)
|
|
||||||
// important to override fallback
|
|
||||||
.map(([key, value]) => `--${key}: ${value} !important;`)
|
|
||||||
.join('\n')}
|
|
||||||
}`;
|
|
||||||
// tailwind dark mode
|
|
||||||
if (themeSettings.light) {
|
|
||||||
document.documentElement.classList.remove('dark');
|
|
||||||
} else {
|
|
||||||
document.documentElement.classList.add('dark');
|
|
||||||
}
|
|
||||||
resetThemeStyle?.();
|
|
||||||
resetThemeStyle = undefined;
|
|
||||||
if (themeSettings.customStyle) {
|
|
||||||
resetThemeStyle = injectStyle(themeSettings.customStyle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default defineConfig({
|
|||||||
lib: {
|
lib: {
|
||||||
entry: resolve(__dirname, 'index.mjs'),
|
entry: resolve(__dirname, 'index.mjs'),
|
||||||
formats: ['es', 'cjs'],
|
formats: ['es', 'cjs'],
|
||||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: [...Object.keys(dependencies)],
|
external: [...Object.keys(dependencies)],
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
# @strudel.cycles/core
|
# @strudel.cycles/core
|
||||||
|
|
||||||
**DEPRECATION NOTE**: This package is old and won't get any updates! The newer version goes by the name of [@strudel/core](https://www.npmjs.com/package/@strudel/core).
|
|
||||||
|
|
||||||
This package contains the bare essence of strudel.
|
This package contains the bare essence of strudel.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|||||||
+8
-117
@@ -234,7 +234,7 @@ const generic_params = [
|
|||||||
* note("c3 e3").decay("<.1 .2 .3 .4>").sustain(0)
|
* note("c3 e3").decay("<.1 .2 .3 .4>").sustain(0)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
['decay', 'dec'],
|
['decay'],
|
||||||
/**
|
/**
|
||||||
* Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset.
|
* Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset.
|
||||||
*
|
*
|
||||||
@@ -270,7 +270,7 @@ const generic_params = [
|
|||||||
* s("bd sd,hh*3").bpf("<1000 2000 4000 8000>")
|
* s("bd sd,hh*3").bpf("<1000 2000 4000 8000>")
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
[['bandf', 'bandq', 'bpenv'], 'bpf', 'bp'],
|
[['bandf', 'bandq'], 'bpf', 'bp'],
|
||||||
// TODO: in tidal, it seems to be normalized
|
// TODO: in tidal, it seems to be normalized
|
||||||
/**
|
/**
|
||||||
* Sets the **b**and-**p**ass **q**-factor (resonance).
|
* Sets the **b**and-**p**ass **q**-factor (resonance).
|
||||||
@@ -381,19 +381,6 @@ const generic_params = [
|
|||||||
*/
|
*/
|
||||||
['coarse'],
|
['coarse'],
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows you to set the output channels on the interface
|
|
||||||
*
|
|
||||||
* @name channels
|
|
||||||
* @synonyms ch
|
|
||||||
*
|
|
||||||
* @param {number | Pattern} channels pattern the output channels
|
|
||||||
* @example
|
|
||||||
* note("e a d b g").channels("3:4")
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
['channels', 'ch'],
|
|
||||||
|
|
||||||
['phaserrate', 'phasr'], // superdirt only
|
['phaserrate', 'phasr'], // superdirt only
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -481,7 +468,7 @@ const generic_params = [
|
|||||||
* s("bd*8").lpf("1000:0 1000:10 1000:20 1000:30")
|
* s("bd*8").lpf("1000:0 1000:10 1000:20 1000:30")
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
[['cutoff', 'resonance', 'lpenv'], 'ctf', 'lpf', 'lp'],
|
[['cutoff', 'resonance'], 'ctf', 'lpf', 'lp'],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the lowpass filter envelope modulation depth.
|
* Sets the lowpass filter envelope modulation depth.
|
||||||
@@ -758,7 +745,7 @@ const generic_params = [
|
|||||||
* .vibmod("<.25 .5 1 2 12>:8")
|
* .vibmod("<.25 .5 1 2 12>:8")
|
||||||
*/
|
*/
|
||||||
[['vibmod', 'vib'], 'vmod'],
|
[['vibmod', 'vib'], 'vmod'],
|
||||||
[['hcutoff', 'hresonance', 'hpenv'], 'hpf', 'hp'],
|
[['hcutoff', 'hresonance'], 'hpf', 'hp'],
|
||||||
/**
|
/**
|
||||||
* Controls the **h**igh-**p**ass **q**-value.
|
* Controls the **h**igh-**p**ass **q**-value.
|
||||||
*
|
*
|
||||||
@@ -891,82 +878,6 @@ const generic_params = [
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
['freq'],
|
['freq'],
|
||||||
// pitch envelope
|
|
||||||
/**
|
|
||||||
* Attack time of pitch envelope.
|
|
||||||
*
|
|
||||||
* @name pattack
|
|
||||||
* @synonyms patt
|
|
||||||
* @param {number | Pattern} time time in seconds
|
|
||||||
* @example
|
|
||||||
* note("<c eb g bb>").pattack("<0 .1 .25 .5>")
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
['pattack', 'patt'],
|
|
||||||
/**
|
|
||||||
* Decay time of pitch envelope.
|
|
||||||
*
|
|
||||||
* @name pdecay
|
|
||||||
* @synonyms pdec
|
|
||||||
* @param {number | Pattern} time time in seconds
|
|
||||||
* @example
|
|
||||||
* note("<c eb g bb>").pdecay("<0 .1 .25 .5>")
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
['pdecay', 'pdec'],
|
|
||||||
// TODO: how to use psustain?!
|
|
||||||
['psustain', 'psus'],
|
|
||||||
/**
|
|
||||||
* Release time of pitch envelope
|
|
||||||
*
|
|
||||||
* @name prelease
|
|
||||||
* @synonyms prel
|
|
||||||
* @param {number | Pattern} time time in seconds
|
|
||||||
* @example
|
|
||||||
* note("<c eb g bb> ~")
|
|
||||||
* .release(.5) // to hear the pitch release
|
|
||||||
* .prelease("<0 .1 .25 .5>")
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
['prelease', 'prel'],
|
|
||||||
/**
|
|
||||||
* Amount of pitch envelope. Negative values will flip the envelope.
|
|
||||||
* If you don't set other pitch envelope controls, `pattack:.2` will be the default.
|
|
||||||
*
|
|
||||||
* @name penv
|
|
||||||
* @param {number | Pattern} semitones change in semitones
|
|
||||||
* @example
|
|
||||||
* note("c")
|
|
||||||
* .penv("<12 7 1 .5 0 -1 -7 -12>")
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
['penv'],
|
|
||||||
/**
|
|
||||||
* Curve of envelope. Defaults to linear. exponential is good for kicks
|
|
||||||
*
|
|
||||||
* @name pcurve
|
|
||||||
* @param {number | Pattern} type 0 = linear, 1 = exponential
|
|
||||||
* @example
|
|
||||||
* note("g1*2")
|
|
||||||
* .s("sine").pdec(.5)
|
|
||||||
* .penv(32)
|
|
||||||
* .pcurve("<0 1>")
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
['pcurve'],
|
|
||||||
/**
|
|
||||||
* Sets the range anchor of the envelope:
|
|
||||||
* - anchor 0: range = [note, note + penv]
|
|
||||||
* - anchor 1: range = [note - penv, note]
|
|
||||||
* If you don't set an anchor, the value will default to the psustain value.
|
|
||||||
*
|
|
||||||
* @name panchor
|
|
||||||
* @param {number | Pattern} anchor anchor offset
|
|
||||||
* @example
|
|
||||||
* note("c").penv(12).panchor("<0 .5 1 .5>")
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
['panchor'],
|
|
||||||
// TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate
|
// TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate
|
||||||
['gate', 'gat'],
|
['gate', 'gat'],
|
||||||
// ['hatgrain'],
|
// ['hatgrain'],
|
||||||
@@ -1285,7 +1196,7 @@ const generic_params = [
|
|||||||
* Formant filter to make things sound like vowels.
|
* Formant filter to make things sound like vowels.
|
||||||
*
|
*
|
||||||
* @name vowel
|
* @name vowel
|
||||||
* @param {string | Pattern} vowel You can use a e i o u ae aa oe ue y uh un en an on, corresponding to [a] [e] [i] [o] [u] [æ] [ɑ] [ø] [y] [ɯ] [ʌ] [œ̃] [ɛ̃] [ɑ̃] [ɔ̃]. Aliases: aa = å = ɑ, oe = ø = ö, y = ı, ae = æ.
|
* @param {string | Pattern} vowel You can use a e i o u.
|
||||||
* @example
|
* @example
|
||||||
* note("c2 <eb2 <g2 g1>>").s('sawtooth')
|
* note("c2 <eb2 <g2 g1>>").s('sawtooth')
|
||||||
* .vowel("<a e i <o u>>")
|
* .vowel("<a e i <o u>>")
|
||||||
@@ -1303,16 +1214,6 @@ const generic_params = [
|
|||||||
* @name waveloss
|
* @name waveloss
|
||||||
*/
|
*/
|
||||||
['waveloss'],
|
['waveloss'],
|
||||||
/*
|
|
||||||
* Noise crackle density
|
|
||||||
*
|
|
||||||
* @name density
|
|
||||||
* @param {number | Pattern} density between 0 and x
|
|
||||||
* @example
|
|
||||||
* s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4))
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
['density'],
|
|
||||||
// TODO: midi effects?
|
// TODO: midi effects?
|
||||||
['dur'],
|
['dur'],
|
||||||
// ['modwheel'],
|
// ['modwheel'],
|
||||||
@@ -1470,20 +1371,10 @@ controls.adsr = register('adsr', (adsr, pat) => {
|
|||||||
const [attack, decay, sustain, release] = adsr;
|
const [attack, decay, sustain, release] = adsr;
|
||||||
return pat.set({ attack, decay, sustain, release });
|
return pat.set({ attack, decay, sustain, release });
|
||||||
});
|
});
|
||||||
controls.ad = register('ad', (t, pat) => {
|
controls.ds = register('ds', (ds, pat) => {
|
||||||
t = !Array.isArray(t) ? [t] : t;
|
ds = !Array.isArray(ds) ? [ds] : ds;
|
||||||
const [attack, decay = attack] = t;
|
const [decay, sustain] = ds;
|
||||||
return pat.attack(attack).decay(decay);
|
|
||||||
});
|
|
||||||
controls.ds = register('ds', (t, pat) => {
|
|
||||||
t = !Array.isArray(t) ? [t] : t;
|
|
||||||
const [decay, sustain = 0] = t;
|
|
||||||
return pat.set({ decay, sustain });
|
return pat.set({ decay, sustain });
|
||||||
});
|
});
|
||||||
controls.ds = register('ar', (t, pat) => {
|
|
||||||
t = !Array.isArray(t) ? [t] : t;
|
|
||||||
const [attack, release = attack] = t;
|
|
||||||
return pat.set({ attack, release });
|
|
||||||
});
|
|
||||||
|
|
||||||
export default controls;
|
export default controls;
|
||||||
|
|||||||
@@ -11,14 +11,13 @@ export class Cyclist {
|
|||||||
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) {
|
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) {
|
||||||
this.started = false;
|
this.started = false;
|
||||||
this.cps = 1;
|
this.cps = 1;
|
||||||
this.num_ticks_since_cps_change = 0;
|
|
||||||
this.lastTick = 0; // absolute time when last tick (clock callback) happened
|
this.lastTick = 0; // absolute time when last tick (clock callback) happened
|
||||||
this.lastBegin = 0; // query begin of last tick
|
this.lastBegin = 0; // query begin of last tick
|
||||||
this.lastEnd = 0; // query end of last tick
|
this.lastEnd = 0; // query end of last tick
|
||||||
this.getTime = getTime; // get absolute time
|
this.getTime = getTime; // get absolute time
|
||||||
this.num_cycles_since_last_cps_change = 0;
|
|
||||||
this.onToggle = onToggle;
|
this.onToggle = onToggle;
|
||||||
this.latency = latency; // fixed trigger time offset
|
this.latency = latency; // fixed trigger time offset
|
||||||
|
const round = (x) => Math.round(x * 1000) / 1000;
|
||||||
this.clock = createClock(
|
this.clock = createClock(
|
||||||
getTime,
|
getTime,
|
||||||
// called slightly before each cycle
|
// called slightly before each cycle
|
||||||
@@ -26,24 +25,14 @@ export class Cyclist {
|
|||||||
if (tick === 0) {
|
if (tick === 0) {
|
||||||
this.origin = phase;
|
this.origin = phase;
|
||||||
}
|
}
|
||||||
if (this.num_ticks_since_cps_change === 0) {
|
|
||||||
this.num_cycles_since_last_cps_change = this.lastEnd;
|
|
||||||
}
|
|
||||||
this.num_ticks_since_cps_change++;
|
|
||||||
try {
|
try {
|
||||||
const time = getTime();
|
const time = getTime();
|
||||||
const begin = this.lastEnd;
|
const begin = this.lastEnd;
|
||||||
this.lastBegin = begin;
|
this.lastBegin = begin;
|
||||||
|
const end = round(begin + duration * this.cps);
|
||||||
//convert ticks to cycles, so you can query the pattern for events
|
|
||||||
const eventLength = duration * this.cps;
|
|
||||||
const end = this.num_cycles_since_last_cps_change + this.num_ticks_since_cps_change * eventLength;
|
|
||||||
this.lastEnd = end;
|
this.lastEnd = end;
|
||||||
|
|
||||||
// query the pattern for events
|
|
||||||
const haps = this.pattern.queryArc(begin, end);
|
const haps = this.pattern.queryArc(begin, end);
|
||||||
|
const tickdeadline = phase - time; // time left till phase begins
|
||||||
const tickdeadline = phase - time; // time left until the phase is a whole number
|
|
||||||
this.lastTick = time + tickdeadline;
|
this.lastTick = time + tickdeadline;
|
||||||
|
|
||||||
haps.forEach((hap) => {
|
haps.forEach((hap) => {
|
||||||
@@ -70,8 +59,6 @@ export class Cyclist {
|
|||||||
this.onToggle?.(v);
|
this.onToggle?.(v);
|
||||||
}
|
}
|
||||||
start() {
|
start() {
|
||||||
this.num_ticks_since_cps_change = 0;
|
|
||||||
this.num_cycles_since_last_cps_change = 0;
|
|
||||||
if (!this.pattern) {
|
if (!this.pattern) {
|
||||||
throw new Error('Scheduler: no pattern set! call .setPattern first.');
|
throw new Error('Scheduler: no pattern set! call .setPattern first.');
|
||||||
}
|
}
|
||||||
@@ -97,11 +84,7 @@ export class Cyclist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setCps(cps = 1) {
|
setCps(cps = 1) {
|
||||||
if (this.cps === cps) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.cps = cps;
|
this.cps = cps;
|
||||||
this.num_ticks_since_cps_change = 0;
|
|
||||||
}
|
}
|
||||||
log(begin, end, haps) {
|
log(begin, end, haps) {
|
||||||
const onsets = haps.filter((h) => h.hasOnset());
|
const onsets = haps.filter((h) => h.hasOnset());
|
||||||
|
|||||||
@@ -111,6 +111,8 @@ export class Framer {
|
|||||||
// see vite-vanilla-repl-cm6 for an example
|
// see vite-vanilla-repl-cm6 for an example
|
||||||
export class Drawer {
|
export class Drawer {
|
||||||
constructor(onDraw, drawTime) {
|
constructor(onDraw, drawTime) {
|
||||||
|
let [lookbehind, lookahead] = drawTime; // e.g. [-2, 2]
|
||||||
|
lookbehind = Math.abs(lookbehind);
|
||||||
this.visibleHaps = [];
|
this.visibleHaps = [];
|
||||||
this.lastFrame = null;
|
this.lastFrame = null;
|
||||||
this.drawTime = drawTime;
|
this.drawTime = drawTime;
|
||||||
@@ -120,8 +122,6 @@ export class Drawer {
|
|||||||
console.warn('Drawer: no scheduler');
|
console.warn('Drawer: no scheduler');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const lookbehind = Math.abs(this.drawTime[0]);
|
|
||||||
const lookahead = this.drawTime[1];
|
|
||||||
// calculate current frame time (think right side of screen for pianoroll)
|
// calculate current frame time (think right side of screen for pianoroll)
|
||||||
const phase = this.scheduler.now() + lookahead;
|
const phase = this.scheduler.now() + lookahead;
|
||||||
// first frame just captures the phase
|
// first frame just captures the phase
|
||||||
@@ -145,16 +145,12 @@ export class Drawer {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setDrawTime(drawTime) {
|
invalidate(scheduler = this.scheduler) {
|
||||||
this.drawTime = drawTime;
|
if (!scheduler || !scheduler.pattern) {
|
||||||
}
|
|
||||||
invalidate(scheduler = this.scheduler, t) {
|
|
||||||
if (!scheduler) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: scheduler.now() seems to move even when it's stopped, this hints at a bug...
|
|
||||||
t = t ?? scheduler.now();
|
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
|
const t = scheduler.now();
|
||||||
let [_, lookahead] = this.drawTime;
|
let [_, lookahead] = this.drawTime;
|
||||||
const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1];
|
const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1];
|
||||||
// remove all future haps
|
// remove all future haps
|
||||||
|
|||||||
@@ -145,8 +145,6 @@ export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], fun
|
|||||||
* so there will be no gaps.
|
* so there will be no gaps.
|
||||||
* @name euclidLegato
|
* @name euclidLegato
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @param {number} pulses the number of onsets / beats
|
|
||||||
* @param {number} steps the number of steps to fill
|
|
||||||
* @example
|
* @example
|
||||||
* n("g2").decay(.1).sustain(.3).euclidLegato(3,8)
|
* n("g2").decay(.1).sustain(.3).euclidLegato(3,8)
|
||||||
*/
|
*/
|
||||||
@@ -168,18 +166,6 @@ export const euclidLegato = register(['euclidLegato'], function (pulses, steps,
|
|||||||
return _euclidLegato(pulses, steps, 0, pat);
|
return _euclidLegato(pulses, steps, 0, pat);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Similar to `euclid`, but each pulse is held until the next pulse,
|
|
||||||
* so there will be no gaps, and has an additional parameter for 'rotating'
|
|
||||||
* the resulting sequence
|
|
||||||
* @name euclidLegatoRot
|
|
||||||
* @memberof Pattern
|
|
||||||
* @param {number} pulses the number of onsets / beats
|
|
||||||
* @param {number} steps the number of steps to fill
|
|
||||||
* @param {number} rotation offset in steps
|
|
||||||
* @example
|
|
||||||
* note("c3").euclidLegatoRot(3,5,2)
|
|
||||||
*/
|
|
||||||
export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, steps, rotation, pat) {
|
export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, steps, rotation, pat) {
|
||||||
return _euclidLegato(pulses, steps, rotation, pat);
|
return _euclidLegato(pulses, steps, rotation, pat);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,5 +47,10 @@ export const evaluate = async (code, transpiler) => {
|
|||||||
// if no transpiler is given, we expect a single instruction (!wrapExpression)
|
// if no transpiler is given, we expect a single instruction (!wrapExpression)
|
||||||
const options = { wrapExpression: !!transpiler };
|
const options = { wrapExpression: !!transpiler };
|
||||||
let evaluated = await safeEval(code, options);
|
let evaluated = await safeEval(code, options);
|
||||||
|
if (!isPattern(evaluated)) {
|
||||||
|
console.log('evaluated', evaluated);
|
||||||
|
const message = `got "${typeof evaluated}" instead of pattern`;
|
||||||
|
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
|
||||||
|
}
|
||||||
return { mode: 'javascript', pattern: evaluated, meta };
|
return { mode: 'javascript', pattern: evaluated, meta };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# vite-vanilla-repl-cm6
|
||||||
|
|
||||||
|
This folder demonstrates how to set up a strudel repl using vite and vanilla JS + codemirror. Run it using:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm i
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^5.0.10"
|
"vite": "^4.3.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel/codemirror": "workspace:*",
|
"@strudel/codemirror": "workspace:*",
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
!dist
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# vite-vanilla-repl
|
||||||
|
|
||||||
|
This folder demonstrates how to set up a strudel repl using vite and vanilla JS. Run it using:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm i
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import{b as s,h as i,m,a as n,c as p,p as t}from"./index.4cbc0a10.js";export{s as SyntaxError,i as h,m as mini,n as minify,p as parse,t as patternifyAST};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
import{g as s,f as d,i as t,n as r,l as u,j as o,d as f,k as p,r as g,s as i,w as l,e as m}from"./index.4cbc0a10.js";export{s as getAudioContext,d as getCachedBuffer,t as getLoadedBuffer,r as getLoadedSamples,u as loadBuffer,o as loadGithubSamples,f as panic,p as resetLoadedSamples,g as reverseBuffer,i as samples,l as webaudioOutput,m as webaudioOutputTrigger};
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite Vanilla Strudel REPL</title>
|
||||||
|
<script type="module" crossorigin src="/tidalcycles/strudel/use-acorn/packages/core/examples/vite-vanilla-repl/dist/assets/index.4cbc0a10.js"></script>
|
||||||
|
</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"
|
||||||
|
></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>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^5.0.10"
|
"vite": "^4.3.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "workspace:*",
|
"@strudel.cycles/core": "workspace:*",
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
gist.js - <short description TODO>
|
||||||
|
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/gist.js>
|
||||||
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// this is a shortcut to eval code from a gist
|
||||||
|
// why? to be able to shorten strudel code + e.g. be able to change instruments after links have been generated
|
||||||
|
export default (route, cache = true) =>
|
||||||
|
fetch(`https://gist.githubusercontent.com/${route}?cachebust=${cache ? '' : Date.now()}`)
|
||||||
|
.then((res) => res.text())
|
||||||
|
.then((code) => eval(code));
|
||||||
@@ -27,6 +27,7 @@ export * from './pianoroll.mjs';
|
|||||||
export * from './spiral.mjs';
|
export * from './spiral.mjs';
|
||||||
export * from './ui.mjs';
|
export * from './ui.mjs';
|
||||||
export { default as drawLine } from './drawLine.mjs';
|
export { default as drawLine } from './drawLine.mjs';
|
||||||
|
export { default as gist } from './gist.js';
|
||||||
// below won't work with runtime.mjs (json import fails)
|
// below won't work with runtime.mjs (json import fails)
|
||||||
/* import * as p from './package.json';
|
/* import * as p from './package.json';
|
||||||
export const version = p.version; */
|
export const version = p.version; */
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/core",
|
"name": "@strudel.cycles/core",
|
||||||
"version": "0.10.0",
|
"version": "0.9.0",
|
||||||
"description": "Port of Tidal Cycles to JavaScript",
|
"description": "Port of Tidal Cycles to JavaScript",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -31,11 +31,12 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://strudel.cc",
|
"homepage": "https://strudel.cc",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fraction.js": "^4.3.7"
|
"fraction.js": "^4.2.0",
|
||||||
|
"nanostores": "^0.8.1"
|
||||||
},
|
},
|
||||||
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
|
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^5.0.10",
|
"vite": "^4.3.3",
|
||||||
"vitest": "^1.1.0"
|
"vitest": "^0.33.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1178,7 +1178,7 @@ export function reify(thing) {
|
|||||||
* @return {Pattern}
|
* @return {Pattern}
|
||||||
* @synonyms polyrhythm, pr
|
* @synonyms polyrhythm, pr
|
||||||
* @example
|
* @example
|
||||||
* stack("g3", "b3", ["e4", "d4"]).note() // "g3,b3,[e4,d4]".note()
|
* stack(g3, b3, [e4, d4]).note() // "g3,b3,[e4,d4]".note()
|
||||||
*/
|
*/
|
||||||
export function stack(...pats) {
|
export function stack(...pats) {
|
||||||
// Array test here is to avoid infinite recursions..
|
// Array test here is to avoid infinite recursions..
|
||||||
@@ -1193,7 +1193,7 @@ export function stack(...pats) {
|
|||||||
*
|
*
|
||||||
* @return {Pattern}
|
* @return {Pattern}
|
||||||
* @example
|
* @example
|
||||||
* slowcat("e5", "b4", ["d5", "c5"])
|
* slowcat(e5, b4, [d5, c5])
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function slowcat(...pats) {
|
export function slowcat(...pats) {
|
||||||
@@ -1237,7 +1237,7 @@ export function slowcatPrime(...pats) {
|
|||||||
* @synonyms slowcat
|
* @synonyms slowcat
|
||||||
* @return {Pattern}
|
* @return {Pattern}
|
||||||
* @example
|
* @example
|
||||||
* cat("e5", "b4", ["d5", "c5"]).note() // "<e5 b4 [d5 c5]>".note()
|
* cat(e5, b4, [d5, c5]).note() // "<e5 b4 [d5 c5]>".note()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function cat(...pats) {
|
export function cat(...pats) {
|
||||||
@@ -1247,7 +1247,7 @@ export function cat(...pats) {
|
|||||||
/** Like {@link Pattern.seq}, but each step has a length, relative to the whole.
|
/** Like {@link Pattern.seq}, but each step has a length, relative to the whole.
|
||||||
* @return {Pattern}
|
* @return {Pattern}
|
||||||
* @example
|
* @example
|
||||||
* timeCat([3,"e3"],[1, "g3"]).note() // "e3@3 g3".note()
|
* timeCat([3,e3],[1, g3]).note() // "e3@3 g3".note()
|
||||||
*/
|
*/
|
||||||
export function timeCat(...timepats) {
|
export function timeCat(...timepats) {
|
||||||
const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0));
|
const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0));
|
||||||
@@ -1287,7 +1287,7 @@ export function sequence(...pats) {
|
|||||||
/** Like **cat**, but the items are crammed into one cycle.
|
/** Like **cat**, but the items are crammed into one cycle.
|
||||||
* @synonyms fastcat, sequence
|
* @synonyms fastcat, sequence
|
||||||
* @example
|
* @example
|
||||||
* seq("e5", "b4", ["d5", "c5"]).note() // "e5 b4 [d5 c5]".note()
|
* seq(e5, b4, [d5, c5]).note() // "e5 b4 [d5 c5]".note()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function seq(...pats) {
|
export function seq(...pats) {
|
||||||
@@ -1975,9 +1975,9 @@ export const press = register('press', function (pat) {
|
|||||||
* s("hh*3")
|
* s("hh*3")
|
||||||
* )
|
* )
|
||||||
*/
|
*/
|
||||||
Pattern.prototype.hush = function () {
|
export const hush = register('hush', function (pat) {
|
||||||
return silence;
|
return silence;
|
||||||
};
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies `rev` to a pattern every other cycle, so that the pattern alternates between forwards and backwards.
|
* Applies `rev` to a pattern every other cycle, so that the pattern alternates between forwards and backwards.
|
||||||
@@ -1985,7 +1985,7 @@ Pattern.prototype.hush = function () {
|
|||||||
* note("c d e g").palindrome()
|
* note("c d e g").palindrome()
|
||||||
*/
|
*/
|
||||||
export const palindrome = register('palindrome', function (pat) {
|
export const palindrome = register('palindrome', function (pat) {
|
||||||
return pat.lastOf(2, rev);
|
return pat.every(2, rev);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2189,14 +2189,6 @@ export const duration = register('duration', function (value, pat) {
|
|||||||
return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
|
return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
|
||||||
});
|
});
|
||||||
|
|
||||||
export const hsla = register('hsla', (h, s, l, a, pat) => {
|
|
||||||
return pat.color(`hsla(${h}turn,${s * 100}%,${l * 100}%,${a})`);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const hsl = register('hsl', (h, s, l, pat) => {
|
|
||||||
return pat.color(`hsl(${h}turn,${s * 100}%,${l * 100}%)`);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the color of the hap in visualizations like pianoroll or highlighting.
|
* Sets the color of the hap in visualizations like pianoroll or highlighting.
|
||||||
* @name color
|
* @name color
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export function pianoroll({
|
|||||||
hideInactive = 0,
|
hideInactive = 0,
|
||||||
colorizeInactive = 1,
|
colorizeInactive = 1,
|
||||||
fontFamily,
|
fontFamily,
|
||||||
|
clear = true,
|
||||||
ctx,
|
ctx,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const w = ctx.canvas.width;
|
const w = ctx.canvas.width;
|
||||||
@@ -165,7 +166,7 @@ export function pianoroll({
|
|||||||
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
|
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
|
||||||
ctx.fillStyle = background;
|
ctx.fillStyle = background;
|
||||||
ctx.globalAlpha = 1; // reset!
|
ctx.globalAlpha = 1; // reset!
|
||||||
if (!smear) {
|
if (clear && !smear) {
|
||||||
ctx.clearRect(0, 0, w, h);
|
ctx.clearRect(0, 0, w, h);
|
||||||
ctx.fillRect(0, 0, w, h);
|
ctx.fillRect(0, 0, w, h);
|
||||||
}
|
}
|
||||||
@@ -256,21 +257,12 @@ export function getDrawOptions(drawTime, options = {}) {
|
|||||||
return { fold: 1, ...options, cycles, playhead };
|
return { fold: 1, ...options, cycles, playhead };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPunchcardPainter =
|
|
||||||
(options = {}) =>
|
|
||||||
(ctx, time, haps, drawTime, paintOptions = {}) =>
|
|
||||||
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, { ...paintOptions, ...options }) });
|
|
||||||
|
|
||||||
Pattern.prototype.punchcard = function (options) {
|
Pattern.prototype.punchcard = function (options) {
|
||||||
return this.onPaint(getPunchcardPainter(options));
|
return this.onPaint((ctx, time, haps, drawTime, paintOptions = {}) =>
|
||||||
|
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, { ...paintOptions, ...options }) }),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a vertical pianoroll with event labels.
|
|
||||||
* Supports all the same options as pianoroll.
|
|
||||||
*
|
|
||||||
* @name wordfall
|
|
||||||
*/
|
|
||||||
Pattern.prototype.wordfall = function (options) {
|
Pattern.prototype.wordfall = function (options) {
|
||||||
return this.punchcard({ vertical: 1, labels: 1, stroke: 0, fillActive: 1, active: 'white', ...options });
|
return this.punchcard({ vertical: 1, labels: 1, stroke: 0, fillActive: 1, active: 'white', ...options });
|
||||||
};
|
};
|
||||||
|
|||||||
+41
-85
@@ -3,7 +3,23 @@ import { evaluate as _evaluate } from './evaluate.mjs';
|
|||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
import { setTime } from './time.mjs';
|
import { setTime } from './time.mjs';
|
||||||
import { evalScope } from './evaluate.mjs';
|
import { evalScope } from './evaluate.mjs';
|
||||||
import { register, Pattern, isPattern, silence, stack } from './pattern.mjs';
|
import { register } from './pattern.mjs';
|
||||||
|
import { atom, computed } from 'nanostores';
|
||||||
|
|
||||||
|
export const $replstate = atom({
|
||||||
|
schedulerError: undefined,
|
||||||
|
evalError: undefined,
|
||||||
|
code: '// LOADING',
|
||||||
|
activeCode: '// LOADING',
|
||||||
|
pattern: undefined,
|
||||||
|
miniLocations: [],
|
||||||
|
widgets: [],
|
||||||
|
pending: true,
|
||||||
|
started: false,
|
||||||
|
});
|
||||||
|
export const $repldirty = computed($replstate, (s) => s.code !== s.activeCode);
|
||||||
|
|
||||||
|
export const setReplState = (key, value) => $replstate.set({ ...$replstate.get(), [key]: value });
|
||||||
|
|
||||||
export function repl({
|
export function repl({
|
||||||
interval,
|
interval,
|
||||||
@@ -16,87 +32,52 @@ export function repl({
|
|||||||
transpiler,
|
transpiler,
|
||||||
onToggle,
|
onToggle,
|
||||||
editPattern,
|
editPattern,
|
||||||
onUpdateState,
|
|
||||||
}) {
|
}) {
|
||||||
const state = {
|
|
||||||
schedulerError: undefined,
|
|
||||||
evalError: undefined,
|
|
||||||
code: '// LOADING',
|
|
||||||
activeCode: '// LOADING',
|
|
||||||
pattern: undefined,
|
|
||||||
miniLocations: [],
|
|
||||||
widgets: [],
|
|
||||||
pending: false,
|
|
||||||
started: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateState = (update) => {
|
|
||||||
Object.assign(state, update);
|
|
||||||
state.isDirty = state.code !== state.activeCode;
|
|
||||||
state.error = state.evalError || state.schedulerError;
|
|
||||||
onUpdateState?.(state);
|
|
||||||
};
|
|
||||||
|
|
||||||
const scheduler = new Cyclist({
|
const scheduler = new Cyclist({
|
||||||
interval,
|
interval,
|
||||||
onTrigger: getTrigger({ defaultOutput, getTime }),
|
onTrigger: getTrigger({ defaultOutput, getTime }),
|
||||||
onError: onSchedulerError,
|
onError: onSchedulerError,
|
||||||
getTime,
|
getTime,
|
||||||
onToggle: (started) => {
|
onToggle: (started) => {
|
||||||
updateState({ started });
|
setReplState('started', started);
|
||||||
onToggle?.(started);
|
onToggle?.(started);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let pPatterns = {};
|
let playPatterns = [];
|
||||||
let allTransform;
|
|
||||||
|
|
||||||
const hush = function () {
|
|
||||||
pPatterns = {};
|
|
||||||
allTransform = undefined;
|
|
||||||
return silence;
|
|
||||||
};
|
|
||||||
|
|
||||||
const setPattern = (pattern, autostart = true) => {
|
const setPattern = (pattern, autostart = true) => {
|
||||||
pattern = editPattern?.(pattern) || pattern;
|
pattern = editPattern?.(pattern) || pattern;
|
||||||
scheduler.setPattern(pattern, autostart);
|
scheduler.setPattern(pattern, autostart);
|
||||||
};
|
};
|
||||||
setTime(() => scheduler.now()); // TODO: refactor?
|
setTime(() => scheduler.now()); // TODO: refactor?
|
||||||
const evaluate = async (code, autostart = true, shouldHush = true) => {
|
const evaluate = async (code, autostart = true) => {
|
||||||
if (!code) {
|
if (!code) {
|
||||||
throw new Error('no code to evaluate');
|
throw new Error('no code to evaluate');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
updateState({ code, pending: true });
|
setReplState('code', code);
|
||||||
|
setReplState('pending', true);
|
||||||
await beforeEval?.({ code });
|
await beforeEval?.({ code });
|
||||||
shouldHush && hush();
|
playPatterns = [];
|
||||||
let { pattern, meta } = await _evaluate(code, transpiler);
|
let { pattern, meta } = await _evaluate(code, transpiler);
|
||||||
if (Object.keys(pPatterns).length) {
|
if (playPatterns.length) {
|
||||||
pattern = stack(...Object.values(pPatterns));
|
pattern = pattern.stack(...playPatterns);
|
||||||
}
|
|
||||||
if (allTransform) {
|
|
||||||
pattern = allTransform(pattern);
|
|
||||||
}
|
|
||||||
if (!isPattern(pattern)) {
|
|
||||||
const message = `got "${typeof evaluated}" instead of pattern`;
|
|
||||||
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
|
|
||||||
}
|
}
|
||||||
logger(`[eval] code updated`);
|
logger(`[eval] code updated`);
|
||||||
setPattern(pattern, autostart);
|
setPattern(pattern, autostart);
|
||||||
updateState({
|
setReplState('miniLocations', meta?.miniLocations || []);
|
||||||
miniLocations: meta?.miniLocations || [],
|
setReplState('widgets', meta?.widgets || []);
|
||||||
widgets: meta?.widgets || [],
|
setReplState('activeCode', code);
|
||||||
activeCode: code,
|
setReplState('pattern', pattern);
|
||||||
pattern,
|
setReplState('evalError', undefined);
|
||||||
evalError: undefined,
|
setReplState('schedulerError', undefined);
|
||||||
schedulerError: undefined,
|
setReplState('pending', false);
|
||||||
pending: false,
|
|
||||||
});
|
|
||||||
afterEval?.({ code, pattern, meta });
|
afterEval?.({ code, pattern, meta });
|
||||||
return pattern;
|
return pattern;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.warn(`[repl] eval error: ${err.message}`);
|
// console.warn(`[repl] eval error: ${err.message}`);
|
||||||
logger(`[eval] error: ${err.message}`, 'error');
|
logger(`[eval] error: ${err.message}`, 'error');
|
||||||
updateState({ evalError: err, pending: false });
|
setReplState('evalError', err);
|
||||||
|
setReplState('pending', false);
|
||||||
onEvalError?.(err);
|
onEvalError?.(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -112,35 +93,10 @@ export function repl({
|
|||||||
return pat.loopAtCps(cycles, scheduler.cps);
|
return pat.loopAtCps(cycles, scheduler.cps);
|
||||||
});
|
});
|
||||||
|
|
||||||
Pattern.prototype.p = function (id) {
|
const play = register('play', (pat) => {
|
||||||
pPatterns[id] = this;
|
playPatterns.push(pat);
|
||||||
return this;
|
return pat;
|
||||||
};
|
|
||||||
Pattern.prototype.q = function (id) {
|
|
||||||
return silence;
|
|
||||||
};
|
|
||||||
|
|
||||||
const all = function (transform) {
|
|
||||||
allTransform = transform;
|
|
||||||
return silence;
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
for (let i = 1; i < 10; ++i) {
|
|
||||||
Object.defineProperty(Pattern.prototype, `d${i}`, {
|
|
||||||
get() {
|
|
||||||
return this.p(i);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
Object.defineProperty(Pattern.prototype, `p${i}`, {
|
|
||||||
get() {
|
|
||||||
return this.p(i);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Pattern.prototype[`q${i}`] = silence;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// already defined..
|
|
||||||
}
|
|
||||||
|
|
||||||
const fit = register('fit', (pat) =>
|
const fit = register('fit', (pat) =>
|
||||||
pat.withHap((hap) =>
|
pat.withHap((hap) =>
|
||||||
@@ -155,15 +111,15 @@ export function repl({
|
|||||||
evalScope({
|
evalScope({
|
||||||
loopAt,
|
loopAt,
|
||||||
fit,
|
fit,
|
||||||
all,
|
play,
|
||||||
hush,
|
|
||||||
setCps,
|
setCps,
|
||||||
setcps: setCps,
|
setcps: setCps,
|
||||||
setCpm,
|
setCpm,
|
||||||
setcpm: setCpm,
|
setcpm: setCpm,
|
||||||
});
|
});
|
||||||
const setCode = (code) => updateState({ code });
|
|
||||||
return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle, state };
|
const setCode = (c) => setReplState('code', c);
|
||||||
|
return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTrigger =
|
export const getTrigger =
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ const timeToRand = (x) => Math.abs(intSeedToRand(timeToIntSeed(x)));
|
|||||||
|
|
||||||
const timeToRandsPrime = (seed, n) => {
|
const timeToRandsPrime = (seed, n) => {
|
||||||
const result = [];
|
const result = [];
|
||||||
// eslint-disable-next-line
|
|
||||||
for (let i = 0; i < n; ++n) {
|
for (let i = 0; i < n; ++n) {
|
||||||
result.push(intSeedToRand(seed));
|
result.push(intSeedToRand(seed));
|
||||||
seed = xorwise(seed);
|
seed = xorwise(seed);
|
||||||
|
|||||||
@@ -67,9 +67,9 @@ Pattern.prototype.spiral = function (options = {}) {
|
|||||||
// logSpiral = true,
|
// logSpiral = true,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
function spiral({ ctx, time, haps, drawTime }) {
|
function spiral({ ctx, time, haps, drawTime, clear }) {
|
||||||
const [w, h] = [ctx.canvas.width, ctx.canvas.height];
|
const [w, h] = [ctx.canvas.width, ctx.canvas.height];
|
||||||
ctx.clearRect(0, 0, w * 2, h * 2);
|
clear && ctx.clearRect(0, 0, w * 2, h * 2);
|
||||||
const [cx, cy] = [w / 2, h / 2];
|
const [cx, cy] = [w / 2, h / 2];
|
||||||
const settings = {
|
const settings = {
|
||||||
margin: size / stretch,
|
margin: size / stretch,
|
||||||
@@ -114,5 +114,5 @@ Pattern.prototype.spiral = function (options = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.onPaint((ctx, time, haps, drawTime) => spiral({ ctx, time, haps, drawTime }));
|
return this.onPaint((ctx, time, haps, drawTime, options) => spiral({ ctx, time, haps, drawTime, ...options }));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
slowcat,
|
slowcat,
|
||||||
cat,
|
cat,
|
||||||
sequence,
|
sequence,
|
||||||
palindrome,
|
|
||||||
polymeter,
|
polymeter,
|
||||||
polymeterSteps,
|
polymeterSteps,
|
||||||
polyrhythm,
|
polyrhythm,
|
||||||
@@ -183,17 +182,13 @@ describe('Pattern', () => {
|
|||||||
});
|
});
|
||||||
it('can Trig() structure', () => {
|
it('can Trig() structure', () => {
|
||||||
sameFirst(
|
sameFirst(
|
||||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
|
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).add.trig(20, 30).early(2),
|
||||||
.add.trig(20, 30)
|
|
||||||
.early(2),
|
|
||||||
sequence(26, 27, 36, 37),
|
sequence(26, 27, 36, 37),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('can Trigzero() structure', () => {
|
it('can Trigzero() structure', () => {
|
||||||
sameFirst(
|
sameFirst(
|
||||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
|
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).add.trigzero(20, 30).early(2),
|
||||||
.add.trigzero(20, 30)
|
|
||||||
.early(2),
|
|
||||||
sequence(21, 22, 31, 32),
|
sequence(21, 22, 31, 32),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -235,17 +230,13 @@ describe('Pattern', () => {
|
|||||||
});
|
});
|
||||||
it('can Trig() structure', () => {
|
it('can Trig() structure', () => {
|
||||||
sameFirst(
|
sameFirst(
|
||||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
|
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keep.trig(20, 30).early(2),
|
||||||
.keep.trig(20, 30)
|
|
||||||
.early(2),
|
|
||||||
sequence(6, 7, 6, 7),
|
sequence(6, 7, 6, 7),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('can Trigzero() structure', () => {
|
it('can Trigzero() structure', () => {
|
||||||
sameFirst(
|
sameFirst(
|
||||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
|
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keep.trigzero(20, 30).early(2),
|
||||||
.keep.trigzero(20, 30)
|
|
||||||
.early(2),
|
|
||||||
sequence(1, 2, 1, 2),
|
sequence(1, 2, 1, 2),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -281,17 +272,13 @@ describe('Pattern', () => {
|
|||||||
});
|
});
|
||||||
it('can Trig() structure', () => {
|
it('can Trig() structure', () => {
|
||||||
sameFirst(
|
sameFirst(
|
||||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
|
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepif.trig(false, true).early(2),
|
||||||
.keepif.trig(false, true)
|
|
||||||
.early(2),
|
|
||||||
sequence(silence, silence, 6, 7),
|
sequence(silence, silence, 6, 7),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('can Trigzero() structure', () => {
|
it('can Trigzero() structure', () => {
|
||||||
sameFirst(
|
sameFirst(
|
||||||
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10)
|
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepif.trigzero(false, true).early(2),
|
||||||
.keepif.trigzero(false, true)
|
|
||||||
.early(2),
|
|
||||||
sequence(silence, silence, 1, 2),
|
sequence(silence, silence, 1, 2),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -584,18 +571,6 @@ describe('Pattern', () => {
|
|||||||
expect(sequence(1, 2, 3).firstCycle()).toStrictEqual(fastcat(1, 2, 3).firstCycle());
|
expect(sequence(1, 2, 3).firstCycle()).toStrictEqual(fastcat(1, 2, 3).firstCycle());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('palindrome()', () => {
|
|
||||||
it('Can create palindrome', () => {
|
|
||||||
expect(
|
|
||||||
fastcat('a', 'b', 'c')
|
|
||||||
.palindrome()
|
|
||||||
.fast(2)
|
|
||||||
.firstCycle()
|
|
||||||
.sort((a, b) => a.part.begin.sub(b.part.begin))
|
|
||||||
.map((a) => a.value),
|
|
||||||
).toStrictEqual(['a', 'b', 'c', 'c', 'b', 'a']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('polyrhythm()', () => {
|
describe('polyrhythm()', () => {
|
||||||
it('Can layer up cycles', () => {
|
it('Can layer up cycles', () => {
|
||||||
expect(polyrhythm(['a', 'b'], ['c']).firstCycle()).toStrictEqual(
|
expect(polyrhythm(['a', 'b'], ['c']).firstCycle()).toStrictEqual(
|
||||||
@@ -663,11 +638,7 @@ describe('Pattern', () => {
|
|||||||
});
|
});
|
||||||
describe('struct()', () => {
|
describe('struct()', () => {
|
||||||
it('Can restructure a discrete pattern', () => {
|
it('Can restructure a discrete pattern', () => {
|
||||||
expect(
|
expect(sequence('a', 'b').struct(sequence(true, true, true)).firstCycle()).toStrictEqual([
|
||||||
sequence('a', 'b')
|
|
||||||
.struct(sequence(true, true, true))
|
|
||||||
.firstCycle(),
|
|
||||||
).toStrictEqual([
|
|
||||||
hap(ts(0, third), ts(0, third), 'a'),
|
hap(ts(0, third), ts(0, third), 'a'),
|
||||||
hap(ts(third, twothirds), ts(third, 0.5), 'a'),
|
hap(ts(third, twothirds), ts(third, 0.5), 'a'),
|
||||||
hap(ts(third, twothirds), ts(0.5, twothirds), 'b'),
|
hap(ts(third, twothirds), ts(0.5, twothirds), 'b'),
|
||||||
@@ -698,11 +669,7 @@ describe('Pattern', () => {
|
|||||||
});
|
});
|
||||||
describe('mask()', () => {
|
describe('mask()', () => {
|
||||||
it('Can fragment a pattern', () => {
|
it('Can fragment a pattern', () => {
|
||||||
expect(
|
expect(sequence('a', 'b').mask(sequence(true, true, true)).firstCycle()).toStrictEqual([
|
||||||
sequence('a', 'b')
|
|
||||||
.mask(sequence(true, true, true))
|
|
||||||
.firstCycle(),
|
|
||||||
).toStrictEqual([
|
|
||||||
hap(ts(0, 0.5), ts(0, third), 'a'),
|
hap(ts(0, 0.5), ts(0, third), 'a'),
|
||||||
hap(ts(0, 0.5), ts(third, 0.5), 'a'),
|
hap(ts(0, 0.5), ts(third, 0.5), 'a'),
|
||||||
hap(ts(0.5, 1), ts(0.5, twothirds), 'b'),
|
hap(ts(0.5, 1), ts(0.5, twothirds), 'b'),
|
||||||
@@ -971,11 +938,9 @@ describe('Pattern', () => {
|
|||||||
expect(stack(pure('a').mask(1, 0), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(1);
|
expect(stack(pure('a').mask(1, 0), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(1);
|
||||||
});
|
});
|
||||||
it('Doesnt merge two overlapping haps', () => {
|
it('Doesnt merge two overlapping haps', () => {
|
||||||
expect(
|
expect(stack(pure('a').mask(1, 1, 0), pure('a').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(
|
||||||
stack(pure('a').mask(1, 1, 0), pure('a').mask(0, 1))
|
2,
|
||||||
.defragmentHaps()
|
);
|
||||||
.firstCycle().length,
|
|
||||||
).toStrictEqual(2);
|
|
||||||
});
|
});
|
||||||
it('Doesnt merge two touching haps with different values', () => {
|
it('Doesnt merge two touching haps with different values', () => {
|
||||||
expect(stack(pure('a').mask(1, 0), pure('b').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(2);
|
expect(stack(pure('a').mask(1, 0), pure('b').mask(0, 1)).defragmentHaps().firstCycle().length).toStrictEqual(2);
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ 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/>.
|
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 { logger } from './logger.mjs';
|
|
||||||
|
|
||||||
// returns true if the given string is a note
|
// returns true if the given string is a note
|
||||||
export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name);
|
export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name);
|
||||||
export const isNote = (name) => /^[a-gA-G][#bsf]*[0-9]?$/.test(name);
|
export const isNote = (name) => /^[a-gA-G][#bsf]*[0-9]?$/.test(name);
|
||||||
@@ -86,18 +84,6 @@ export const midi2note = (n) => {
|
|||||||
// modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does)
|
// modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does)
|
||||||
export const _mod = (n, m) => ((n % m) + m) % m;
|
export const _mod = (n, m) => ((n % m) + m) % m;
|
||||||
|
|
||||||
export function nanFallback(value, fallback = 0) {
|
|
||||||
if (isNaN(Number(value))) {
|
|
||||||
logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning');
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
// round to nearest int, negative numbers will output a subtracted index
|
|
||||||
export const getSoundIndex = (n, numSounds) => {
|
|
||||||
return _mod(Math.round(nanFallback(n ?? 0, 0)), numSounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPlayableNoteValue = (hap) => {
|
export const getPlayableNoteValue = (hap) => {
|
||||||
let { value, context } = hap;
|
let { value, context } = hap;
|
||||||
let note = value;
|
let note = value;
|
||||||
@@ -288,31 +274,3 @@ export const sol2note = (n, notation = 'letters') => {
|
|||||||
const oct = Math.floor(n / 12) - 1;
|
const oct = Math.floor(n / 12) - 1;
|
||||||
return note + oct;
|
return note + oct;
|
||||||
};
|
};
|
||||||
|
|
||||||
// code hashing helpers
|
|
||||||
|
|
||||||
export function unicodeToBase64(text) {
|
|
||||||
const utf8Bytes = new TextEncoder().encode(text);
|
|
||||||
const base64String = btoa(String.fromCharCode(...utf8Bytes));
|
|
||||||
return base64String;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function base64ToUnicode(base64String) {
|
|
||||||
const utf8Bytes = new Uint8Array(
|
|
||||||
atob(base64String)
|
|
||||||
.split('')
|
|
||||||
.map((char) => char.charCodeAt(0)),
|
|
||||||
);
|
|
||||||
const decodedText = new TextDecoder().decode(utf8Bytes);
|
|
||||||
return decodedText;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function code2hash(code) {
|
|
||||||
return encodeURIComponent(unicodeToBase64(code));
|
|
||||||
//return '#' + encodeURIComponent(btoa(code));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hash2code(hash) {
|
|
||||||
return base64ToUnicode(decodeURIComponent(hash));
|
|
||||||
//return atob(decodeURIComponent(codeParam || ''));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default defineConfig({
|
|||||||
lib: {
|
lib: {
|
||||||
entry: resolve(__dirname, 'index.mjs'),
|
entry: resolve(__dirname, 'index.mjs'),
|
||||||
formats: ['es', 'cjs'],
|
formats: ['es', 'cjs'],
|
||||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: [...Object.keys(dependencies)],
|
external: [...Object.keys(dependencies)],
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
# @strudel.cycles/csound
|
|
||||||
|
|
||||||
**DEPRECATION NOTE**: This package is old and won't get any updates! The newer version goes by the name of [@strudel/csound](https://www.npmjs.com/package/@strudel/csound).
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/csound",
|
"name": "@strudel.cycles/csound",
|
||||||
"version": "0.10.0",
|
"version": "0.9.0",
|
||||||
"description": "csound bindings for strudel",
|
"description": "csound bindings for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
@@ -37,6 +37,6 @@
|
|||||||
"@strudel.cycles/webaudio": "workspace:*"
|
"@strudel.cycles/webaudio": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^5.0.10"
|
"vite": "^4.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default defineConfig({
|
|||||||
lib: {
|
lib: {
|
||||||
entry: resolve(__dirname, 'index.mjs'),
|
entry: resolve(__dirname, 'index.mjs'),
|
||||||
formats: ['es', 'cjs'],
|
formats: ['es', 'cjs'],
|
||||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: [...Object.keys(dependencies)],
|
external: [...Object.keys(dependencies)],
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "workspace:*",
|
"@strudel.cycles/core": "workspace:*",
|
||||||
"@tauri-apps/api": "^1.5.3"
|
"@tauri-apps/api": "^1.4.0"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/tidalcycles/strudel#readme"
|
"homepage": "https://github.com/tidalcycles/strudel#readme"
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
# @strudel.cycles/embed
|
# @strudel.cycles/embed
|
||||||
|
|
||||||
**DEPRECATION NOTE**: This package is old and won't get any updates! The newer version goes by the name of [@strudel/embed](https://www.npmjs.com/package/@strudel/embed).
|
|
||||||
|
|
||||||
This package contains a embeddable web component for the Strudel REPL.
|
This package contains a embeddable web component for the Strudel REPL.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/embed",
|
"name": "@strudel.cycles/embed",
|
||||||
"version": "0.10.0",
|
"version": "0.2.0",
|
||||||
"description": "Embeddable Web Component to load a Strudel REPL into an iframe",
|
"description": "Embeddable Web Component to load a Strudel REPL into an iframe",
|
||||||
"main": "embed.js",
|
"main": "embed.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -12,12 +12,6 @@ await initHydra();
|
|||||||
|
|
||||||
Then you can use hydra below!
|
Then you can use hydra below!
|
||||||
|
|
||||||
### options
|
|
||||||
|
|
||||||
You can also pass options to the `initHydra` function. These can be used to set [hydra options](https://github.com/hydra-synth/hydra-synth#api) + these strudel specific options:
|
|
||||||
|
|
||||||
- `feedStrudel`: sends the strudel canvas to `s0`. The strudel canvas is used to draw `pianoroll`, `spiral`, `scope` etc..
|
|
||||||
|
|
||||||
## Usage via npm
|
## Usage via npm
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -1,39 +1,14 @@
|
|||||||
import { getDrawContext } from '@strudel.cycles/core';
|
import { getDrawContext } from '@strudel.cycles/core';
|
||||||
|
|
||||||
let latestOptions;
|
export async function initHydra() {
|
||||||
|
|
||||||
function appendCanvas(c) {
|
|
||||||
const { canvas: testCanvas } = getDrawContext();
|
|
||||||
c.canvas.id = 'hydra-canvas';
|
|
||||||
c.canvas.style.position = 'fixed';
|
|
||||||
c.canvas.style.top = '0px';
|
|
||||||
testCanvas.after(c.canvas);
|
|
||||||
return testCanvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function initHydra(options = {}) {
|
|
||||||
// reset if options have changed since last init
|
|
||||||
if (latestOptions && JSON.stringify(latestOptions) !== JSON.stringify(options)) {
|
|
||||||
document.getElementById('hydra-canvas').remove();
|
|
||||||
}
|
|
||||||
latestOptions = options;
|
|
||||||
//load and init hydra
|
|
||||||
if (!document.getElementById('hydra-canvas')) {
|
if (!document.getElementById('hydra-canvas')) {
|
||||||
console.log('reinit..');
|
const { canvas: testCanvas } = getDrawContext();
|
||||||
const {
|
await import('https://unpkg.com/hydra-synth');
|
||||||
src = 'https://unpkg.com/hydra-synth',
|
const hydraCanvas = testCanvas.cloneNode(true);
|
||||||
feedStrudel = false,
|
hydraCanvas.id = 'hydra-canvas';
|
||||||
...hydraConfig
|
testCanvas.after(hydraCanvas);
|
||||||
} = { detectAudio: false, ...options };
|
new Hydra({ canvas: hydraCanvas, detectAudio: false });
|
||||||
|
s0.init({ src: testCanvas });
|
||||||
await import(/* @vite-ignore */ src);
|
|
||||||
const hydra = new Hydra(hydraConfig);
|
|
||||||
if (feedStrudel) {
|
|
||||||
const { canvas } = getDrawContext();
|
|
||||||
canvas.style.display = 'none';
|
|
||||||
hydra.synth.s0.init({ src: canvas });
|
|
||||||
}
|
|
||||||
appendCanvas(hydra);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/hydra",
|
"name": "@strudel/hydra",
|
||||||
"version": "0.10.0",
|
"version": "0.9.0",
|
||||||
"description": "Hydra integration for strudel",
|
"description": "Hydra integration for strudel",
|
||||||
"main": "hydra.mjs",
|
"main": "hydra.mjs",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
@@ -38,6 +38,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"pkg": "^5.8.1",
|
"pkg": "^5.8.1",
|
||||||
"vite": "^5.0.10"
|
"vite": "^4.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default defineConfig({
|
|||||||
lib: {
|
lib: {
|
||||||
entry: resolve(__dirname, 'hydra.mjs'),
|
entry: resolve(__dirname, 'hydra.mjs'),
|
||||||
formats: ['es', 'cjs'],
|
formats: ['es', 'cjs'],
|
||||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: [...Object.keys(dependencies)],
|
external: [...Object.keys(dependencies)],
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
# @strudel.cycles/midi
|
# @strudel.cycles/midi
|
||||||
|
|
||||||
**DEPRECATION NOTE**: This package is old and won't get any updates! The newer version goes by the name of [@strudel/midi](https://www.npmjs.com/package/@strudel/midi).
|
|
||||||
|
|
||||||
This package adds midi functionality to strudel Patterns.
|
This package adds midi functionality to strudel Patterns.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/midi",
|
"name": "@strudel.cycles/midi",
|
||||||
"version": "0.10.0",
|
"version": "0.9.0",
|
||||||
"description": "Midi API for strudel",
|
"description": "Midi API for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
@@ -31,9 +31,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "workspace:*",
|
"@strudel.cycles/core": "workspace:*",
|
||||||
"@strudel.cycles/webaudio": "workspace:*",
|
"@strudel.cycles/webaudio": "workspace:*",
|
||||||
"webmidi": "^3.1.8"
|
"webmidi": "^3.1.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^5.0.10"
|
"vite": "^4.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default defineConfig({
|
|||||||
lib: {
|
lib: {
|
||||||
entry: resolve(__dirname, 'index.mjs'),
|
entry: resolve(__dirname, 'index.mjs'),
|
||||||
formats: ['es', 'cjs'],
|
formats: ['es', 'cjs'],
|
||||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: [...Object.keys(dependencies)],
|
external: [...Object.keys(dependencies)],
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
# @strudel.cycles/mini
|
# @strudel.cycles/mini
|
||||||
|
|
||||||
**DEPRECATION NOTE**: This package is old and won't get any updates! The newer version goes by the name of [@strudel/mini](https://www.npmjs.com/package/@strudel/mini).
|
|
||||||
|
|
||||||
This package contains the mini notation parser and pattern generator.
|
This package contains the mini notation parser and pattern generator.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ function peg$parse(input, options) {
|
|||||||
var peg$r0 = /^[1-9]/;
|
var peg$r0 = /^[1-9]/;
|
||||||
var peg$r1 = /^[eE]/;
|
var peg$r1 = /^[eE]/;
|
||||||
var peg$r2 = /^[0-9]/;
|
var peg$r2 = /^[0-9]/;
|
||||||
var peg$r3 = /^[ \n\r\t\xA0]/;
|
var peg$r3 = /^[ \n\r\t]/;
|
||||||
var peg$r4 = /^[0-9~]/;
|
var peg$r4 = /^[0-9~]/;
|
||||||
var peg$r5 = /^[^\n]/;
|
var peg$r5 = /^[^\n]/;
|
||||||
var peg$r6 = /^[a-z\xB5\xDF-\xF6\xF8-\xFF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137-\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148-\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C-\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA-\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9-\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC-\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF-\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F-\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02AF\u0371\u0373\u0377\u037B-\u037D\u0390\u03AC-\u03CE\u03D0-\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB-\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE-\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0560-\u0588\u10D0-\u10FA\u10FD-\u10FF\u13F8-\u13FD\u1C80-\u1C88\u1D00-\u1D2B\u1D6B-\u1D77\u1D79-\u1D9A\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6-\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FC7\u1FD0-\u1FD3\u1FD6-\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6-\u1FF7\u210A\u210E-\u210F\u2113\u212F\u2134\u2139\u213C-\u213D\u2146-\u2149\u214E\u2184\u2C30-\u2C5E\u2C61\u2C65-\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73-\u2C74\u2C76-\u2C7B\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3-\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F\uA771-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7AF\uA7B5\uA7B7\uA7B9\uA7FA\uAB30-\uAB5A\uAB60-\uAB65\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A]/;
|
var peg$r6 = /^[a-z\xB5\xDF-\xF6\xF8-\xFF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137-\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148-\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C-\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA-\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9-\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC-\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF-\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F-\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02AF\u0371\u0373\u0377\u037B-\u037D\u0390\u03AC-\u03CE\u03D0-\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB-\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE-\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0560-\u0588\u10D0-\u10FA\u10FD-\u10FF\u13F8-\u13FD\u1C80-\u1C88\u1D00-\u1D2B\u1D6B-\u1D77\u1D79-\u1D9A\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6-\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FC7\u1FD0-\u1FD3\u1FD6-\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6-\u1FF7\u210A\u210E-\u210F\u2113\u212F\u2134\u2139\u213C-\u213D\u2146-\u2149\u214E\u2184\u2C30-\u2C5E\u2C61\u2C65-\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73-\u2C74\u2C76-\u2C7B\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3-\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F\uA771-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7AF\uA7B5\uA7B7\uA7B9\uA7FA\uAB30-\uAB5A\uAB60-\uAB65\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A]/;
|
||||||
@@ -238,7 +238,7 @@ function peg$parse(input, options) {
|
|||||||
var peg$e6 = peg$literalExpectation("0", false);
|
var peg$e6 = peg$literalExpectation("0", false);
|
||||||
var peg$e7 = peg$classExpectation([["0", "9"]], false, false);
|
var peg$e7 = peg$classExpectation([["0", "9"]], false, false);
|
||||||
var peg$e8 = peg$otherExpectation("whitespace");
|
var peg$e8 = peg$otherExpectation("whitespace");
|
||||||
var peg$e9 = peg$classExpectation([" ", "\n", "\r", "\t", "\xA0"], false, false);
|
var peg$e9 = peg$classExpectation([" ", "\n", "\r", "\t"], false, false);
|
||||||
var peg$e10 = peg$literalExpectation(",", false);
|
var peg$e10 = peg$literalExpectation(",", false);
|
||||||
var peg$e11 = peg$literalExpectation("|", false);
|
var peg$e11 = peg$literalExpectation("|", false);
|
||||||
var peg$e12 = peg$literalExpectation("\"", false);
|
var peg$e12 = peg$literalExpectation("\"", false);
|
||||||
@@ -292,7 +292,7 @@ function peg$parse(input, options) {
|
|||||||
var peg$f3 = function(s) { return s };
|
var peg$f3 = function(s) { return s };
|
||||||
var peg$f4 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; };
|
var peg$f4 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; };
|
||||||
var peg$f5 = function(a) { return a };
|
var peg$f5 = function(a) { return a };
|
||||||
var peg$f6 = function(s) { s.arguments_.alignment = 'polymeter_slowcat'; return s; };
|
var peg$f6 = function(s) { s.arguments_.alignment = 'slowcat'; return s; };
|
||||||
var peg$f7 = function(a) { return x => x.options_['weight'] = a };
|
var peg$f7 = function(a) { return x => x.options_['weight'] = a };
|
||||||
var peg$f8 = function(a) { return x => x.options_['reps'] = a };
|
var peg$f8 = function(a) { return x => x.options_['reps'] = a };
|
||||||
var peg$f9 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) };
|
var peg$f9 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) };
|
||||||
@@ -1073,7 +1073,7 @@ function peg$parse(input, options) {
|
|||||||
}
|
}
|
||||||
if (s2 !== peg$FAILED) {
|
if (s2 !== peg$FAILED) {
|
||||||
s3 = peg$parsews();
|
s3 = peg$parsews();
|
||||||
s4 = peg$parsepolymeter_stack();
|
s4 = peg$parsesequence();
|
||||||
if (s4 !== peg$FAILED) {
|
if (s4 !== peg$FAILED) {
|
||||||
s5 = peg$parsews();
|
s5 = peg$parsews();
|
||||||
if (input.charCodeAt(peg$currPos) === 62) {
|
if (input.charCodeAt(peg$currPos) === 62) {
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ DIGIT = [0-9]
|
|||||||
|
|
||||||
// ------------------ delimiters ---------------------------
|
// ------------------ delimiters ---------------------------
|
||||||
|
|
||||||
ws "whitespace" = [ \n\r\t\u00A0]*
|
ws "whitespace" = [ \n\r\t]*
|
||||||
comma = ws "," ws
|
comma = ws "," ws
|
||||||
pipe = ws "|" ws
|
pipe = ws "|" ws
|
||||||
quote = '"' / "'"
|
quote = '"' / "'"
|
||||||
@@ -119,8 +119,8 @@ polymeter_steps = "%"a:slice
|
|||||||
|
|
||||||
// define a step-per-cycle timeline e.g <1 3 [3 5]>. We simply defer to a sequence and
|
// define a step-per-cycle timeline e.g <1 3 [3 5]>. We simply defer to a sequence and
|
||||||
// change the alignment to slowcat
|
// change the alignment to slowcat
|
||||||
slow_sequence = ws "<" ws s:polymeter_stack ws ">" ws
|
slow_sequence = ws "<" ws s:sequence ws ">" ws
|
||||||
{ s.arguments_.alignment = 'polymeter_slowcat'; return s; }
|
{ s.arguments_.alignment = 'slowcat'; return s; }
|
||||||
|
|
||||||
// a slice is either a single step or a sub cycle
|
// a slice is either a single step or a sub cycle
|
||||||
slice = step / sub_cycle / polymeter / slow_sequence
|
slice = step / sub_cycle / polymeter / slow_sequence
|
||||||
|
|||||||
+12
-18
@@ -42,7 +42,7 @@ const applyOptions = (parent, enter) => (pat, i) => {
|
|||||||
}
|
}
|
||||||
case 'tail': {
|
case 'tail': {
|
||||||
const friend = enter(op.arguments_.element);
|
const friend = enter(op.arguments_.element);
|
||||||
pat = pat.fmap((a) => (b) => (Array.isArray(a) ? [...a, b] : [a, b])).appLeft(friend);
|
pat = pat.fmap((a) => (b) => Array.isArray(a) ? [...a, b] : [a, b]).appLeft(friend);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'range': {
|
case 'range': {
|
||||||
@@ -91,10 +91,6 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
|
|||||||
if (alignment === 'stack') {
|
if (alignment === 'stack') {
|
||||||
return strudel.stack(...children);
|
return strudel.stack(...children);
|
||||||
}
|
}
|
||||||
if (alignment === 'polymeter_slowcat') {
|
|
||||||
const aligned = children.map((child) => child._slow(strudel.Fraction(child.__weight ?? 1)));
|
|
||||||
return strudel.stack(...aligned);
|
|
||||||
}
|
|
||||||
if (alignment === 'polymeter') {
|
if (alignment === 'polymeter') {
|
||||||
// polymeter
|
// polymeter
|
||||||
const stepsPerCycle = ast.arguments_.stepsPerCycle
|
const stepsPerCycle = ast.arguments_.stepsPerCycle
|
||||||
@@ -108,9 +104,15 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
|
|||||||
return strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
|
return strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
|
||||||
}
|
}
|
||||||
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
|
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
|
||||||
|
if (!weightedChildren && alignment === 'slowcat') {
|
||||||
|
return strudel.slowcat(...children);
|
||||||
|
}
|
||||||
if (weightedChildren) {
|
if (weightedChildren) {
|
||||||
const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0);
|
const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0);
|
||||||
const pat = strudel.timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
|
const pat = strudel.timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
|
||||||
|
if (alignment === 'slowcat') {
|
||||||
|
return pat._slow(weightSum); // timecat + slow
|
||||||
|
}
|
||||||
pat.__weight = weightSum;
|
pat.__weight = weightSum;
|
||||||
return pat;
|
return pat;
|
||||||
}
|
}
|
||||||
@@ -158,19 +160,11 @@ export const getLeafLocation = (code, leaf, globalOffset = 0) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// takes quoted mini string, returns ast
|
// takes quoted mini string, returns ast
|
||||||
export const mini2ast = (code, start, userCode) => {
|
export const mini2ast = (code) => krill.parse(code);
|
||||||
try {
|
|
||||||
return krill.parse(code);
|
|
||||||
} catch (error) {
|
|
||||||
const region = [error.location.start.offset + start, error.location.end.offset + start];
|
|
||||||
const line = userCode.slice(0, region[0]).split('\n').length;
|
|
||||||
throw new Error(`[mini] parse error at line ${line}: ${error.message}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// takes quoted mini string, returns all nodes that are leaves
|
// takes quoted mini string, returns all nodes that are leaves
|
||||||
export const getLeaves = (code, start, userCode) => {
|
export const getLeaves = (code) => {
|
||||||
const ast = mini2ast(code, start, userCode);
|
const ast = mini2ast(code);
|
||||||
let leaves = [];
|
let leaves = [];
|
||||||
patternifyAST(
|
patternifyAST(
|
||||||
ast,
|
ast,
|
||||||
@@ -186,8 +180,8 @@ export const getLeaves = (code, start, userCode) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// takes quoted mini string, returns locations [fromCol,toCol] of all leaf nodes
|
// takes quoted mini string, returns locations [fromCol,toCol] of all leaf nodes
|
||||||
export const getLeafLocations = (code, start = 0, userCode) => {
|
export const getLeafLocations = (code, offset = 0) => {
|
||||||
return getLeaves(code, start, userCode).map((l) => getLeafLocation(code, l, start));
|
return getLeaves(code).map((l) => getLeafLocation(code, l, offset));
|
||||||
};
|
};
|
||||||
|
|
||||||
// mini notation only (wraps in "")
|
// mini notation only (wraps in "")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/mini",
|
"name": "@strudel.cycles/mini",
|
||||||
"version": "0.10.0",
|
"version": "0.9.0",
|
||||||
"description": "Mini notation for strudel",
|
"description": "Mini notation for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"peggy": "^3.0.2",
|
"peggy": "^3.0.2",
|
||||||
"vite": "^5.0.10",
|
"vite": "^4.3.3",
|
||||||
"vitest": "^1.1.0"
|
"vitest": "^0.33.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default defineConfig({
|
|||||||
lib: {
|
lib: {
|
||||||
entry: resolve(__dirname, 'index.mjs'),
|
entry: resolve(__dirname, 'index.mjs'),
|
||||||
formats: ['es', 'cjs'],
|
formats: ['es', 'cjs'],
|
||||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: [...Object.keys(dependencies)],
|
external: [...Object.keys(dependencies)],
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
# @strudel.cycles/osc
|
# @strudel.cycles/osc
|
||||||
|
|
||||||
**DEPRECATION NOTE**: This package is old and won't get any updates! The newer version goes by the name of [@strudel/osc](https://www.npmjs.com/package/@strudel/osc).
|
|
||||||
|
|
||||||
OSC output for strudel patterns! Currently only tested with super collider / super dirt.
|
OSC output for strudel patterns! Currently only tested with super collider / super dirt.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function connect() {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Sends each hap as an OSC message, which can be picked up by SuperCollider or any other OSC-enabled software.
|
* Sends each hap as an OSC message, which can be picked up by SuperCollider or any other OSC-enabled software.
|
||||||
* For more info, read [MIDI & OSC in the docs](https://strudel.cc/learn/input-output/)
|
* For more info, read [MIDI & OSC in the docs](https://strudel.cc/learn/input-output)
|
||||||
*
|
*
|
||||||
* @name osc
|
* @name osc
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel.cycles/osc",
|
"name": "@strudel.cycles/osc",
|
||||||
"version": "0.10.0",
|
"version": "0.9.0",
|
||||||
"description": "OSC messaging for strudel",
|
"description": "OSC messaging for strudel",
|
||||||
"main": "osc.mjs",
|
"main": "osc.mjs",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
@@ -41,6 +41,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"pkg": "^5.8.1",
|
"pkg": "^5.8.1",
|
||||||
"vite": "^5.0.10"
|
"vite": "^4.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default defineConfig({
|
|||||||
lib: {
|
lib: {
|
||||||
entry: resolve(__dirname, 'osc.mjs'),
|
entry: resolve(__dirname, 'osc.mjs'),
|
||||||
formats: ['es', 'cjs'],
|
formats: ['es', 'cjs'],
|
||||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' })[ext],
|
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: [...Object.keys(dependencies)],
|
external: [...Object.keys(dependencies)],
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
examples
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user