mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-31 21:27:07 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f46cf02819 |
@@ -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,112 +1,37 @@
|
|||||||
import { closeBrackets } from '@codemirror/autocomplete';
|
import { defaultKeymap } from '@codemirror/commands';
|
||||||
// import { search, highlightSelectionMatches } from '@codemirror/search';
|
|
||||||
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 { EditorState } from '@codemirror/state';
|
||||||
import {
|
import { EditorView, highlightActiveLineGutter, keymap, lineNumbers } from '@codemirror/view';
|
||||||
EditorView,
|
import { Drawer, repl } from '@strudel.cycles/core';
|
||||||
highlightActiveLineGutter,
|
import { flashField, flash } from './flash.mjs';
|
||||||
highlightActiveLine,
|
import { highlightExtension, highlightMiniLocations } from './highlight.mjs';
|
||||||
keymap,
|
import { oneDark } from './themes/one-dark';
|
||||||
lineNumbers,
|
|
||||||
drawSelection,
|
|
||||||
} from '@codemirror/view';
|
|
||||||
import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core';
|
|
||||||
import { isAutoCompletionEnabled } from './autocomplete.mjs';
|
|
||||||
import { isTooltipEnabled } from './tooltip.mjs';
|
|
||||||
import { flash, isFlashEnabled } from './flash.mjs';
|
|
||||||
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
|
|
||||||
import { keybindings } from './keybindings.mjs';
|
|
||||||
import { initTheme, activateTheme, theme } from './themes.mjs';
|
|
||||||
import { updateWidgets, sliderPlugin } from './slider.mjs';
|
|
||||||
import { persistentAtom } from '@nanostores/persistent';
|
|
||||||
|
|
||||||
const extensions = {
|
|
||||||
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
|
|
||||||
isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []),
|
|
||||||
theme,
|
|
||||||
isAutoCompletionEnabled,
|
|
||||||
isTooltipEnabled,
|
|
||||||
isPatternHighlightingEnabled,
|
|
||||||
isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []),
|
|
||||||
isFlashEnabled,
|
|
||||||
keybindings,
|
|
||||||
};
|
|
||||||
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, theme = oneDark, root }) {
|
||||||
const settings = codemirrorSettings.get();
|
|
||||||
const initialSettings = Object.keys(compartments).map((key) =>
|
|
||||||
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
|
|
||||||
);
|
|
||||||
initTheme(settings.theme);
|
|
||||||
let state = EditorState.create({
|
let state = EditorState.create({
|
||||||
doc: initialCode,
|
doc: initialCode,
|
||||||
extensions: [
|
extensions: [
|
||||||
/* search(),
|
theme,
|
||||||
highlightSelectionMatches(), */
|
|
||||||
...initialSettings,
|
|
||||||
javascript(),
|
javascript(),
|
||||||
sliderPlugin,
|
lineNumbers(),
|
||||||
// indentOnInput(), // works without. already brought with javascript extension?
|
highlightExtension,
|
||||||
// bracketMatching(), // does not do anything
|
highlightActiveLineGutter(),
|
||||||
closeBrackets(),
|
|
||||||
syntaxHighlighting(defaultHighlightStyle),
|
syntaxHighlighting(defaultHighlightStyle),
|
||||||
history(),
|
keymap.of(defaultKeymap),
|
||||||
|
flashField,
|
||||||
EditorView.updateListener.of((v) => onChange(v)),
|
EditorView.updateListener.of((v) => onChange(v)),
|
||||||
drawSelection({ cursorBlinkRate: 0 }),
|
keymap.of([
|
||||||
Prec.highest(
|
{
|
||||||
keymap.of([
|
key: 'Ctrl-Enter',
|
||||||
{
|
run: () => onEvaluate(),
|
||||||
key: 'Ctrl-Enter',
|
|
||||||
run: () => onEvaluate?.(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'Alt-Enter',
|
|
||||||
run: () => onEvaluate?.(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'Ctrl-.',
|
|
||||||
run: () => onStop?.(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'Alt-.',
|
|
||||||
run: (_, e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
onStop?.();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
/* {
|
|
||||||
key: 'Ctrl-Shift-.',
|
|
||||||
run: () => (onPanic ? onPanic() : onStop?.()),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'Ctrl-Shift-Enter',
|
key: 'Ctrl-.',
|
||||||
run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()),
|
run: () => onStop(),
|
||||||
}, */
|
},
|
||||||
]),
|
]),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,76 +43,45 @@ 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, ...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.miniLocations = [];
|
|
||||||
this.widgets = [];
|
|
||||||
this.painters = [];
|
|
||||||
this.drawTime = drawTime;
|
|
||||||
this.onDraw = onDraw;
|
|
||||||
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);
|
||||||
this.highlight(currentFrame, time);
|
this.highlight(currentFrame, time);
|
||||||
this.onDraw?.(haps, time, currentFrame, this.painters);
|
onDraw?.(haps, time, currentFrame);
|
||||||
}, drawTime);
|
}, drawTime);
|
||||||
|
|
||||||
// this approach does not work with multiple repls on screen
|
const prebaked = prebake();
|
||||||
// TODO: refactor onPaint usages + find fix, maybe remove painters here?
|
prebaked.then(async () => {
|
||||||
Pattern.prototype.onPaint = function (onPaint) {
|
if (!onDraw) {
|
||||||
self.painters.push(onPaint);
|
return;
|
||||||
return this;
|
}
|
||||||
};
|
const { scheduler, evaluate } = await this.repl;
|
||||||
|
// draw first frame instantly
|
||||||
this.prebaked = prebake();
|
prebaked.then(async () => {
|
||||||
autodraw && this.drawFirstFrame();
|
await evaluate(this.code, false);
|
||||||
|
this.drawer.invalidate(scheduler);
|
||||||
|
onDraw?.(this.drawer.visibleHaps, 0, []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
this.repl = repl({
|
this.repl = repl({
|
||||||
...replOptions,
|
...replOptions,
|
||||||
onToggle: (started) => {
|
onToggle: async (started) => {
|
||||||
replOptions?.onToggle?.(started);
|
replOptions?.onToggle?.(started);
|
||||||
|
const { scheduler } = await this.repl;
|
||||||
if (started) {
|
if (started) {
|
||||||
this.adjustDrawTime();
|
this.drawer.start(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, []);
|
|
||||||
cleanupDraw(false);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeEval: async () => {
|
beforeEval: async () => {
|
||||||
cleanupDraw();
|
await prebaked;
|
||||||
this.painters = [];
|
|
||||||
await this.prebaked;
|
|
||||||
await replOptions?.beforeEval?.();
|
|
||||||
},
|
},
|
||||||
afterEval: (options) => {
|
afterEval: (options) => {
|
||||||
// remember for when highlighting is toggled on
|
|
||||||
this.miniLocations = options.meta?.miniLocations;
|
|
||||||
this.widgets = options.meta?.widgets;
|
|
||||||
updateWidgets(this.editor, this.widgets);
|
|
||||||
updateMiniLocations(this.editor, this.miniLocations);
|
|
||||||
replOptions?.afterEval?.(options);
|
replOptions?.afterEval?.(options);
|
||||||
this.adjustDrawTime();
|
|
||||||
this.drawer.invalidate();
|
this.drawer.invalidate();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -195,145 +89,25 @@ export class StrudelMirror {
|
|||||||
root,
|
root,
|
||||||
initialCode,
|
initialCode,
|
||||||
onChange: (v) => {
|
onChange: (v) => {
|
||||||
if (v.docChanged) {
|
this.code = v.state.doc.toString();
|
||||||
this.code = v.state.doc.toString();
|
|
||||||
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() {
|
|
||||||
if (!this.onDraw) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// draw first frame instantly
|
|
||||||
await this.prebaked;
|
|
||||||
try {
|
|
||||||
await this.repl.evaluate(this.code, false);
|
|
||||||
this.drawer.invalidate(this.repl.scheduler, -0.001);
|
|
||||||
// draw at -0.001 to avoid haps at 0 to be visualized as active
|
|
||||||
this.onDraw?.(this.drawer.visibleHaps, -0.001, [], this.painters);
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('first frame could not be painted');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
async evaluate() {
|
async evaluate() {
|
||||||
|
const { evaluate } = await this.repl;
|
||||||
this.flash();
|
this.flash();
|
||||||
await this.repl.evaluate(this.code);
|
await evaluate(this.code);
|
||||||
}
|
}
|
||||||
async stop() {
|
async stop() {
|
||||||
this.repl.scheduler.stop();
|
const { scheduler } = await this.repl;
|
||||||
}
|
scheduler.stop();
|
||||||
async toggle() {
|
|
||||||
if (this.repl.scheduler.started) {
|
|
||||||
this.repl.stop();
|
|
||||||
} else {
|
|
||||||
this.evaluate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
flash(ms) {
|
flash(ms) {
|
||||||
flash(this.editor, ms);
|
flash(this.editor, ms);
|
||||||
}
|
}
|
||||||
highlight(haps, time) {
|
highlight(haps, time) {
|
||||||
highlightMiniLocations(this.editor, time, haps);
|
highlightMiniLocations(this.editor.view, time, haps);
|
||||||
}
|
|
||||||
setFontSize(size) {
|
|
||||||
this.root.style.fontSize = size + 'px';
|
|
||||||
}
|
|
||||||
setFontFamily(family) {
|
|
||||||
this.root.style.fontFamily = family;
|
|
||||||
const scroller = this.root.querySelector('.cm-scroller');
|
|
||||||
if (scroller) {
|
|
||||||
scroller.style.fontFamily = family;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reconfigureExtension(key, value) {
|
|
||||||
if (!extensions[key]) {
|
|
||||||
console.warn(`extension ${key} is not known`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
value = parseBooleans(value);
|
|
||||||
const newValue = extensions[key](value, this);
|
|
||||||
this.editor.dispatch({
|
|
||||||
effects: compartments[key].reconfigure(newValue),
|
|
||||||
});
|
|
||||||
if (key === 'theme') {
|
|
||||||
activateTheme(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setLineWrappingEnabled(enabled) {
|
|
||||||
this.reconfigureExtension('isLineWrappingEnabled', enabled);
|
|
||||||
}
|
|
||||||
setLineNumbersDisplayed(enabled) {
|
|
||||||
this.reconfigureExtension('isLineNumbersDisplayed', enabled);
|
|
||||||
}
|
|
||||||
setTheme(theme) {
|
|
||||||
this.reconfigureExtension('theme', theme);
|
|
||||||
}
|
|
||||||
setAutocompletionEnabled(enabled) {
|
|
||||||
this.reconfigureExtension('isAutoCompletionEnabled', enabled);
|
|
||||||
}
|
|
||||||
updateSettings(settings) {
|
|
||||||
this.setFontSize(settings.fontSize);
|
|
||||||
this.setFontFamily(settings.fontFamily);
|
|
||||||
for (let key in extensions) {
|
|
||||||
this.reconfigureExtension(key, settings[key]);
|
|
||||||
}
|
|
||||||
const updated = { ...codemirrorSettings.get(), ...settings };
|
|
||||||
codemirrorSettings.set(updated);
|
|
||||||
}
|
|
||||||
changeSetting(key, value) {
|
|
||||||
if (extensions[key]) {
|
|
||||||
this.reconfigureExtension(key, value);
|
|
||||||
return;
|
|
||||||
} else if (key === 'fontFamily') {
|
|
||||||
this.setFontFamily(value);
|
|
||||||
} else if (key === 'fontSize') {
|
|
||||||
this.setFontSize(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setCode(code) {
|
|
||||||
const changes = { from: 0, to: this.editor.state.doc.length, insert: code };
|
|
||||||
this.editor.dispatch({ changes });
|
|
||||||
}
|
|
||||||
clear() {
|
|
||||||
this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseBooleans(value) {
|
|
||||||
return { true: true, false: false }[value] ?? value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function to generate repl ids
|
|
||||||
function s4() {
|
|
||||||
return Math.floor((1 + Math.random()) * 0x10000)
|
|
||||||
.toString(16)
|
|
||||||
.substring(1);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -33,5 +33,3 @@ export const flash = (view, ms = 200) => {
|
|||||||
view.dispatch({ effects: setFlash.of(false) });
|
view.dispatch({ effects: setFlash.of(false) });
|
||||||
}, ms);
|
}, ms);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isFlashEnabled = (on) => (on ? flashField : []);
|
|
||||||
|
|||||||
@@ -124,12 +124,3 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights];
|
export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights];
|
||||||
|
|
||||||
export const isPatternHighlightingEnabled = (on, config) => {
|
|
||||||
on &&
|
|
||||||
config &&
|
|
||||||
setTimeout(() => {
|
|
||||||
updateMiniLocations(config.editor, config.miniLocations);
|
|
||||||
}, 100);
|
|
||||||
return on ? highlightExtension : [];
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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,31 +0,0 @@
|
|||||||
import { Prec } from '@codemirror/state';
|
|
||||||
import { keymap, ViewPlugin } from '@codemirror/view';
|
|
||||||
// import { searchKeymap } from '@codemirror/search';
|
|
||||||
import { emacs } from '@replit/codemirror-emacs';
|
|
||||||
import { vim } from '@replit/codemirror-vim';
|
|
||||||
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
|
||||||
import { defaultKeymap, historyKeymap } from '@codemirror/commands';
|
|
||||||
|
|
||||||
const vscodePlugin = ViewPlugin.fromClass(
|
|
||||||
class {
|
|
||||||
constructor() {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: () => {
|
|
||||||
return Prec.highest(keymap.of([...vscodeKeymap]));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
|
|
||||||
|
|
||||||
const keymaps = {
|
|
||||||
vim,
|
|
||||||
emacs,
|
|
||||||
vscode: vscodeExtension,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function keybindings(name) {
|
|
||||||
const active = keymaps[name];
|
|
||||||
return [keymap.of(defaultKeymap), keymap.of(historyKeymap), active ? active() : []];
|
|
||||||
// keymap.of(searchKeymap),
|
|
||||||
}
|
|
||||||
@@ -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,15 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.11.1",
|
"@codemirror/commands": "^6.2.4",
|
||||||
"@codemirror/commands": "^6.3.3",
|
"@codemirror/lang-javascript": "^6.1.7",
|
||||||
"@codemirror/lang-javascript": "^6.2.1",
|
"@codemirror/language": "^6.6.0",
|
||||||
"@codemirror/language": "^6.10.0",
|
"@codemirror/state": "^6.2.0",
|
||||||
"@codemirror/search": "^6.5.5",
|
"@codemirror/view": "^6.10.0",
|
||||||
"@codemirror/state": "^6.4.0",
|
"@lezer/highlight": "^1.1.4",
|
||||||
"@codemirror/view": "^6.23.0",
|
"@strudel.cycles/core": "workspace:*"
|
||||||
"@lezer/highlight": "^1.2.0",
|
|
||||||
"@nanostores/persistent": "^0.9.1",
|
|
||||||
"@replit/codemirror-emacs": "^6.0.1",
|
|
||||||
"@replit/codemirror-vim": "^6.1.0",
|
|
||||||
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
|
||||||
"@strudel.cycles/core": "workspace:*",
|
|
||||||
"@uiw/codemirror-themes": "^4.21.21",
|
|
||||||
"@uiw/codemirror-themes-all": "^4.21.21",
|
|
||||||
"nanostores": "^0.9.5"
|
|
||||||
},
|
},
|
||||||
"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);
|
||||||
|
|||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
import { EditorView } from '@codemirror/view';
|
||||||
|
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
|
|
||||||
|
// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors
|
||||||
|
|
||||||
|
const chalky = '#e5c07b',
|
||||||
|
coral = '#e06c75',
|
||||||
|
cyan = '#56b6c2',
|
||||||
|
invalid = '#ffffff',
|
||||||
|
ivory = '#abb2bf',
|
||||||
|
stone = '#7d8799', // Brightened compared to original to increase contrast
|
||||||
|
malibu = '#61afef',
|
||||||
|
sage = '#98c379',
|
||||||
|
whiskey = '#d19a66',
|
||||||
|
violet = '#c678dd',
|
||||||
|
darkBackground = '#21252b',
|
||||||
|
highlightBackground = '#2c313a',
|
||||||
|
background = '#282c34',
|
||||||
|
tooltipBackground = '#353a42',
|
||||||
|
selection = '#3E4451',
|
||||||
|
cursor = '#528bff';
|
||||||
|
|
||||||
|
/// The colors used in the theme, as CSS color strings.
|
||||||
|
export const color = {
|
||||||
|
chalky,
|
||||||
|
coral,
|
||||||
|
cyan,
|
||||||
|
invalid,
|
||||||
|
ivory,
|
||||||
|
stone,
|
||||||
|
malibu,
|
||||||
|
sage,
|
||||||
|
whiskey,
|
||||||
|
violet,
|
||||||
|
darkBackground,
|
||||||
|
highlightBackground,
|
||||||
|
background,
|
||||||
|
tooltipBackground,
|
||||||
|
selection,
|
||||||
|
cursor,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The editor theme styles for One Dark.
|
||||||
|
export const oneDarkTheme = EditorView.theme(
|
||||||
|
{
|
||||||
|
'&': {
|
||||||
|
color: ivory,
|
||||||
|
backgroundColor: background,
|
||||||
|
},
|
||||||
|
|
||||||
|
'.cm-content': {
|
||||||
|
caretColor: cursor,
|
||||||
|
},
|
||||||
|
|
||||||
|
'.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
|
||||||
|
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':
|
||||||
|
{ backgroundColor: selection },
|
||||||
|
|
||||||
|
'.cm-panels': { backgroundColor: darkBackground, color: ivory },
|
||||||
|
'.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
|
||||||
|
'.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
|
||||||
|
|
||||||
|
'.cm-searchMatch': {
|
||||||
|
backgroundColor: '#72a1ff59',
|
||||||
|
outline: '1px solid #457dff',
|
||||||
|
},
|
||||||
|
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||||
|
backgroundColor: '#6199ff2f',
|
||||||
|
},
|
||||||
|
|
||||||
|
'.cm-activeLine': { backgroundColor: '#6699ff0b' },
|
||||||
|
'.cm-selectionMatch': { backgroundColor: '#aafe661a' },
|
||||||
|
|
||||||
|
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||||
|
backgroundColor: '#bad0f847',
|
||||||
|
},
|
||||||
|
|
||||||
|
'.cm-gutters': {
|
||||||
|
backgroundColor: background,
|
||||||
|
color: stone,
|
||||||
|
border: 'none',
|
||||||
|
},
|
||||||
|
|
||||||
|
'.cm-activeLineGutter': {
|
||||||
|
backgroundColor: highlightBackground,
|
||||||
|
},
|
||||||
|
|
||||||
|
'.cm-foldPlaceholder': {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
color: '#ddd',
|
||||||
|
},
|
||||||
|
|
||||||
|
'.cm-tooltip': {
|
||||||
|
border: 'none',
|
||||||
|
backgroundColor: tooltipBackground,
|
||||||
|
},
|
||||||
|
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||||
|
borderTopColor: 'transparent',
|
||||||
|
borderBottomColor: 'transparent',
|
||||||
|
},
|
||||||
|
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||||
|
borderTopColor: tooltipBackground,
|
||||||
|
borderBottomColor: tooltipBackground,
|
||||||
|
},
|
||||||
|
'.cm-tooltip-autocomplete': {
|
||||||
|
'& > ul > li[aria-selected]': {
|
||||||
|
backgroundColor: highlightBackground,
|
||||||
|
color: ivory,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ dark: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The highlighting style for code in the One Dark theme.
|
||||||
|
export const oneDarkHighlightStyle = HighlightStyle.define([
|
||||||
|
{ tag: t.keyword, color: violet },
|
||||||
|
{ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: coral },
|
||||||
|
{ tag: [t.function(t.variableName), t.labelName], color: malibu },
|
||||||
|
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey },
|
||||||
|
{ tag: [t.definition(t.name), t.separator], color: ivory },
|
||||||
|
{ tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: chalky },
|
||||||
|
{ tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], color: cyan },
|
||||||
|
{ tag: [t.meta, t.comment], color: stone },
|
||||||
|
{ tag: t.strong, fontWeight: 'bold' },
|
||||||
|
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||||
|
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||||
|
{ tag: t.link, color: stone, textDecoration: 'underline' },
|
||||||
|
{ tag: t.heading, fontWeight: 'bold', color: coral },
|
||||||
|
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey },
|
||||||
|
{ tag: [t.processingInstruction, t.string, t.inserted], color: sage },
|
||||||
|
{ tag: t.invalid, color: invalid },
|
||||||
|
]);
|
||||||
|
|
||||||
|
/// Extension to enable the One Dark theme (both the editor theme and
|
||||||
|
/// the highlight style).
|
||||||
|
export const oneDark = [oneDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)];
|
||||||
@@ -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
|
||||||
|
|||||||
+13
-181
@@ -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).
|
||||||
@@ -380,75 +380,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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Phaser audio effect that approximates popular guitar pedals.
|
|
||||||
*
|
|
||||||
* @name phaser
|
|
||||||
* @synonyms ph
|
|
||||||
* @param {number | Pattern} speed speed of modulation
|
|
||||||
* @example
|
|
||||||
* n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
|
|
||||||
* .phaser("<1 2 4 8>")
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
[['phaser', 'phaserdepth', 'phasercenter', 'phasersweep'], 'ph'],
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The frequency sweep range of the lfo for the phaser effect. Defaults to 2000
|
|
||||||
*
|
|
||||||
* @name phasersweep
|
|
||||||
* @synonyms phs
|
|
||||||
* @param {number | Pattern} phasersweep most useful values are between 0 and 4000
|
|
||||||
* @example
|
|
||||||
* n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
|
|
||||||
* .phaser(2).phasersweep("<800 2000 4000>")
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
['phasersweep', 'phs'],
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The center frequency of the phaser in HZ. Defaults to 1000
|
|
||||||
*
|
|
||||||
* @name phasercenter
|
|
||||||
* @synonyms phc
|
|
||||||
* @param {number | Pattern} centerfrequency in HZ
|
|
||||||
* @example
|
|
||||||
* n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
|
|
||||||
* .phaser(2).phasercenter("<800 2000 4000>")
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
['phasercenter', 'phc'],
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The amount the signal is affected by the phaser effect. Defaults to 0.75
|
|
||||||
*
|
|
||||||
* @name phaserdepth
|
|
||||||
* @synonyms phd
|
|
||||||
* @param {number | Pattern} depth number between 0 and 1
|
|
||||||
* @example
|
|
||||||
* n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5)
|
|
||||||
* .phaser(2).phaserdepth("<0 .5 .75 1>")
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
['phaserdepth', 'phd', 'phasdp'], // also a superdirt control
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* choose the channel the pattern is sent to in superdirt
|
* choose the channel the pattern is sent to in superdirt
|
||||||
*
|
*
|
||||||
@@ -481,7 +412,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 +689,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 +822,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'],
|
||||||
@@ -1012,12 +867,7 @@ const generic_params = [
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
['lsize'],
|
['lsize'],
|
||||||
/**
|
// label for pianoroll
|
||||||
* Sets the displayed text for an event on the pianoroll
|
|
||||||
*
|
|
||||||
* @name label
|
|
||||||
* @param {string} label text to display
|
|
||||||
*/
|
|
||||||
['activeLabel'],
|
['activeLabel'],
|
||||||
[['label', 'activeLabel']],
|
[['label', 'activeLabel']],
|
||||||
// ['lfo'],
|
// ['lfo'],
|
||||||
@@ -1181,8 +1031,7 @@ const generic_params = [
|
|||||||
*/
|
*/
|
||||||
['roomfade', 'rfade'],
|
['roomfade', 'rfade'],
|
||||||
/**
|
/**
|
||||||
* Sets the sample to use as an impulse response for the reverb.
|
* Sets the sample to use as an impulse response for the reverb. * * @name iresponse
|
||||||
* @name iresponse
|
|
||||||
* @param {string | Pattern} sample to use as an impulse response
|
* @param {string | Pattern} sample to use as an impulse response
|
||||||
* @synonyms ir
|
* @synonyms ir
|
||||||
* @example
|
* @example
|
||||||
@@ -1285,7 +1134,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 +1152,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'],
|
||||||
@@ -1330,6 +1169,9 @@ const generic_params = [
|
|||||||
*/
|
*/
|
||||||
['tremolodepth', 'tremdp'],
|
['tremolodepth', 'tremdp'],
|
||||||
['tremolorate', 'tremr'],
|
['tremolorate', 'tremr'],
|
||||||
|
// TODO: doesn't seem to do anything
|
||||||
|
['phaserdepth', 'phasdp'],
|
||||||
|
['phaserrate', 'phasr'],
|
||||||
|
|
||||||
['fshift'],
|
['fshift'],
|
||||||
['fshiftnote'],
|
['fshiftnote'],
|
||||||
@@ -1470,20 +1312,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;
|
|
||||||
}
|
|
||||||
invalidate(scheduler = this.scheduler, t) {
|
|
||||||
if (!scheduler) {
|
if (!scheduler) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: scheduler.now() seems to move even when it's stopped, this hints at a bug...
|
|
||||||
t = t ?? scheduler.now();
|
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
|
const t = scheduler.now();
|
||||||
let [_, lookahead] = this.drawTime;
|
let [_, lookahead] = this.drawTime;
|
||||||
const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1];
|
const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1];
|
||||||
// remove all future haps
|
// remove all future haps
|
||||||
|
|||||||
@@ -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,11 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://strudel.cc",
|
"homepage": "https://strudel.cc",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fraction.js": "^4.3.7"
|
"fraction.js": "^4.2.0"
|
||||||
},
|
},
|
||||||
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
|
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^5.0.10",
|
"vite": "^4.3.3",
|
||||||
"vitest": "^1.1.0"
|
"vitest": "^0.33.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-20
@@ -9,7 +9,7 @@ import Fraction from './fraction.mjs';
|
|||||||
import Hap from './hap.mjs';
|
import Hap from './hap.mjs';
|
||||||
import State from './state.mjs';
|
import State from './state.mjs';
|
||||||
import { unionWithObj } from './value.mjs';
|
import { unionWithObj } from './value.mjs';
|
||||||
|
import { char2fraction } from './util.mjs';
|
||||||
import { compose, removeUndefineds, flatten, id, listRange, curry, _mod, numeralArgs, parseNumeral } from './util.mjs';
|
import { compose, removeUndefineds, flatten, id, listRange, curry, _mod, numeralArgs, parseNumeral } from './util.mjs';
|
||||||
import drawLine from './drawLine.mjs';
|
import drawLine from './drawLine.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
@@ -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) {
|
||||||
@@ -1903,6 +1903,7 @@ export const when = register('when', function (on, func, pat) {
|
|||||||
* "c3 eb3 g3".off(1/8, x=>x.add(7)).note()
|
* "c3 eb3 g3".off(1/8, x=>x.add(7)).note()
|
||||||
*/
|
*/
|
||||||
export const off = register('off', function (time_pat, func, pat) {
|
export const off = register('off', function (time_pat, func, pat) {
|
||||||
|
time_pat = char2fraction(time_pat);
|
||||||
return stack(pat, func(pat.late(time_pat)));
|
return stack(pat, func(pat.late(time_pat)));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1975,9 +1976,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 +1986,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,19 +2190,8 @@ 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
|
|
||||||
* @synonyms colour
|
|
||||||
* @param {string} color Hexadecimal or CSS color name
|
|
||||||
*/
|
*/
|
||||||
// TODO: move this to controls https://github.com/tidalcycles/strudel/issues/288
|
// TODO: move this to controls https://github.com/tidalcycles/strudel/issues/288
|
||||||
export const { color, colour } = register(['color', 'colour'], function (color, pat) {
|
export const { color, colour } = register(['color', 'colour'], function (color, pat) {
|
||||||
|
|||||||
@@ -56,40 +56,6 @@ Pattern.prototype.pianoroll = function (options = {}) {
|
|||||||
|
|
||||||
// this function allows drawing a pianoroll without ties to Pattern.prototype
|
// this function allows drawing a pianoroll without ties to Pattern.prototype
|
||||||
// it will probably replace the above in the future
|
// it will probably replace the above in the future
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a midi-style piano roll
|
|
||||||
*
|
|
||||||
* @name pianoroll
|
|
||||||
* @param {Object} options Object containing all the optional following parameters as key value pairs:
|
|
||||||
* @param {integer} cycles number of cycles to be displayed at the same time - defaults to 4
|
|
||||||
* @param {number} playhead location of the active notes on the time axis - 0 to 1, defaults to 0.5
|
|
||||||
* @param {boolean} vertical displays the roll vertically - 0 by default
|
|
||||||
* @param {boolean} labels displays labels on individual notes (see the label function) - 0 by default
|
|
||||||
* @param {boolean} flipTime reverse the direction of the roll - 0 by default
|
|
||||||
* @param {boolean} flipValues reverse the relative location of notes on the value axis - 0 by default
|
|
||||||
* @param {number} overscan lookup X cycles outside of the cycles window to display notes in advance - 1 by default
|
|
||||||
* @param {boolean} hideNegative hide notes with negative time (before starting playing the pattern) - 0 by default
|
|
||||||
* @param {boolean} smear notes leave a solid trace - 0 by default
|
|
||||||
* @param {boolean} fold notes takes the full value axis width - 0 by default
|
|
||||||
* @param {string} active hexadecimal or CSS color of the active notes - defaults to #FFCA28
|
|
||||||
* @param {string} inactive hexadecimal or CSS color of the inactive notes - defaults to #7491D2
|
|
||||||
* @param {string} background hexadecimal or CSS color of the background - defaults to transparent
|
|
||||||
* @param {string} playheadColor hexadecimal or CSS color of the line representing the play head - defaults to white
|
|
||||||
* @param {boolean} fill notes are filled with color (otherwise only the label is displayed) - 0 by default
|
|
||||||
* @param {boolean} fillActive active notes are filled with color - 0 by default
|
|
||||||
* @param {boolean} stroke notes are shown with colored borders - 0 by default
|
|
||||||
* @param {boolean} strokeActive active notes are shown with colored borders - 0 by default
|
|
||||||
* @param {boolean} hideInactive only active notes are shown - 0 by default
|
|
||||||
* @param {boolean} colorizeInactive use note color for inactive notes - 1 by default
|
|
||||||
* @param {string} fontFamily define the font used by notes labels - defaults to 'monospace'
|
|
||||||
* @param {integer} minMidi minimum note value to display on the value axis - defaults to 10
|
|
||||||
* @param {integer} maxMidi maximum note value to display on the value axis - defaults to 90
|
|
||||||
* @param {boolean} autorange automatically calculate the minMidi and maxMidi parameters - 0 by default
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* note("C2 A2 G2").euclid(5,8).s('piano').clip(1).color('salmon').pianoroll({vertical:1, labels:1})
|
|
||||||
*/
|
|
||||||
export function pianoroll({
|
export function pianoroll({
|
||||||
time,
|
time,
|
||||||
haps,
|
haps,
|
||||||
@@ -256,21 +222,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 });
|
||||||
};
|
};
|
||||||
|
|||||||
+14
-90
@@ -3,7 +3,7 @@ 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';
|
||||||
|
|
||||||
export function repl({
|
export function repl({
|
||||||
interval,
|
interval,
|
||||||
@@ -16,94 +16,44 @@ 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,
|
||||||
updateState({ 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 });
|
|
||||||
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({
|
|
||||||
miniLocations: meta?.miniLocations || [],
|
|
||||||
widgets: meta?.widgets || [],
|
|
||||||
activeCode: code,
|
|
||||||
pattern,
|
|
||||||
evalError: undefined,
|
|
||||||
schedulerError: undefined,
|
|
||||||
pending: false,
|
|
||||||
});
|
|
||||||
afterEval?.({ code, pattern, meta });
|
afterEval?.({ code, pattern, meta });
|
||||||
return pattern;
|
return pattern;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.warn(`[repl] eval error: ${err.message}`);
|
// console.warn(`[repl] eval error: ${err.message}`);
|
||||||
logger(`[eval] error: ${err.message}`, 'error');
|
logger(`[eval] error: ${err.message}`, 'error');
|
||||||
updateState({ evalError: err, pending: false });
|
|
||||||
onEvalError?.(err);
|
onEvalError?.(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const stop = () => scheduler.stop();
|
const stop = () => scheduler.stop();
|
||||||
const start = () => scheduler.start();
|
const start = () => scheduler.start();
|
||||||
const pause = () => scheduler.pause();
|
const pause = () => scheduler.pause();
|
||||||
const toggle = () => scheduler.toggle();
|
|
||||||
const setCps = (cps) => scheduler.setCps(cps);
|
const setCps = (cps) => scheduler.setCps(cps);
|
||||||
const setCpm = (cpm) => scheduler.setCps(cpm / 60);
|
const setCpm = (cpm) => scheduler.setCps(cpm / 60);
|
||||||
|
|
||||||
@@ -112,35 +62,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 +80,14 @@ 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 };
|
return { scheduler, evaluate, start, stop, pause, setCps, setPattern };
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
+27
-49
@@ -1,11 +1,11 @@
|
|||||||
|
import Fraction from './fraction.mjs';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
util.mjs - <short description TODO>
|
util.mjs - <short description TODO>
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/util.mjs>
|
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/util.mjs>
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
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 +86,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;
|
||||||
@@ -276,43 +264,33 @@ export const sol2note = (n, notation = 'letters') => {
|
|||||||
notation === 'solfeggio'
|
notation === 'solfeggio'
|
||||||
? solfeggio /*check if its is any of the following*/
|
? solfeggio /*check if its is any of the following*/
|
||||||
: notation === 'indian'
|
: notation === 'indian'
|
||||||
? indian
|
? indian
|
||||||
: notation === 'german'
|
: notation === 'german'
|
||||||
? german
|
? german
|
||||||
: notation === 'byzantine'
|
: notation === 'byzantine'
|
||||||
? byzantine
|
? byzantine
|
||||||
: notation === 'japanese'
|
: notation === 'japanese'
|
||||||
? japanese
|
? japanese
|
||||||
: english; /*if not use standard version*/
|
: english; /*if not use standard version*/
|
||||||
const note = pc[n % 12]; /*calculating the midi value to the note*/
|
const note = pc[n % 12]; /*calculating the midi value to the note*/
|
||||||
const oct = Math.floor(n / 12) - 1;
|
const oct = Math.floor(n / 12) - 1;
|
||||||
return note + oct;
|
return note + oct;
|
||||||
};
|
};
|
||||||
|
|
||||||
// code hashing helpers
|
const fractionCharacters = {
|
||||||
|
w: Fraction(1),
|
||||||
export function unicodeToBase64(text) {
|
h: Fraction(0.5),
|
||||||
const utf8Bytes = new TextEncoder().encode(text);
|
q: Fraction(0.25),
|
||||||
const base64String = btoa(String.fromCharCode(...utf8Bytes));
|
e: Fraction(0.125),
|
||||||
return base64String;
|
s: Fraction(0.0625),
|
||||||
}
|
t: Fraction(1 / 3),
|
||||||
|
f: Fraction(0.2),
|
||||||
export function base64ToUnicode(base64String) {
|
x: Fraction(1 / 6),
|
||||||
const utf8Bytes = new Uint8Array(
|
};
|
||||||
atob(base64String)
|
const validFractionCharacters = Object.keys(fractionCharacters);
|
||||||
.split('')
|
export const char2fraction = (maybeChar) => {
|
||||||
.map((char) => char.charCodeAt(0)),
|
if (typeof maybeChar === 'string' && validFractionCharacters.includes(maybeChar)) {
|
||||||
);
|
return fractionCharacters[maybeChar];
|
||||||
const decodedText = new TextDecoder().decode(utf8Bytes);
|
}
|
||||||
return decodedText;
|
return maybeChar;
|
||||||
}
|
};
|
||||||
|
|
||||||
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).
|
|
||||||
@@ -137,7 +137,7 @@ export async function loadOrc(url) {
|
|||||||
export const csoundm = register('csoundm', (instrument, pat) => {
|
export const csoundm = register('csoundm', (instrument, pat) => {
|
||||||
let p1 = instrument;
|
let p1 = instrument;
|
||||||
if (typeof instrument === 'string') {
|
if (typeof instrument === 'string') {
|
||||||
p1 = `"${instrument}"`;
|
p1 = `"{instrument}"`;
|
||||||
}
|
}
|
||||||
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
|
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
|
||||||
return pat.onTrigger((tidal_time, hap) => {
|
return pat.onTrigger((tidal_time, hap) => {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# @strudel.cycles/react
|
||||||
|
|
||||||
|
This package contains react hooks and components for strudel. It is used internally by the Strudel REPL.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```js
|
||||||
|
npm i @strudel.cycles/react
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Here is a minimal example of how to set up a MiniRepl:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import * as React from 'react';
|
||||||
|
import '@strudel.cycles/react/dist/style.css';
|
||||||
|
import { MiniRepl } from '@strudel.cycles/react';
|
||||||
|
import { evalScope, controls } from '@strudel.cycles/core';
|
||||||
|
import { samples, initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
||||||
|
|
||||||
|
async function prebake() {
|
||||||
|
await samples(
|
||||||
|
'https://strudel.cc/tidal-drum-machines.json',
|
||||||
|
'github:ritchse/tidal-drum-machines/main/machines/'
|
||||||
|
);
|
||||||
|
await samples(
|
||||||
|
'https://strudel.cc/EmuSP12.json',
|
||||||
|
'https://strudel.cc/EmuSP12/'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
await evalScope(
|
||||||
|
controls,
|
||||||
|
import('@strudel.cycles/core'),
|
||||||
|
import('@strudel.cycles/mini'),
|
||||||
|
import('@strudel.cycles/webaudio'),
|
||||||
|
import('@strudel.cycles/tonal')
|
||||||
|
);
|
||||||
|
await prebake();
|
||||||
|
initAudioOnFirstClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return <MiniRepl tune={`s("bd sd,hh*4")`} />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Open [example on stackblitz](https://stackblitz.com/edit/react-ts-saaair?file=tune.tsx,App.tsx)
|
||||||
|
- Also check out the [nano-repl](./examples/nano-repl/) for a more sophisticated example
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# nano-repl
|
||||||
|
|
||||||
|
this is an example of how to create a repl with strudel and react.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
after cloning the strudel repo:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm i
|
||||||
|
cd packages/react/examples/nano-repl
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
you should now have a repl running at `http://localhost:5173/`
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Strudel Nano REPL</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "@strudel.cycles/nano-repl",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.6.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"@strudel.cycles/core": "workspace:*",
|
||||||
|
"@strudel.cycles/osc": "workspace:*",
|
||||||
|
"@strudel.cycles/mini": "workspace:*",
|
||||||
|
"@strudel.cycles/transpiler": "workspace:*",
|
||||||
|
"@strudel.cycles/soundfonts": "workspace:*",
|
||||||
|
"@strudel.cycles/webaudio": "workspace:*",
|
||||||
|
"@strudel.cycles/tonal": "workspace:*",
|
||||||
|
"@strudel.cycles/react": "workspace:*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.2.0",
|
||||||
|
"@types/react-dom": "^18.2.1",
|
||||||
|
"@vitejs/plugin-react": "^4.0.0",
|
||||||
|
"autoprefixer": "^10.4.14",
|
||||||
|
"postcss": "^8.4.23",
|
||||||
|
"tailwindcss": "^3.3.2",
|
||||||
|
"vite": "^4.3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user