Compare commits

..

29 Commits

Author SHA1 Message Date
Felix Roos 6d6114beb6 Merge branch 'main' into global-repl 2023-11-15 17:46:03 +01:00
Felix Roos 82380a0f4e make sliders really work 2023-11-03 14:08:13 +01:00
Felix Roos c030f456c3 widget support 2023-11-03 13:50:18 +01:00
Felix Roos 694e44dc8a better init + don't draw first frame by default 2023-11-03 12:07:19 +01:00
Felix Roos 351da196b9 StrudelMirror onPaint support 2023-11-03 11:46:32 +01:00
Felix Roos e27ee03517 format 2023-11-03 11:11:23 +01:00
Felix Roos 19db419c53 handle shuffle 2023-11-03 11:10:53 +01:00
Felix Roos ff60a89668 fix: autocomplete + add missing basic extensions 2023-11-03 10:47:05 +01:00
Felix Roos 7cc17fefcb fix: undo redo key mapping
+ fix hidden line number style
2023-11-03 10:20:07 +01:00
Felix Roos 76ee9485f4 format 2023-11-03 10:05:37 +01:00
Felix Roos 1d65f09f3c - enable most settings:
- keybindings
- line numbers
- line wrapping
- highlighting (new)
- flashing (new)
- autocomplete not working yet
- move themes to codemirror package (currently duplicated)
- generally simplify settings handling
2023-11-03 10:04:58 +01:00
Felix Roos cc97f6395d better keymap 2023-11-02 23:15:41 +01:00
Felix Roos 33eef619e4 no destructuring 2023-11-02 22:49:52 +01:00
Felix Roos e9e3b97621 format 2023-11-02 22:45:52 +01:00
Felix Roos 1f765e54ca Repl: use codemirror custom keys
+ use new prebake
2023-11-02 22:45:32 +01:00
Felix Roos 2b81cc5d0c comment out (probably) dead end 2023-11-02 22:37:42 +01:00
Felix Roos ca054dbd74 restore to old state 2023-11-02 22:37:31 +01:00
Felix Roos a8433f61a4 add missing dep 2023-11-02 22:36:03 +01:00
Felix Roos 5850106eef restore react hooks 2023-11-02 22:33:16 +01:00
Felix Roos 95a9ab6182 revert Repl.jsx 2023-11-02 22:29:27 +01:00
Felix Roos ba19c4ddce duplicate new lighter repl 2023-11-02 22:28:46 +01:00
Felix Roos b49b1c98ae make play and update buttons work 2023-11-02 17:14:59 +01:00
Felix Roos 7c00aa1a36 intermediate /vanilla repl
+ react is now only used for ui around editor!
+ ui still not fully functional
2023-11-02 16:31:45 +01:00
Felix Roos 07d6bb3c44 - encapsulate repl state from react
- might still be buggy!
- smarter way to flush sounds
2023-11-02 14:37:50 +01:00
Felix Roos 3f2e2554be add back missing shortcuts 2023-11-02 11:29:36 +01:00
Felix Roos 9ebb7edde9 remove old export 2023-11-02 11:29:27 +01:00
Felix Roos a89091cbc7 bind custom keyboard shortcuts via codemirror
+ add reevaluate and panic shortcuts
2023-11-02 11:24:23 +01:00
Felix Roos 0d1a99deb2 also pull in audio init to prebake 2023-11-02 10:37:38 +01:00
Felix Roos b71f8a33d7 refactor: pull module loading into prebake 2023-11-02 10:21:39 +01:00
359 changed files with 14810 additions and 15269 deletions
-1
View File
@@ -21,4 +21,3 @@ vite.config.js
/src-tauri/target/**/* /src-tauri/target/**/*
reverbGen.mjs reverbGen.mjs
hydra.mjs hydra.mjs
jsdoc-synonyms.js
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
View File
@@ -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
-1
View File
@@ -10,4 +10,3 @@ paper
pnpm-lock.yaml pnpm-lock.yaml
pnpm-workspace.yaml pnpm-workspace.yaml
**/dev-dist **/dev-dist
website/.astro
+2 -2
View File
@@ -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:
@@ -114,7 +114,7 @@ You can run the same check with `pnpm check`
## Package Workflow ## Package Workflow
The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning. The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning.
When you run `pnpm i` on the root folder, [pnpm workspaces](https://pnpm.io/workspaces) will install all dependencies of all subpackages. This will allow any js file to import `@strudel/<package-name>` to get the local version, When you run `pnpm i` on the root folder, [pnpm workspaces](https://pnpm.io/workspaces) will install all dependencies of all subpackages. This will allow any js file to import `@strudel.cycles/<package-name>` to get the local version,
allowing to develop multiple packages at the same time. allowing to develop multiple packages at the same time.
## Package Publishing ## Package Publishing
+1
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 150 KiB

-3
View File
@@ -1,3 +0,0 @@
# examples
This folder contains usage examples for different scenarios.
-5
View File
@@ -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>
-8
View File
@@ -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
```
-23
View File
@@ -1,23 +0,0 @@
{
"name": "vite-vanilla-repl-cm6",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^5.0.10"
},
"dependencies": {
"@strudel/codemirror": "workspace:*",
"@strudel/core": "workspace:*",
"@strudel/mini": "workspace:*",
"@strudel/soundfonts": "workspace:*",
"@strudel/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*"
}
}
-13
View File
@@ -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
View File
@@ -1 +0,0 @@
dist
-10
View File
@@ -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)
-11
View File
@@ -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/
```
-38
View File
@@ -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
View File
@@ -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
} }
+20 -19
View File
@@ -1,5 +1,5 @@
{ {
"name": "@strudel/monorepo", "name": "@strudel.cycles/monorepo",
"version": "0.5.0", "version": "0.5.0",
"private": true, "private": true,
"description": "Port of tidalcycles to javascript", "description": "Port of tidalcycles to javascript",
@@ -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"
}, },
@@ -45,27 +45,28 @@
}, },
"homepage": "https://strudel.cc", "homepage": "https://strudel.cc",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@strudel/mini": "workspace:*", "@strudel.cycles/mini": "workspace:*",
"@strudel/tonal": "workspace:*", "@strudel.cycles/tonal": "workspace:*",
"@strudel/transpiler": "workspace:*", "@strudel.cycles/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*", "@strudel.cycles/webaudio": "workspace:*",
"@strudel/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"
} }
} }
+11 -1
View File
@@ -1,5 +1,15 @@
# Packages # Packages
Each folder represents one of the @strudel/* packages [published to npm](https://www.npmjs.com/org/strudel). 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)
![dependencies](https://raw.githubusercontent.com/tidalcycles/strudel/main/dependencies.svg)
Generated with
```sh
npx depcruise --include-only "^packages" -X "node_modules" --output-type dot packages | dot -T svg > dependencygraph.svg
```
@@ -1,13 +1,7 @@
import { createRoot } from 'react-dom/client';
import jsdoc from '../../doc.json'; import jsdoc from '../../doc.json';
// import { javascriptLanguage } from '@codemirror/lang-javascript'; // import { javascriptLanguage } from '@codemirror/lang-javascript';
import { autocompletion } from '@codemirror/autocomplete'; import { autocompletion } from '@codemirror/autocomplete';
import { h } from './html';
function plaintext(str) {
const div = document.createElement('div');
div.innerText = str;
return div.innerHTML;
}
const getDocLabel = (doc) => doc.name || doc.longname; const getDocLabel = (doc) => doc.name || doc.longname;
const getInnerText = (html) => { const getInnerText = (html) => {
@@ -16,21 +10,21 @@ const getInnerText = (html) => {
return div.textContent || div.innerText || ''; return div.textContent || div.innerText || '';
}; };
export function Autocomplete({ doc, label }) { export function Autocomplete({ doc }) {
return h`<div class="prose dark:prose-invert max-h-[400px] overflow-auto p-2"> return (
<h1 class="pt-0 mt-0">${label || getDocLabel(doc)}</h1> <div className="prose dark:prose-invert max-h-[400px] overflow-auto">
${doc.description} <h3 className="pt-0 mt-0">{getDocLabel(doc)}</h3>
<div dangerouslySetInnerHTML={{ __html: doc.description }} />
<ul> <ul>
${doc.params?.map( {doc.params?.map(({ name, type, description }, i) => (
({ name, type, description }) => <li key={i}>
`<li>${name} : ${type.names?.join(' | ')} ${description ? ` - ${getInnerText(description)}` : ''}</li>`, {name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)}</> : ''}
)} </li>
))}
</ul> </ul>
<div> <div>
${doc.examples?.map((example) => `<div><pre>${plaintext(example)}</pre></div>`)} {doc.examples?.map((example, i) => (
</div> <div key={i}>
</div>`[0];
/*
<pre <pre
className="cursor-pointer" className="cursor-pointer"
onMouseDown={(e) => { onMouseDown={(e) => {
@@ -41,7 +35,11 @@ onMouseDown={(e) => {
> >
{example} {example}
</pre> </pre>
*/ </div>
))}
</div>
</div>
);
} }
const jsdocCompletions = jsdoc.docs const jsdocCompletions = jsdoc.docs
@@ -56,7 +54,13 @@ const jsdocCompletions = jsdoc.docs
.map((doc) /*: Completion */ => ({ .map((doc) /*: Completion */ => ({
label: getDocLabel(doc), label: getDocLabel(doc),
// detail: 'xxx', // An optional short piece of information to show (with a different style) after the label. // detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
info: () => Autocomplete({ doc }), info: () => {
const node = document.createElement('div');
// if Autocomplete is non-interactive, it could also be rendered at build time..
// .. using renderToStaticMarkup
createRoot(node).render(<Autocomplete doc={doc} />);
return node;
},
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
})); }));
+16 -113
View File
@@ -3,64 +3,32 @@ import { closeBrackets } from '@codemirror/autocomplete';
import { history } from '@codemirror/commands'; import { history } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript'; import { javascript } from '@codemirror/lang-javascript';
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language'; import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
import { Compartment, EditorState, Prec } from '@codemirror/state'; import { Compartment, EditorState } from '@codemirror/state';
import { import { EditorView, highlightActiveLineGutter, highlightActiveLine, keymap, lineNumbers } from '@codemirror/view';
EditorView, import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core';
highlightActiveLineGutter, import { isAutoCompletionEnabled } from './Autocomplete';
highlightActiveLine,
keymap,
lineNumbers,
drawSelection,
} from '@codemirror/view';
import { Pattern, Drawer, repl, cleanupDraw } from '@strudel/core';
import { isAutoCompletionEnabled } from './autocomplete.mjs';
import { isTooltipEnabled } from './tooltip.mjs';
import { flash, isFlashEnabled } from './flash.mjs'; import { flash, isFlashEnabled } from './flash.mjs';
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs'; import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
import { keybindings } from './keybindings.mjs'; import { keybindings } from './keybindings.mjs';
import { initTheme, activateTheme, theme } from './themes.mjs'; import { theme } from './themes.mjs';
import { updateWidgets, sliderPlugin } from './slider.mjs'; import { updateWidgets, sliderPlugin } from './slider.mjs';
import { persistentAtom } from '@nanostores/persistent';
const extensions = { const extensions = {
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []), isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []), isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []),
theme, theme,
isAutoCompletionEnabled, isAutoCompletionEnabled,
isTooltipEnabled,
isPatternHighlightingEnabled, isPatternHighlightingEnabled,
isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []),
isFlashEnabled, isFlashEnabled,
keybindings, keybindings,
}; };
const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()])); const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
export const defaultSettings = {
keybindings: 'codemirror',
isLineNumbersDisplayed: true,
isActiveLineHighlighted: false,
isAutoCompletionEnabled: false,
isPatternHighlightingEnabled: true,
isFlashEnabled: true,
isTooltipEnabled: false,
isLineWrappingEnabled: false,
theme: 'strudelTheme',
fontFamily: 'monospace',
fontSize: 18,
};
export const codemirrorSettings = persistentAtom('codemirror-settings', defaultSettings, {
encode: JSON.stringify,
decode: JSON.parse,
});
// https://codemirror.net/docs/guide/ // https://codemirror.net/docs/guide/
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, root }) { export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, settings, root }) {
const settings = codemirrorSettings.get();
const initialSettings = Object.keys(compartments).map((key) => const initialSettings = Object.keys(compartments).map((key) =>
compartments[key].of(extensions[key](parseBooleans(settings[key]))), compartments[key].of(extensions[key](parseBooleans(settings[key]))),
); );
initTheme(settings.theme);
let state = EditorState.create({ let state = EditorState.create({
doc: initialCode, doc: initialCode,
extensions: [ extensions: [
@@ -72,11 +40,11 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
// indentOnInput(), // works without. already brought with javascript extension? // indentOnInput(), // works without. already brought with javascript extension?
// bracketMatching(), // does not do anything // bracketMatching(), // does not do anything
closeBrackets(), closeBrackets(),
highlightActiveLineGutter(),
highlightActiveLine(),
syntaxHighlighting(defaultHighlightStyle), syntaxHighlighting(defaultHighlightStyle),
history(), history(),
EditorView.updateListener.of((v) => onChange(v)), EditorView.updateListener.of((v) => onChange(v)),
drawSelection({ cursorBlinkRate: 0 }),
Prec.highest(
keymap.of([ keymap.of([
{ {
key: 'Ctrl-Enter', key: 'Ctrl-Enter',
@@ -106,7 +74,6 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()), run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()),
}, */ }, */
]), ]),
),
], ],
}); });
@@ -118,26 +85,14 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
export class StrudelMirror { export class StrudelMirror {
constructor(options) { constructor(options) {
const { const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, settings, ...replOptions } = options;
root,
id,
initialCode = '',
onDraw,
drawTime = [0, 0],
autodraw,
prebake,
bgFill = true,
...replOptions
} = options;
this.code = initialCode; this.code = initialCode;
this.root = root; this.root = root;
this.miniLocations = []; this.miniLocations = [];
this.widgets = []; this.widgets = [];
this.painters = []; this.painters = [];
this.drawTime = drawTime;
this.onDraw = onDraw; this.onDraw = onDraw;
const self = this; const self = this;
this.id = id || s4();
this.drawer = new Drawer((haps, time) => { this.drawer = new Drawer((haps, time) => {
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped); const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
@@ -145,29 +100,21 @@ export class StrudelMirror {
this.onDraw?.(haps, time, currentFrame, this.painters); this.onDraw?.(haps, time, currentFrame, this.painters);
}, drawTime); }, drawTime);
// this approach does not work with multiple repls on screen // this approach might not work with multiple repls on screen..
// TODO: refactor onPaint usages + find fix, maybe remove painters here?
Pattern.prototype.onPaint = function (onPaint) { Pattern.prototype.onPaint = function (onPaint) {
self.painters.push(onPaint); self.painters.push(onPaint);
return this; return this;
}; };
this.prebaked = prebake(); this.prebaked = prebake();
autodraw && this.drawFirstFrame(); // this.drawFirstFrame();
this.repl = repl({ this.repl = repl({
...replOptions, ...replOptions,
onToggle: (started) => { onToggle: (started) => {
replOptions?.onToggle?.(started); replOptions?.onToggle?.(started);
if (started) { if (started) {
this.adjustDrawTime();
this.drawer.start(this.repl.scheduler); this.drawer.start(this.repl.scheduler);
// stop other repls when this one is started
document.dispatchEvent(
new CustomEvent('start-repl', {
detail: this.id,
}),
);
} else { } else {
this.drawer.stop(); this.drawer.stop();
updateMiniLocations(this.editor, []); updateMiniLocations(this.editor, []);
@@ -187,46 +134,22 @@ export class StrudelMirror {
updateWidgets(this.editor, this.widgets); updateWidgets(this.editor, this.widgets);
updateMiniLocations(this.editor, this.miniLocations); updateMiniLocations(this.editor, this.miniLocations);
replOptions?.afterEval?.(options); replOptions?.afterEval?.(options);
this.adjustDrawTime();
this.drawer.invalidate(); this.drawer.invalidate();
}, },
}); });
this.editor = initEditor({ this.editor = initEditor({
root, root,
settings,
initialCode, initialCode,
onChange: (v) => { onChange: (v) => {
if (v.docChanged) { if (v.docChanged) {
this.code = v.state.doc.toString(); this.code = v.state.doc.toString();
this.repl.setCode?.(this.code); this.repl.setCode(this.code);
} }
}, },
onEvaluate: () => this.evaluate(), onEvaluate: () => this.evaluate(),
onStop: () => this.stop(), onStop: () => this.stop(),
}); });
const cmEditor = this.root.querySelector('.cm-editor');
if (cmEditor) {
this.root.style.display = 'block';
if (bgFill) {
this.root.style.backgroundColor = 'var(--background)';
}
cmEditor.style.backgroundColor = 'transparent';
}
const settings = codemirrorSettings.get();
this.setFontSize(settings.fontSize);
this.setFontFamily(settings.fontFamily);
// stop this repl when another repl is started
this.onStartRepl = (e) => {
if (e.detail !== this.id) {
this.stop();
}
};
document.addEventListener('start-repl', this.onStartRepl);
}
// adjusts draw time depending on if there are painters
adjustDrawTime() {
// when no painters are set, [0,0] is enough (just highlighting)
this.drawer.setDrawTime(this.painters.length ? this.drawTime : [0, 0]);
} }
async drawFirstFrame() { async drawFirstFrame() {
if (!this.onDraw) { if (!this.onDraw) {
@@ -236,9 +159,8 @@ export class StrudelMirror {
await this.prebaked; await this.prebaked;
try { try {
await this.repl.evaluate(this.code, false); await this.repl.evaluate(this.code, false);
this.drawer.invalidate(this.repl.scheduler, -0.001); this.drawer.invalidate(this.repl.scheduler);
// draw at -0.001 to avoid haps at 0 to be visualized as active this.onDraw?.(this.drawer.visibleHaps, 0, []);
this.onDraw?.(this.drawer.visibleHaps, -0.001, [], this.painters);
} catch (err) { } catch (err) {
console.warn('first frame could not be painted'); console.warn('first frame could not be painted');
} }
@@ -252,7 +174,7 @@ export class StrudelMirror {
} }
async toggle() { async toggle() {
if (this.repl.scheduler.started) { if (this.repl.scheduler.started) {
this.repl.stop(); this.repl.scheduler.stop();
} else { } else {
this.evaluate(); this.evaluate();
} }
@@ -268,10 +190,6 @@ export class StrudelMirror {
} }
setFontFamily(family) { setFontFamily(family) {
this.root.style.fontFamily = family; this.root.style.fontFamily = family;
const scroller = this.root.querySelector('.cm-scroller');
if (scroller) {
scroller.style.fontFamily = family;
}
} }
reconfigureExtension(key, value) { reconfigureExtension(key, value) {
if (!extensions[key]) { if (!extensions[key]) {
@@ -283,9 +201,6 @@ export class StrudelMirror {
this.editor.dispatch({ this.editor.dispatch({
effects: compartments[key].reconfigure(newValue), effects: compartments[key].reconfigure(newValue),
}); });
if (key === 'theme') {
activateTheme(value);
}
} }
setLineWrappingEnabled(enabled) { setLineWrappingEnabled(enabled) {
this.reconfigureExtension('isLineWrappingEnabled', enabled); this.reconfigureExtension('isLineWrappingEnabled', enabled);
@@ -305,8 +220,6 @@ export class StrudelMirror {
for (let key in extensions) { for (let key in extensions) {
this.reconfigureExtension(key, settings[key]); this.reconfigureExtension(key, settings[key]);
} }
const updated = { ...codemirrorSettings.get(), ...settings };
codemirrorSettings.set(updated);
} }
changeSetting(key, value) { changeSetting(key, value) {
if (extensions[key]) { if (extensions[key]) {
@@ -322,18 +235,8 @@ export class StrudelMirror {
const changes = { from: 0, to: this.editor.state.doc.length, insert: code }; const changes = { from: 0, to: this.editor.state.doc.length, insert: code };
this.editor.dispatch({ changes }); this.editor.dispatch({ changes });
} }
clear() {
this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl);
}
} }
function parseBooleans(value) { function parseBooleans(value) {
return { true: true, false: false }[value] ?? value; return { true: true, false: false }[value] ?? value;
} }
// helper function to generate repl ids
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
+1 -3
View File
@@ -57,9 +57,6 @@ const visibleMiniLocations = StateField.define({
// this is why we need to find a way to update the existing decorations, showing the ones that have an active range // this is why we need to find a way to update the existing decorations, showing the ones that have an active range
const haps = new Map(); const haps = new Map();
for (let hap of e.value.haps) { for (let hap of e.value.haps) {
if (!hap.context?.locations || !hap.whole) {
continue;
}
for (let { start, end } of hap.context.locations) { for (let { start, end } of hap.context.locations) {
let id = `${start}:${end}`; let id = `${start}:${end}`;
if (!haps.has(id) || haps.get(id).whole.begin.lt(hap.whole.begin)) { if (!haps.has(id) || haps.get(id).whole.begin.lt(hap.whole.begin)) {
@@ -67,6 +64,7 @@ const visibleMiniLocations = StateField.define({
} }
} }
} }
visible = { atTime: e.value.atTime, haps }; visible = { atTime: e.value.atTime, haps };
} }
} }
-17
View File
@@ -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);
};
-1
View File
@@ -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';
+15 -16
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/codemirror", "name": "@strudel/codemirror",
"version": "1.0.0", "version": "0.9.0",
"description": "Codemirror Extensions for Strudel", "description": "Codemirror Extensions for Strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -33,24 +33,23 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.11.1", "@codemirror/autocomplete": "^6.6.0",
"@codemirror/commands": "^6.3.3", "@codemirror/commands": "^6.2.4",
"@codemirror/lang-javascript": "^6.2.1", "@codemirror/lang-javascript": "^6.1.7",
"@codemirror/language": "^6.10.0", "@codemirror/language": "^6.6.0",
"@codemirror/search": "^6.5.5", "@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.4.0", "@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.23.0", "@codemirror/view": "^6.10.0",
"@lezer/highlight": "^1.2.0", "@lezer/highlight": "^1.1.4",
"@nanostores/persistent": "^0.9.1",
"@replit/codemirror-emacs": "^6.0.1", "@replit/codemirror-emacs": "^6.0.1",
"@replit/codemirror-vim": "^6.1.0", "@replit/codemirror-vim": "^6.0.14",
"@replit/codemirror-vscode-keymap": "^6.0.2", "@replit/codemirror-vscode-keymap": "^6.0.2",
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@uiw/codemirror-themes": "^4.21.21", "@uiw/codemirror-themes": "^4.19.16",
"@uiw/codemirror-themes-all": "^4.21.21", "@uiw/codemirror-themes-all": "^4.19.16",
"nanostores": "^0.9.5" "react-dom": "^18.2.0"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^4.3.3"
} }
} }
+1 -10
View File
@@ -1,4 +1,4 @@
import { ref, pure } from '@strudel/core'; import { ref, pure } from '@strudel.cycles/core';
import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view'; import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view';
import { StateEffect, StateField } from '@codemirror/state'; import { StateEffect, StateField } from '@codemirror/state';
@@ -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);
+1 -44
View File
@@ -473,9 +473,6 @@ function stringifySafe(json) {
return JSON.stringify(json, getCircularReplacer()); return JSON.stringify(json, getCircularReplacer());
} }
export const theme = (theme) => themes[theme] || themes.strudelTheme;
// css style injection helpers
export function injectStyle(rule) { export function injectStyle(rule) {
const newStyle = document.createElement('style'); const newStyle = document.createElement('style');
document.head.appendChild(newStyle); document.head.appendChild(newStyle);
@@ -484,44 +481,4 @@ export function injectStyle(rule) {
return () => styleSheet.deleteRule(ruleIndex); return () => styleSheet.deleteRule(ruleIndex);
} }
let currentTheme, export const theme = (theme) => themes[theme] || themes.strudelTheme;
resetThemeStyle,
themeStyle,
styleID = 'strudel-theme-vars';
export function initTheme(theme) {
if (!document.getElementById(styleID)) {
themeStyle = document.createElement('style');
themeStyle.id = styleID;
document.head.append(themeStyle);
}
activateTheme(theme);
}
export function activateTheme(name) {
if (currentTheme === name) {
return;
}
currentTheme = name;
if (!settings[name]) {
console.warn('theme', name, 'has no settings.. defaulting to strudelTheme settings');
}
const themeSettings = settings[name] || settings.strudelTheme;
// set css variables
themeStyle.innerHTML = `:root {
${Object.entries(themeSettings)
// important to override fallback
.map(([key, value]) => `--${key}: ${value} !important;`)
.join('\n')}
}`;
// tailwind dark mode
if (themeSettings.light) {
document.documentElement.classList.remove('dark');
} else {
document.documentElement.classList.add('dark');
}
resetThemeStyle?.();
resetThemeStyle = undefined;
if (themeSettings.customStyle) {
resetThemeStyle = injectStyle(themeSettings.customStyle);
}
}
+1 -1
View File
@@ -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)],
+4 -4
View File
@@ -1,17 +1,17 @@
# @strudel/core # @strudel.cycles/core
This package contains the bare essence of strudel. This package contains the bare essence of strudel.
## Install ## Install
```sh ```sh
npm i @strudel/core --save npm i @strudel.cycles/core --save
``` ```
## Example ## Example
```js ```js
import { sequence } from '@strudel/core'; import { sequence } from '@strudel.cycles/core';
const pattern = sequence('a', ['b', 'c']); const pattern = sequence('a', ['b', 'c']);
@@ -33,7 +33,7 @@ b: 3/2 - 7/4
c: 7/4 - 2 c: 7/4 - 2
``` ```
- [play with @strudel/core on codesandbox](https://codesandbox.io/s/strudel-core-test-forked-9ywhv7?file=/src/index.js). - [play with @strudel.cycles/core on codesandbox](https://codesandbox.io/s/strudel-core-test-forked-9ywhv7?file=/src/index.js).
- [open color pattern example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/canvas.html) - [open color pattern example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/canvas.html)
- [open minimal repl example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/vanilla.html) - [open minimal repl example](https://raw.githack.com/tidalcycles/strudel/main/packages/core/examples/vanilla.html)
- [open minimal vite example](./examples/vite-vanilla-repl/) - [open minimal vite example](./examples/vite-vanilla-repl/)
+81 -194
View File
@@ -40,7 +40,7 @@ const generic_params = [
* @name n * @name n
* @param {number | Pattern} value sample index starting from 0 * @param {number | Pattern} value sample index starting from 0
* @example * @example
* s("bd sd [~ bd] sd,hh*6").n("<0 1>") * s("bd sd,hh*3").n("<0 1>")
*/ */
// also see https://github.com/tidalcycles/strudel/pull/63 // also see https://github.com/tidalcycles/strudel/pull/63
['n'], ['n'],
@@ -82,7 +82,7 @@ const generic_params = [
* @name gain * @name gain
* @param {number | Pattern} amount gain. * @param {number | Pattern} amount gain.
* @example * @example
* s("hh*8").gain(".4!2 1 .4!2 1 .4 1").fast(2) * s("hh*8").gain(".4!2 1 .4!2 1 .4 1")
* *
*/ */
['gain'], ['gain'],
@@ -91,13 +91,13 @@ const generic_params = [
* *
* @name postgain * @name postgain
* @example * @example
* s("bd sd [~ bd] sd,hh*8") * s("bd sd,hh*4")
* .compressor("-20:20:10:.002:.02").postgain(1.5) * .compressor("-20:20:10:.002:.02").postgain(1.5)
* *
*/ */
['postgain'], ['postgain'],
/** /**
* Like `gain`, but linear. * Like {@link gain}, but linear.
* *
* @name amp * @name amp
* @param {number | Pattern} amount gain. * @param {number | Pattern} amount gain.
@@ -114,7 +114,7 @@ const generic_params = [
* @param {number | Pattern} attack time in seconds. * @param {number | Pattern} attack time in seconds.
* @synonyms att * @synonyms att
* @example * @example
* note("c3 e3 f3 g3").attack("<0 .1 .5>") * note("c3 e3").attack("<0 .1 .5>")
* *
*/ */
['attack', 'att'], ['attack', 'att'],
@@ -128,7 +128,7 @@ const generic_params = [
* @name fmh * @name fmh
* @param {number | Pattern} harmonicity * @param {number | Pattern} harmonicity
* @example * @example
* note("c e g b g e") * note("c e g b")
* .fm(4) * .fm(4)
* .fmh("<1 2 1.5 1.61>") * .fmh("<1 2 1.5 1.61>")
* .scope() * .scope()
@@ -143,7 +143,7 @@ const generic_params = [
* @param {number | Pattern} brightness modulation index * @param {number | Pattern} brightness modulation index
* @synonyms fmi * @synonyms fmi
* @example * @example
* note("c e g b g e") * note("c e g b")
* .fm("<0 1 2 8 32>") * .fm("<0 1 2 8 32>")
* .scope() * .scope()
* *
@@ -156,7 +156,7 @@ const generic_params = [
* @name fmenv * @name fmenv
* @param {number | Pattern} type lin | exp * @param {number | Pattern} type lin | exp
* @example * @example
* note("c e g b g e") * note("c e g b")
* .fm(4) * .fm(4)
* .fmdecay(.2) * .fmdecay(.2)
* .fmsustain(0) * .fmsustain(0)
@@ -171,7 +171,7 @@ const generic_params = [
* @name fmattack * @name fmattack
* @param {number | Pattern} time attack time * @param {number | Pattern} time attack time
* @example * @example
* note("c e g b g e") * note("c e g b")
* .fm(4) * .fm(4)
* .fmattack("<0 .05 .1 .2>") * .fmattack("<0 .05 .1 .2>")
* .scope() * .scope()
@@ -184,7 +184,7 @@ const generic_params = [
* @name fmdecay * @name fmdecay
* @param {number | Pattern} time decay time * @param {number | Pattern} time decay time
* @example * @example
* note("c e g b g e") * note("c e g b")
* .fm(4) * .fm(4)
* .fmdecay("<.01 .05 .1 .2>") * .fmdecay("<.01 .05 .1 .2>")
* .fmsustain(.4) * .fmsustain(.4)
@@ -198,7 +198,7 @@ const generic_params = [
* @name fmsustain * @name fmsustain
* @param {number | Pattern} level sustain level * @param {number | Pattern} level sustain level
* @example * @example
* note("c e g b g e") * note("c e g b")
* .fm(4) * .fm(4)
* .fmdecay(.1) * .fmdecay(.1)
* .fmsustain("<1 .75 .5 0>") * .fmsustain("<1 .75 .5 0>")
@@ -216,7 +216,7 @@ const generic_params = [
* @name bank * @name bank
* @param {string | Pattern} bank the name of the bank * @param {string | Pattern} bank the name of the bank
* @example * @example
* s("bd sd [~ bd] sd").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd") * s("bd sd").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd")
* *
*/ */
['bank'], ['bank'],
@@ -231,10 +231,10 @@ const generic_params = [
* @name decay * @name decay
* @param {number | Pattern} time decay time in seconds * @param {number | Pattern} time decay time in seconds
* @example * @example
* note("c3 e3 f3 g3").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.
* *
@@ -242,7 +242,7 @@ const generic_params = [
* @param {number | Pattern} gain sustain level between 0 and 1 * @param {number | Pattern} gain sustain level between 0 and 1
* @synonyms sus * @synonyms sus
* @example * @example
* note("c3 e3 f3 g3").decay(.2).sustain("<0 .1 .4 .6 1>") * note("c3 e3").decay(.2).sustain("<0 .1 .4 .6 1>")
* *
*/ */
['sustain', 'sus'], ['sustain', 'sus'],
@@ -267,10 +267,10 @@ const generic_params = [
* @param {number | Pattern} frequency center frequency * @param {number | Pattern} frequency center frequency
* @synonyms bandf, bp * @synonyms bandf, bp
* @example * @example
* s("bd sd [~ bd] sd,hh*6").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).
@@ -279,7 +279,7 @@ const generic_params = [
* @param {number | Pattern} q q factor * @param {number | Pattern} q q factor
* @synonyms bandq * @synonyms bandq
* @example * @example
* s("bd sd [~ bd] sd").bpf(500).bpq("<0 1 2 3>") * s("bd sd").bpf(500).bpq("<0 1 2 3>")
* *
*/ */
// currently an alias of 'bandq' https://github.com/tidalcycles/strudel/issues/496 // currently an alias of 'bandq' https://github.com/tidalcycles/strudel/issues/496
@@ -293,7 +293,7 @@ const generic_params = [
* @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample * @param {number | Pattern} amount between 0 and 1, where 1 is the length of the sample
* @example * @example
* samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/Dirt-Samples/master/') * samples({ rave: 'rave/AREUREADY.wav' }, 'github:tidalcycles/Dirt-Samples/master/')
* s("rave").begin("<0 .25 .5 .75>").fast(2) * s("rave").begin("<0 .25 .5 .75>")
* *
*/ */
['begin'], ['begin'],
@@ -304,7 +304,7 @@ const generic_params = [
* @name end * @name end
* @param {number | Pattern} length 1 = whole sample, .5 = half sample, .25 = quarter sample etc.. * @param {number | Pattern} length 1 = whole sample, .5 = half sample, .25 = quarter sample etc..
* @example * @example
* s("bd*2,oh*4").end("<.1 .2 .5 1>").fast(2) * s("bd*2,oh*4").end("<.1 .2 .5 1>")
* *
*/ */
['end'], ['end'],
@@ -376,24 +376,11 @@ const generic_params = [
* @name coarse * @name coarse
* @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on. * @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on.
* @example * @example
* s("bd sd [~ bd] sd,hh*8").coarse("<1 4 8 16 32>") * s("bd sd,hh*4").coarse("<1 4 8 16 32>")
* *
*/ */
['coarse'], ['coarse'],
/**
* Allows you to set the output channels on the interface
*
* @name channels
* @synonyms ch
*
* @param {number | Pattern} channels pattern the output channels
* @example
* note("e a d b g").channels("3:4")
*
*/
['channels', 'ch'],
['phaserrate', 'phasr'], // superdirt only ['phaserrate', 'phasr'], // superdirt only
/** /**
@@ -463,7 +450,7 @@ const generic_params = [
* @name cut * @name cut
* @param {number | Pattern} group cut group number * @param {number | Pattern} group cut group number
* @example * @example
* s("[oh hh]*4").cut(1) * s("rd*4").cut(1)
* *
*/ */
['cut'], ['cut'],
@@ -476,12 +463,12 @@ const generic_params = [
* @param {number | Pattern} frequency audible between 0 and 20000 * @param {number | Pattern} frequency audible between 0 and 20000
* @synonyms cutoff, ctf, lp * @synonyms cutoff, ctf, lp
* @example * @example
* s("bd sd [~ bd] sd,hh*6").lpf("<4000 2000 1000 500 200 100>") * s("bd sd,hh*3").lpf("<4000 2000 1000 500 200 100>")
* @example * @example
* s("bd*16").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.
@@ -489,7 +476,7 @@ const generic_params = [
* @param {number | Pattern} modulation depth of the lowpass filter envelope between 0 and _n_ * @param {number | Pattern} modulation depth of the lowpass filter envelope between 0 and _n_
* @synonyms lpe * @synonyms lpe
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .lpf(500) * .lpf(500)
* .lpa(.5) * .lpa(.5)
@@ -502,7 +489,7 @@ const generic_params = [
* @param {number | Pattern} modulation depth of the highpass filter envelope between 0 and _n_ * @param {number | Pattern} modulation depth of the highpass filter envelope between 0 and _n_
* @synonyms hpe * @synonyms hpe
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .hpf(500) * .hpf(500)
* .hpa(.5) * .hpa(.5)
@@ -515,7 +502,7 @@ const generic_params = [
* @param {number | Pattern} modulation depth of the bandpass filter envelope between 0 and _n_ * @param {number | Pattern} modulation depth of the bandpass filter envelope between 0 and _n_
* @synonyms bpe * @synonyms bpe
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .bpf(500) * .bpf(500)
* .bpa(.5) * .bpa(.5)
@@ -528,7 +515,7 @@ const generic_params = [
* @param {number | Pattern} attack time of the filter envelope * @param {number | Pattern} attack time of the filter envelope
* @synonyms lpa * @synonyms lpa
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .lpf(500) * .lpf(500)
* .lpa("<.5 .25 .1 .01>/4") * .lpa("<.5 .25 .1 .01>/4")
@@ -541,7 +528,7 @@ const generic_params = [
* @param {number | Pattern} attack time of the highpass filter envelope * @param {number | Pattern} attack time of the highpass filter envelope
* @synonyms hpa * @synonyms hpa
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .hpf(500) * .hpf(500)
* .hpa("<.5 .25 .1 .01>/4") * .hpa("<.5 .25 .1 .01>/4")
@@ -554,7 +541,7 @@ const generic_params = [
* @param {number | Pattern} attack time of the bandpass filter envelope * @param {number | Pattern} attack time of the bandpass filter envelope
* @synonyms bpa * @synonyms bpa
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .bpf(500) * .bpf(500)
* .bpa("<.5 .25 .1 .01>/4") * .bpa("<.5 .25 .1 .01>/4")
@@ -567,7 +554,7 @@ const generic_params = [
* @param {number | Pattern} decay time of the filter envelope * @param {number | Pattern} decay time of the filter envelope
* @synonyms lpd * @synonyms lpd
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .lpf(500) * .lpf(500)
* .lpd("<.5 .25 .1 0>/4") * .lpd("<.5 .25 .1 0>/4")
@@ -581,7 +568,7 @@ const generic_params = [
* @param {number | Pattern} decay time of the highpass filter envelope * @param {number | Pattern} decay time of the highpass filter envelope
* @synonyms hpd * @synonyms hpd
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .hpf(500) * .hpf(500)
* .hpd("<.5 .25 .1 0>/4") * .hpd("<.5 .25 .1 0>/4")
@@ -595,7 +582,7 @@ const generic_params = [
* @param {number | Pattern} decay time of the bandpass filter envelope * @param {number | Pattern} decay time of the bandpass filter envelope
* @synonyms bpd * @synonyms bpd
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .bpf(500) * .bpf(500)
* .bpd("<.5 .25 .1 0>/4") * .bpd("<.5 .25 .1 0>/4")
@@ -609,7 +596,7 @@ const generic_params = [
* @param {number | Pattern} sustain amplitude of the lowpass filter envelope * @param {number | Pattern} sustain amplitude of the lowpass filter envelope
* @synonyms lps * @synonyms lps
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .lpf(500) * .lpf(500)
* .lpd(.5) * .lpd(.5)
@@ -623,7 +610,7 @@ const generic_params = [
* @param {number | Pattern} sustain amplitude of the highpass filter envelope * @param {number | Pattern} sustain amplitude of the highpass filter envelope
* @synonyms hps * @synonyms hps
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .hpf(500) * .hpf(500)
* .hpd(.5) * .hpd(.5)
@@ -637,7 +624,7 @@ const generic_params = [
* @param {number | Pattern} sustain amplitude of the bandpass filter envelope * @param {number | Pattern} sustain amplitude of the bandpass filter envelope
* @synonyms bps * @synonyms bps
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .bpf(500) * .bpf(500)
* .bpd(.5) * .bpd(.5)
@@ -651,7 +638,7 @@ const generic_params = [
* @param {number | Pattern} release time of the filter envelope * @param {number | Pattern} release time of the filter envelope
* @synonyms lpr * @synonyms lpr
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .clip(.5) * .clip(.5)
* .lpf(500) * .lpf(500)
@@ -666,7 +653,7 @@ const generic_params = [
* @param {number | Pattern} release time of the highpass filter envelope * @param {number | Pattern} release time of the highpass filter envelope
* @synonyms hpr * @synonyms hpr
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .clip(.5) * .clip(.5)
* .hpf(500) * .hpf(500)
@@ -681,7 +668,7 @@ const generic_params = [
* @param {number | Pattern} release time of the bandpass filter envelope * @param {number | Pattern} release time of the bandpass filter envelope
* @synonyms bpr * @synonyms bpr
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .clip(.5) * .clip(.5)
* .bpf(500) * .bpf(500)
@@ -695,11 +682,11 @@ const generic_params = [
* @name ftype * @name ftype
* @param {number | Pattern} type 12db (default) or 24db * @param {number | Pattern} type 12db (default) or 24db
* @example * @example
* note("c2 e2 f2 g2") * note("<c2 e2 f2 g2>")
* .sound('sawtooth') * .sound('sawtooth')
* .lpf(500) * .lpf(500)
* .bpenv(4) * .bpenv(4)
* .ftype("12db 24db") * .ftype("<12db 24db>")
*/ */
['ftype'], ['ftype'],
['fanchor'], ['fanchor'],
@@ -712,9 +699,9 @@ const generic_params = [
* @param {number | Pattern} frequency audible between 0 and 20000 * @param {number | Pattern} frequency audible between 0 and 20000
* @synonyms hp, hcutoff * @synonyms hp, hcutoff
* @example * @example
* s("bd sd [~ bd] sd,hh*8").hpf("<4000 2000 1000 500 200 100>") * s("bd sd,hh*4").hpf("<4000 2000 1000 500 200 100>")
* @example * @example
* s("bd sd [~ bd] sd,hh*8").hpf("<2000 2000:25>") * s("bd sd,hh*4").hpf("<2000 2000:25>")
* *
*/ */
// currently an alias of 'hcutoff' https://github.com/tidalcycles/strudel/issues/496 // currently an alias of 'hcutoff' https://github.com/tidalcycles/strudel/issues/496
@@ -726,11 +713,11 @@ const generic_params = [
* @synonyms vibrato, v * @synonyms vibrato, v
* @param {number | Pattern} frequency of the vibrato in hertz * @param {number | Pattern} frequency of the vibrato in hertz
* @example * @example
* note("a e") * note("a")
* .vib("<.5 1 2 4 8 16>") * .vib("<.5 1 2 4 8 16>")
* @example * @example
* // change the modulation depth with ":" * // change the modulation depth with ":"
* note("a e") * note("a")
* .vib("<.5 1 2 4 8 16>:12") * .vib("<.5 1 2 4 8 16>:12")
*/ */
[['vib', 'vibmod'], 'vibrato', 'v'], [['vib', 'vibmod'], 'vibrato', 'v'],
@@ -750,15 +737,15 @@ const generic_params = [
* @synonyms vmod * @synonyms vmod
* @param {number | Pattern} depth of vibrato (in semitones) * @param {number | Pattern} depth of vibrato (in semitones)
* @example * @example
* note("a e").vib(4) * note("a").vib(4)
* .vibmod("<.25 .5 1 2 12>") * .vibmod("<.25 .5 1 2 12>")
* @example * @example
* // change the vibrato frequency with ":" * // change the vibrato frequency with ":"
* note("a e") * note("a")
* .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.
* *
@@ -766,7 +753,7 @@ const generic_params = [
* @param {number | Pattern} q resonance factor between 0 and 50 * @param {number | Pattern} q resonance factor between 0 and 50
* @synonyms hresonance * @synonyms hresonance
* @example * @example
* s("bd sd [~ bd] sd,hh*8").hpf(2000).hpq("<0 10 20 30>") * s("bd sd,hh*4").hpf(2000).hpq("<0 10 20 30>")
* *
*/ */
['hresonance', 'hpq'], ['hresonance', 'hpq'],
@@ -777,7 +764,7 @@ const generic_params = [
* @param {number | Pattern} q resonance factor between 0 and 50 * @param {number | Pattern} q resonance factor between 0 and 50
* @synonyms resonance * @synonyms resonance
* @example * @example
* s("bd sd [~ bd] sd,hh*8").lpf(2000).lpq("<0 10 20 30>") * s("bd sd,hh*4").lpf(2000).lpq("<0 10 20 30>")
* *
*/ */
// currently an alias of 'resonance' https://github.com/tidalcycles/strudel/issues/496 // currently an alias of 'resonance' https://github.com/tidalcycles/strudel/issues/496
@@ -804,7 +791,7 @@ const generic_params = [
* @name delay * @name delay
* @param {number | Pattern} level between 0 and 1 * @param {number | Pattern} level between 0 and 1
* @example * @example
* s("bd bd").delay("<0 .25 .5 1>") * s("bd").delay("<0 .25 .5 1>")
* @example * @example
* s("bd bd").delay("0.65:0.25:0.9 0.65:0.125:0.7") * s("bd bd").delay("0.65:0.25:0.9 0.65:0.125:0.7")
* *
@@ -818,7 +805,7 @@ const generic_params = [
* @param {number | Pattern} feedback between 0 and 1 * @param {number | Pattern} feedback between 0 and 1
* @synonyms delayfb, dfb * @synonyms delayfb, dfb
* @example * @example
* s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>") * s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>").slow(2)
* *
*/ */
['delayfeedback', 'delayfb', 'dfb'], ['delayfeedback', 'delayfb', 'dfb'],
@@ -829,7 +816,7 @@ const generic_params = [
* @param {number | Pattern} seconds between 0 and Infinity * @param {number | Pattern} seconds between 0 and Infinity
* @synonyms delayt, dt * @synonyms delayt, dt
* @example * @example
* s("bd bd").delay(.25).delaytime("<.125 .25 .5 1>") * s("bd").delay(.25).delaytime("<.125 .25 .5 1>").slow(2)
* *
*/ */
['delaytime', 'delayt', 'dt'], ['delaytime', 'delayt', 'dt'],
@@ -856,7 +843,7 @@ const generic_params = [
*/ */
['detune', 'det'], ['detune', 'det'],
/** /**
* Set dryness of reverb. See `room` and `size` for more information about reverb. * Set dryness of reverb. See {@link room} and {@link size} for more information about reverb.
* *
* @name dry * @name dry
* @param {number | Pattern} dry 0 = wet, 1 = dry * @param {number | Pattern} dry 0 = wet, 1 = dry
@@ -868,7 +855,7 @@ const generic_params = [
['dry'], ['dry'],
// TODO: does not seem to do anything // TODO: does not seem to do anything
/* /*
* Used when using `begin`/`end` or `chop`/`striate` and friends, to change the fade out time of the 'grain' envelope. * Used when using {@link begin}/{@link end} or {@link chop}/{@link striate} and friends, to change the fade out time of the 'grain' envelope.
* *
* @name fadeTime * @name fadeTime
* @param {number | Pattern} time between 0 and 1 * @param {number | Pattern} time between 0 and 1
@@ -891,82 +878,6 @@ const generic_params = [
* *
*/ */
['freq'], ['freq'],
// pitch envelope
/**
* Attack time of pitch envelope.
*
* @name pattack
* @synonyms patt
* @param {number | Pattern} time time in seconds
* @example
* note("c eb g bb").pattack("0 .1 .25 .5").slow(2)
*
*/
['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*4")
* .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 c4").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'],
@@ -1062,8 +973,8 @@ const generic_params = [
* @param {number | Pattern} number * @param {number | Pattern} number
* @example * @example
* stack( * stack(
* s("hh*6").delay(.5).delaytime(.25).orbit(1), * s("hh*3").delay(.5).delaytime(.25).orbit(1),
* s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2) * s("~ sd").delay(.5).delaytime(.125).orbit(2)
* ) * )
*/ */
['orbit'], ['orbit'],
@@ -1076,8 +987,6 @@ const generic_params = [
* @param {number | Pattern} pan between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel) * @param {number | Pattern} pan between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel)
* @example * @example
* s("[bd hh]*2").pan("<.5 1 .5 0>") * s("[bd hh]*2").pan("<.5 1 .5 0>")
* @example
* s("bd rim sd rim bd ~ cp rim").pan(sine.slow(2))
* *
*/ */
['pan'], ['pan'],
@@ -1135,9 +1044,9 @@ const generic_params = [
* @name room * @name room
* @param {number | Pattern} level between 0 and 1 * @param {number | Pattern} level between 0 and 1
* @example * @example
* s("bd sd [~ bd] sd").room("<0 .2 .4 .6 .8 1>") * s("bd sd").room("<0 .2 .4 .6 .8 1>")
* @example * @example
* s("bd sd [~ bd] sd").room("<0.9:1 0.9:4>") * s("bd sd").room("<0.9:1 0.9:4>")
* *
*/ */
[['room', 'size']], [['room', 'size']],
@@ -1149,9 +1058,9 @@ const generic_params = [
* @synonyms rlp * @synonyms rlp
* @param {number} frequency between 0 and 20000hz * @param {number} frequency between 0 and 20000hz
* @example * @example
* s("bd sd [~ bd] sd").room(0.5).rlp(10000) * s("bd sd").room(0.5).rlp(10000)
* @example * @example
* s("bd sd [~ bd] sd").room(0.5).rlp(5000) * s("bd sd").room(0.5).rlp(5000)
*/ */
['roomlp', 'rlp'], ['roomlp', 'rlp'],
/** /**
@@ -1162,9 +1071,9 @@ const generic_params = [
* @synonyms rdim * @synonyms rdim
* @param {number} frequency between 0 and 20000hz * @param {number} frequency between 0 and 20000hz
* @example * @example
* s("bd sd [~ bd] sd").room(0.5).rlp(10000).rdim(8000) * s("bd sd").room(0.5).rlp(10000).rdim(8000)
* @example * @example
* s("bd sd [~ bd] sd").room(0.5).rlp(5000).rdim(400) * s("bd sd").room(0.5).rlp(5000).rdim(400)
* *
*/ */
['roomdim', 'rdim'], ['roomdim', 'rdim'],
@@ -1176,9 +1085,9 @@ const generic_params = [
* @synonyms rfade * @synonyms rfade
* @param {number} seconds for the reverb to fade * @param {number} seconds for the reverb to fade
* @example * @example
* s("bd sd [~ bd] sd").room(0.5).rlp(10000).rfade(0.5) * s("bd sd").room(0.5).rlp(10000).rfade(0.5)
* @example * @example
* s("bd sd [~ bd] sd").room(0.5).rlp(5000).rfade(4) * s("bd sd").room(0.5).rlp(5000).rfade(4)
* *
*/ */
['roomfade', 'rfade'], ['roomfade', 'rfade'],
@@ -1188,25 +1097,25 @@ const generic_params = [
* @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
* s("bd sd [~ bd] sd").room(.8).ir("<shaker_large:0 shaker_large:2>") * s("bd sd").room(.8).ir("<shaker_large:0 shaker_large:2>")
* *
*/ */
[['ir', 'i'], 'iresponse'], [['ir', 'i'], 'iresponse'],
/** /**
* Sets the room size of the reverb, see `room`. * Sets the room size of the reverb, see {@link room}.
* When this property is changed, the reverb will be recaculated, so only change this sparsely.. * When this property is changed, the reverb will be recaculated, so only change this sparsely..
* *
* @name roomsize * @name roomsize
* @param {number | Pattern} size between 0 and 10 * @param {number | Pattern} size between 0 and 10
* @synonyms rsize, sz, size * @synonyms rsize, sz, size
* @example * @example
* s("bd sd [~ bd] sd").room(.8).rsize(1) * s("bd sd").room(.8).rsize(1)
* @example * @example
* s("bd sd [~ bd] sd").room(.8).rsize(4) * s("bd sd").room(.8).rsize(4)
* *
*/ */
// TODO: find out why : // TODO: find out why :
// s("bd sd [~ bd] sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc() // s("bd sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc()
// .. does not work. Is it because room is only one effect? // .. does not work. Is it because room is only one effect?
['roomsize', 'size', 'sz', 'rsize'], ['roomsize', 'size', 'sz', 'rsize'],
// ['sagogo'], // ['sagogo'],
@@ -1219,7 +1128,7 @@ const generic_params = [
* @name shape * @name shape
* @param {number | Pattern} distortion between 0 and 1 * @param {number | Pattern} distortion between 0 and 1
* @example * @example
* s("bd sd [~ bd] sd,hh*8").shape("<0 .2 .4 .6 .8>") * s("bd sd,hh*4").shape("<0 .2 .4 .6 .8>")
* *
*/ */
['shape'], ['shape'],
@@ -1229,7 +1138,7 @@ const generic_params = [
* *
* @name compressor * @name compressor
* @example * @example
* s("bd sd [~ bd] sd,hh*8") * s("bd sd,hh*4")
* .compressor("-20:20:10:.002:.02") * .compressor("-20:20:10:.002:.02")
* *
*/ */
@@ -1244,14 +1153,14 @@ const generic_params = [
* @name speed * @name speed
* @param {number | Pattern} speed -inf to inf, negative numbers play the sample backwards. * @param {number | Pattern} speed -inf to inf, negative numbers play the sample backwards.
* @example * @example
* s("bd*6").speed("1 2 4 1 -2 -4") * s("bd").speed("<1 2 4 1 -2 -4>")
* @example * @example
* speed("1 1.5*2 [2 1.1]").s("piano").clip(1) * speed("1 1.5*2 [2 1.1]").s("piano").clip(1)
* *
*/ */
['speed'], ['speed'],
/** /**
* Used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`. * Used in conjunction with {@link speed}, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`.
* *
* @name unit * @name unit
* @param {number | string | Pattern} unit see description above * @param {number | string | Pattern} unit see description above
@@ -1287,12 +1196,10 @@ 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>>]*2").s('sawtooth') * note("c2 <eb2 <g2 g1>>").s('sawtooth')
* .vowel("<a e i <o u>>") * .vowel("<a e i <o u>>")
* @example
* s("bd sd mt ht bd [~ cp] ht lt").vowel("[a|e|i|o|u]")
* *
*/ */
['vowel'], ['vowel'],
@@ -1307,16 +1214,6 @@ const generic_params = [
* @name waveloss * @name waveloss
*/ */
['waveloss'], ['waveloss'],
/*
* Noise crackle density
*
* @name density
* @param {number | Pattern} density between 0 and x
* @example
* s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4))
*
*/
['density'],
// TODO: midi effects? // TODO: midi effects?
['dur'], ['dur'],
// ['modwheel'], // ['modwheel'],
@@ -1467,27 +1364,17 @@ controls.createParams = (...names) =>
* @param {number | Pattern} gain sustain level (0 to 1) * @param {number | Pattern} gain sustain level (0 to 1)
* @param {number | Pattern} time release time in seconds * @param {number | Pattern} time release time in seconds
* @example * @example
* note("[c3 bb2 f3 eb3]*2").sound("sawtooth").lpf(600).adsr(".1:.1:.5:.2") * note("<c3 bb2 f3 eb3>").sound("sawtooth").lpf(600).adsr(".1:.1:.5:.2")
*/ */
controls.adsr = register('adsr', (adsr, pat) => { controls.adsr = register('adsr', (adsr, pat) => {
adsr = !Array.isArray(adsr) ? [adsr] : adsr; adsr = !Array.isArray(adsr) ? [adsr] : adsr;
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;
+6 -23
View File
@@ -10,15 +10,14 @@ import { logger } from './logger.mjs';
export class Cyclist { 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 = 0.5; 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;
const haps = this.pattern.queryArc(begin, end);
// query the pattern for events const tickdeadline = phase - time; // time left till phase begins
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps });
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.');
} }
@@ -96,12 +83,8 @@ export class Cyclist {
this.start(); this.start();
} }
} }
setCps(cps = 0.5) { 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());
+6 -10
View File
@@ -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
@@ -134,7 +134,7 @@ export class Drawer {
this.lastFrame = phase; this.lastFrame = phase;
this.visibleHaps = (this.visibleHaps || []) this.visibleHaps = (this.visibleHaps || [])
// filter out haps that are too far in the past (think left edge of screen for pianoroll) // filter out haps that are too far in the past (think left edge of screen for pianoroll)
.filter((h) => h.whole?.end >= phase - lookbehind - lookahead) .filter((h) => h.whole.end >= phase - lookbehind - lookahead)
// add new haps with onset (think right edge bars scrolling in) // add new haps with onset (think right edge bars scrolling in)
.concat(haps.filter((h) => h.hasOnset())); .concat(haps.filter((h) => h.hasOnset()));
const time = phase - lookahead; const time = phase - lookahead;
@@ -145,16 +145,12 @@ export class Drawer {
}, },
); );
} }
setDrawTime(drawTime) { invalidate(scheduler = this.scheduler) {
this.drawTime = drawTime; if (!scheduler || !scheduler.pattern) {
}
invalidate(scheduler = this.scheduler, t) {
if (!scheduler) {
return; return;
} }
// TODO: scheduler.now() seems to move even when it's stopped, this hints at a bug...
t = t ?? scheduler.now();
this.scheduler = scheduler; this.scheduler = scheduler;
const t = scheduler.now();
let [_, lookahead] = this.drawTime; let [_, lookahead] = this.drawTime;
const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1]; const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1];
// remove all future haps // remove all future haps
+1 -15
View File
@@ -145,10 +145,8 @@ 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
* note("c3").euclidLegato(3,8) * n("g2").decay(.1).sustain(.3).euclidLegato(3,8)
*/ */
const _euclidLegato = function (pulses, steps, rotation, pat) { const _euclidLegato = function (pulses, steps, rotation, pat) {
@@ -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);
}); });
+5
View File
@@ -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 };
}; };
@@ -7,7 +7,7 @@
/> />
<div id="output"></div> <div id="output"></div>
<script type="module"> <script type="module">
const strudel = await import('https://cdn.skypack.dev/@strudel/core@0.6.8'); const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8');
Object.assign(window, strudel); // assign all strudel functions to global scope to use with eval Object.assign(window, strudel); // assign all strudel functions to global scope to use with eval
const input = document.getElementById('text'); const input = document.getElementById('text');
const getEvents = () => { const getEvents = () => {
@@ -8,7 +8,7 @@
/> />
<canvas id="canvas"></canvas> <canvas id="canvas"></canvas>
<script type="module"> <script type="module">
const strudel = await import('https://cdn.skypack.dev/@strudel/core@0.6.8'); const strudel = await import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8');
// this adds all strudel functions to the global scope, to be used by eval // this adds all strudel functions to the global scope, to be used by eval
Object.assign(window, strudel); Object.assign(window, strudel);
// setup elements // setup elements
@@ -1,4 +1,4 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@@ -16,40 +16,34 @@
</div> </div>
<div id="output"></div> <div id="output"></div>
<script type="module"> <script type="module">
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel/core@0.11.0'; import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel.cycles/core@0.6.8';
import { mini } from 'https://cdn.skypack.dev/@strudel/mini@0.11.0'; import { mini } from 'https://cdn.skypack.dev/@strudel.cycles/mini@0.6.0';
import { transpiler } from 'https://cdn.skypack.dev/@strudel/transpiler@0.11.0'; import { transpiler } from 'https://cdn.skypack.dev/@strudel.cycles/transpiler@0.6.0';
import { import {
getAudioContext, getAudioContext,
webaudioOutput, webaudioOutput,
initAudioOnFirstClick, initAudioOnFirstClick,
registerSynthSounds, } from 'https://cdn.skypack.dev/@strudel.cycles/webaudio@0.6.0';
} from 'https://cdn.skypack.dev/@strudel/webaudio@0.11.0';
initAudioOnFirstClick();
const ctx = getAudioContext(); const ctx = getAudioContext();
const input = document.getElementById('text'); const input = document.getElementById('text');
input.innerHTML = getTune(); input.innerHTML = getTune();
const loadModules = evalScope( evalScope(
controls, controls,
import('https://cdn.skypack.dev/@strudel/core@0.11.0'), import('https://cdn.skypack.dev/@strudel.cycles/core@0.6.8'),
import('https://cdn.skypack.dev/@strudel/mini@0.11.0'), import('https://cdn.skypack.dev/@strudel.cycles/mini@0.6.0'),
import('https://cdn.skypack.dev/@strudel/tonal@0.11.0'), import('https://cdn.skypack.dev/@strudel.cycles/tonal@0.6.0'),
import('https://cdn.skypack.dev/@strudel/webaudio@0.11.0'), import('https://cdn.skypack.dev/@strudel.cycles/webaudio@0.6.0'),
); );
const initAudio = Promise.all([initAudioOnFirstClick(), registerSynthSounds()]);
const { evaluate } = repl({ const { evaluate } = repl({
defaultOutput: webaudioOutput, defaultOutput: webaudioOutput,
getTime: () => ctx.currentTime, getTime: () => ctx.currentTime,
transpiler, transpiler,
}); });
document.getElementById('start').addEventListener('click', async () => { document.getElementById('start').addEventListener('click', () => evaluate(input.value));
await loadModules;
await initAudio;
evaluate(input.value);
});
function getTune() { function getTune() {
return `await samples('github:tidalcycles/Dirt-Samples/master') return `await samples('github:tidalcycles/Dirt-Samples/master')
@@ -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,4 +1,4 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@@ -1,11 +1,11 @@
import { StrudelMirror } from '@strudel/codemirror'; import { StrudelMirror } from '@strudel/codemirror';
import { funk42 } from './tunes'; import { funk42 } from './tunes';
import { drawPianoroll, evalScope, controls } from '@strudel/core'; import { drawPianoroll, evalScope, controls } from '@strudel.cycles/core';
import './style.css'; import './style.css';
import { initAudioOnFirstClick } from '@strudel/webaudio'; import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import { transpiler } from '@strudel/transpiler'; import { transpiler } from '@strudel.cycles/transpiler';
import { getAudioContext, webaudioOutput, registerSynthSounds } from '@strudel/webaudio'; import { getAudioContext, webaudioOutput, registerSynthSounds } from '@strudel.cycles/webaudio';
import { registerSoundfonts } from '@strudel/soundfonts'; import { registerSoundfonts } from '@strudel.cycles/soundfonts';
// init canvas // init canvas
const canvas = document.getElementById('roll'); const canvas = document.getElementById('roll');
@@ -26,10 +26,10 @@ const editor = new StrudelMirror({
initAudioOnFirstClick(); // needed to make the browser happy (don't await this here..) initAudioOnFirstClick(); // needed to make the browser happy (don't await this here..)
const loadModules = evalScope( const loadModules = evalScope(
controls, controls,
import('@strudel/core'), import('@strudel.cycles/core'),
import('@strudel/mini'), import('@strudel.cycles/mini'),
import('@strudel/tonal'), import('@strudel.cycles/tonal'),
import('@strudel/webaudio'), import('@strudel.cycles/webaudio'),
); );
await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]); await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]);
}, },
@@ -0,0 +1,23 @@
{
"name": "vite-vanilla-repl-cm6",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^4.3.2"
},
"dependencies": {
"@strudel/codemirror": "workspace:*",
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/soundfonts": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "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,4 +1,4 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@@ -1,6 +1,6 @@
import { controls, repl, evalScope } from '@strudel/core'; import { controls, repl, evalScope } from '@strudel.cycles/core';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio'; import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import { transpiler } from '@strudel/transpiler'; import { transpiler } from '@strudel.cycles/transpiler';
import tune from './tune.mjs'; import tune from './tune.mjs';
const ctx = getAudioContext(); const ctx = getAudioContext();
@@ -10,10 +10,10 @@ initAudioOnFirstClick();
evalScope( evalScope(
controls, controls,
import('@strudel/core'), import('@strudel.cycles/core'),
import('@strudel/mini'), import('@strudel.cycles/mini'),
import('@strudel/webaudio'), import('@strudel.cycles/webaudio'),
import('@strudel/tonal'), import('@strudel.cycles/tonal'),
); );
const { evaluate } = repl({ const { evaluate } = repl({
@@ -10,13 +10,13 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^4.3.3"
}, },
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@strudel/mini": "workspace:*", "@strudel.cycles/mini": "workspace:*",
"@strudel/transpiler": "workspace:*", "@strudel.cycles/transpiler": "workspace:*",
"@strudel/webaudio": "workspace:*", "@strudel.cycles/webaudio": "workspace:*",
"@strudel/tonal": "workspace:*" "@strudel.cycles/tonal": "workspace:*"
} }
} }
+12
View File
@@ -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));
+4 -3
View File
@@ -27,15 +27,16 @@ 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; */
logger('🌀 @strudel/core loaded 🌀'); logger('🌀 @strudel.cycles/core loaded 🌀');
if (globalThis._strudelLoaded) { if (globalThis._strudelLoaded) {
console.warn( console.warn(
`@strudel/core was loaded more than once... `@strudel.cycles/core was loaded more than once...
This might happen when you have multiple versions of strudel installed. This might happen when you have multiple versions of strudel installed.
Please check with "npm ls @strudel/core".`, Please check with "npm ls @strudel.cycles/core".`,
); );
} }
globalThis._strudelLoaded = true; globalThis._strudelLoaded = true;
+6 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/core", "name": "@strudel.cycles/core",
"version": "1.0.0", "version": "0.9.0",
"description": "Port of Tidal Cycles to JavaScript", "description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
@@ -31,11 +31,12 @@
}, },
"homepage": "https://strudel.cc", "homepage": "https://strudel.cc",
"dependencies": { "dependencies": {
"fraction.js": "^4.3.7" "fraction.js": "^4.2.0",
"nanostores": "^0.8.1"
}, },
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2", "gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
"devDependencies": { "devDependencies": {
"vite": "^5.0.10", "vite": "^4.3.3",
"vitest": "^1.1.0" "vitest": "^0.33.0"
} }
} }
+97 -126
View File
@@ -26,7 +26,7 @@ export class Pattern {
/** /**
* Create a pattern. As an end user, you will most likely not create a Pattern directly. * Create a pattern. As an end user, you will most likely not create a Pattern directly.
* *
* @param {function} query - The function that maps a `State` to an array of `Hap`. * @param {function} query - The function that maps a {@link State} to an array of {@link Hap}.
* @noAutocomplete * @noAutocomplete
*/ */
constructor(query) { constructor(query) {
@@ -39,7 +39,7 @@ export class Pattern {
/** /**
* Returns a new pattern, with the function applied to the value of * Returns a new pattern, with the function applied to the value of
* each hap. It has the alias `fmap`. * each hap. It has the alias {@link Pattern#fmap}.
* @synonyms fmap * @synonyms fmap
* @param {Function} func to to apply to the value * @param {Function} func to to apply to the value
* @returns Pattern * @returns Pattern
@@ -51,7 +51,7 @@ export class Pattern {
} }
/** /**
* see `withValue` * see {@link Pattern#withValue}
* @noAutocomplete * @noAutocomplete
*/ */
fmap(func) { fmap(func) {
@@ -115,7 +115,7 @@ export class Pattern {
} }
/** /**
* As with `appBoth`, but the `whole` timespan is not the intersection, * As with {@link Pattern#appBoth}, but the `whole` timespan is not the intersection,
* but the timespan from the function of patterns that this method is called * but the timespan from the function of patterns that this method is called
* on. In practice, this means that the pattern structure, including onsets, * on. In practice, this means that the pattern structure, including onsets,
* are preserved from the pattern of functions (often referred to as the left * are preserved from the pattern of functions (often referred to as the left
@@ -148,7 +148,7 @@ export class Pattern {
} }
/** /**
* As with `appLeft`, but `whole` timespans are instead taken from the * As with {@link Pattern#appLeft}, but `whole` timespans are instead taken from the
* pattern of values, i.e. structure is preserved from the right hand/outer * pattern of values, i.e. structure is preserved from the right hand/outer
* pattern. * pattern.
* @param {Pattern} pat_val * @param {Pattern} pat_val
@@ -340,9 +340,9 @@ export class Pattern {
* silence * silence
* @noAutocomplete * @noAutocomplete
*/ */
queryArc(begin, end, controls = {}) { queryArc(begin, end) {
try { try {
return this.query(new State(new TimeSpan(begin, end), controls)); return this.query(new State(new TimeSpan(begin, end)));
} catch (err) { } catch (err) {
logger(`[query]: ${err.message}`, 'error'); logger(`[query]: ${err.message}`, 'error');
return []; return [];
@@ -387,7 +387,7 @@ export class Pattern {
} }
/** /**
* As with `withQuerySpan`, but the function is applied to both the * As with {@link Pattern#withQuerySpan}, but the function is applied to both the
* begin and end time of the query timespan. * begin and end time of the query timespan.
* @param {Function} func the function to apply * @param {Function} func the function to apply
* @returns Pattern * @returns Pattern
@@ -398,7 +398,7 @@ export class Pattern {
} }
/** /**
* Similar to `withQuerySpan`, but the function is applied to the timespans * Similar to {@link Pattern#withQuerySpan}, but the function is applied to the timespans
* of all haps returned by pattern queries (both `part` timespans, and where * of all haps returned by pattern queries (both `part` timespans, and where
* present, `whole` timespans). * present, `whole` timespans).
* @param {Function} func * @param {Function} func
@@ -410,7 +410,7 @@ export class Pattern {
} }
/** /**
* As with `withHapSpan`, but the function is applied to both the * As with {@link Pattern#withHapSpan}, but the function is applied to both the
* begin and end time of the hap timespans. * begin and end time of the hap timespans.
* @param {Function} func the function to apply * @param {Function} func the function to apply
* @returns Pattern * @returns Pattern
@@ -427,11 +427,11 @@ export class Pattern {
* @noAutocomplete * @noAutocomplete
*/ */
withHaps(func) { withHaps(func) {
return new Pattern((state) => func(this.query(state), state)); return new Pattern((state) => func(this.query(state)));
} }
/** /**
* As with `withHaps`, but applies the function to every hap, rather than every list of haps. * As with {@link Pattern#withHaps}, but applies the function to every hap, rather than every list of haps.
* @param {Function} func * @param {Function} func
* @returns Pattern * @returns Pattern
* @noAutocomplete * @noAutocomplete
@@ -499,7 +499,7 @@ export class Pattern {
} }
/** /**
* As with `filterHaps`, but the function is applied to values * As with {@link Pattern#filterHaps}, but the function is applied to values
* inside haps. * inside haps.
* @param {Function} value_test * @param {Function} value_test
* @returns Pattern * @returns Pattern
@@ -621,7 +621,7 @@ export class Pattern {
} }
/** /**
* More human-readable version of the `firstCycleValues` accessor. * More human-readable version of the {@link Pattern#firstCycleValues} accessor.
* @noAutocomplete * @noAutocomplete
*/ */
get showFirstCycle() { get showFirstCycle() {
@@ -691,13 +691,13 @@ export class Pattern {
// Methods without corresponding toplevel functions // Methods without corresponding toplevel functions
/** /**
* Layers the result of the given function(s). Like `superimpose`, but without the original pattern: * Layers the result of the given function(s). Like {@link Pattern.superimpose}, but without the original pattern:
* @name layer * @name layer
* @memberof Pattern * @memberof Pattern
* @synonyms apply * @synonyms apply
* @returns Pattern * @returns Pattern
* @example * @example
* "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*8" * "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*4"
* .layer(x=>x.add("0,2")) * .layer(x=>x.add("0,2"))
* .scale('C minor').note() * .scale('C minor').note()
*/ */
@@ -711,7 +711,7 @@ export class Pattern {
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @example * @example
* "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*8" * "<0 2 4 6 ~ 4 ~ 2 0!3 ~!5>*4"
* .superimpose(x=>x.add(2)) * .superimpose(x=>x.add(2))
* .scale('C minor').note() * .scale('C minor').note()
*/ */
@@ -727,8 +727,8 @@ export class Pattern {
* @name stack * @name stack
* @memberof Pattern * @memberof Pattern
* @example * @example
* s("hh*4").stack( * s("hh*2").stack(
* note("c4(5,8)") * note("c2(3,8)")
* ) * )
*/ */
stack(...pats) { stack(...pats) {
@@ -745,8 +745,8 @@ export class Pattern {
* @memberof Pattern * @memberof Pattern
* @synonyms sequence, fastcat * @synonyms sequence, fastcat
* @example * @example
* s("hh*4").seq( * s("hh*2").seq(
* note("c4(5,8)") * note("c2(3,8)")
* ) * )
*/ */
seq(...pats) { seq(...pats) {
@@ -759,8 +759,8 @@ export class Pattern {
* @memberof Pattern * @memberof Pattern
* @synonyms slowcat * @synonyms slowcat
* @example * @example
* s("hh*4").cat( * s("hh*2").cat(
* note("c4(5,8)") * note("c2(3,8)")
* ) * )
*/ */
cat(...pats) { cat(...pats) {
@@ -858,7 +858,7 @@ Pattern.prototype.arpWith = function (func) {
* Selects indices in in stacked notes. * Selects indices in in stacked notes.
* @example * @example
* note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>") * note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>")
* .arp("0 [0,2] 1 [0,2]") * .arp("0 [0,2] 1 [0,2]").slow(2)
* */ * */
Pattern.prototype.arp = function (pat) { Pattern.prototype.arp = function (pat) {
return this.arpWith((haps) => pat.fmap((i) => haps[i % haps.length])); return this.arpWith((haps) => pat.fmap((i) => haps[i % haps.length]));
@@ -929,14 +929,14 @@ function _composeOp(a, b, func) {
* @memberof Pattern * @memberof Pattern
* @example * @example
* // Here, the triad 0, 2, 4 is shifted by different amounts * // Here, the triad 0, 2, 4 is shifted by different amounts
* n("0 2 4".add("<0 3 4 0>")).scale("C:major") * "0 2 4".add("<0 3 4 0>").scale('C major').note()
* // Without add, the equivalent would be: * // Without add, the equivalent would be:
* // n("<[0 2 4] [3 5 7] [4 6 8] [0 2 4]>").scale("C:major") * // "<[0 2 4] [3 5 7] [4 6 8] [0 2 4]>".scale('C major').note()
* @example * @example
* // You can also use add with notes: * // You can also use add with notes:
* note("c3 e3 g3".add("<0 5 7 0>")) * "c3 e3 g3".add("<0 5 7 0>").note()
* // Behind the scenes, the notes are converted to midi numbers: * // Behind the scenes, the notes are converted to midi numbers:
* // note("48 52 55".add("<0 5 7 0>")) * // "48 52 55".add("<0 5 7 0>").note()
*/ */
add: [numeralArgs((a, b) => a + b)], // support string concatenation add: [numeralArgs((a, b) => a + b)], // support string concatenation
/** /**
@@ -945,7 +945,7 @@ function _composeOp(a, b, func) {
* @name sub * @name sub
* @memberof Pattern * @memberof Pattern
* @example * @example
* n("0 2 4".sub("<0 1 2 3>")).scale("C4:minor") * "0 2 4".sub("<0 1 2 3>").scale('C4 minor').note()
* // See add for more information. * // See add for more information.
*/ */
sub: [numeralArgs((a, b) => a - b)], sub: [numeralArgs((a, b) => a - b)],
@@ -955,7 +955,7 @@ function _composeOp(a, b, func) {
* @name mul * @name mul
* @memberof Pattern * @memberof Pattern
* @example * @example
* "<1 1.5 [1.66, <2 2.33>]>*4".mul(150).freq() * "1 1.5 [1.66, <2 2.33>]".mul(150).freq()
*/ */
mul: [numeralArgs((a, b) => a * b)], mul: [numeralArgs((a, b) => a * b)],
/** /**
@@ -1049,9 +1049,9 @@ function _composeOp(a, b, func) {
* Applies the given structure to the pattern: * Applies the given structure to the pattern:
* *
* @example * @example
* note("c,eb,g") * note("c3,eb3,g3")
* .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~") * .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~")
* .slow(2) * .slow(4)
*/ */
Pattern.prototype.struct = function (...args) { Pattern.prototype.struct = function (...args) {
return this.keepif.out(...args); return this.keepif.out(...args);
@@ -1063,7 +1063,7 @@ function _composeOp(a, b, func) {
* Returns silence when mask is 0 or "~" * Returns silence when mask is 0 or "~"
* *
* @example * @example
* note("c [eb,g] d [eb,g]").mask("<1 [0 1]>") * note("c [eb,g] d [eb,g]").mask("<1 [0 1]>").slow(2)
*/ */
Pattern.prototype.mask = function (...args) { Pattern.prototype.mask = function (...args) {
return this.keepif.in(...args); return this.keepif.in(...args);
@@ -1075,7 +1075,7 @@ function _composeOp(a, b, func) {
* Resets the pattern to the start of the cycle for each onset of the reset pattern. * Resets the pattern to the start of the cycle for each onset of the reset pattern.
* *
* @example * @example
* s("[<bd lt> sd]*2, hh*8").reset("<x@3 x(5,8)>") * s("<bd lt> sd, hh*4").reset("<x@3 x(3,8)>")
*/ */
Pattern.prototype.reset = function (...args) { Pattern.prototype.reset = function (...args) {
return this.keepif.trig(...args); return this.keepif.trig(...args);
@@ -1088,7 +1088,7 @@ function _composeOp(a, b, func) {
* While reset will only reset the current cycle, restart will start from cycle 0. * While reset will only reset the current cycle, restart will start from cycle 0.
* *
* @example * @example
* s("[<bd lt> sd]*2, hh*8").restart("<x@3 x(5,8)>") * s("<bd lt> sd, hh*4").restart("<x@3 x(3,8)>")
*/ */
Pattern.prototype.restart = function (...args) { Pattern.prototype.restart = function (...args) {
return this.keepif.trigzero(...args); return this.keepif.trigzero(...args);
@@ -1154,8 +1154,8 @@ export function isPattern(thing) {
/* if (!thing instanceof Pattern) { /* if (!thing instanceof Pattern) {
console.warn( console.warn(
`Found Pattern that fails "instanceof Pattern" check. `Found Pattern that fails "instanceof Pattern" check.
This may happen if you are using multiple versions of @strudel/core. This may happen if you are using multiple versions of @strudel.cycles/core.
Please check by running "npm ls @strudel/core".`, Please check by running "npm ls @strudel.cycles/core".`,
); );
console.log(thing); console.log(thing);
} */ } */
@@ -1178,8 +1178,7 @@ export function reify(thing) {
* @return {Pattern} * @return {Pattern}
* @synonyms polyrhythm, pr * @synonyms polyrhythm, pr
* @example * @example
* stack("g3", "b3", ["e4", "d4"]).note() * stack(g3, b3, [e4, d4]).note() // "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..
@@ -1190,11 +1189,11 @@ export function stack(...pats) {
/** Concatenation: combines a list of patterns, switching between them successively, one per cycle: /** Concatenation: combines a list of patterns, switching between them successively, one per cycle:
* *
* synonyms: `cat` * synonyms: {@link cat}
* *
* @return {Pattern} * @return {Pattern}
* @example * @example
* slowcat("e5", "b4", ["d5", "c5"]) * slowcat(e5, b4, [d5, c5])
* *
*/ */
export function slowcat(...pats) { export function slowcat(...pats) {
@@ -1238,19 +1237,17 @@ export function slowcatPrime(...pats) {
* @synonyms slowcat * @synonyms slowcat
* @return {Pattern} * @return {Pattern}
* @example * @example
* cat("e5", "b4", ["d5", "c5"]).note() * cat(e5, b4, [d5, c5]).note() // "<e5 b4 [d5 c5]>".note()
* // "<e5 b4 [d5 c5]>".note()
* *
*/ */
export function cat(...pats) { export function cat(...pats) {
return slowcat(...pats); return slowcat(...pats);
} }
/** Like `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() * timeCat([3,e3],[1, g3]).note() // "e3@3 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));
@@ -1270,10 +1267,7 @@ export function timeCat(...timepats) {
* *
* @return {Pattern} * @return {Pattern}
* @example * @example
* arrange( * arrange([4, "<c a f e>(3,8)"],[2, "<g a>(5,8)"]).note()
* [4, "<c a f e>(3,8)"],
* [2, "<g a>(5,8)"]
* ).note()
*/ */
export function arrange(...sections) { export function arrange(...sections) {
const total = sections.reduce((sum, [cycles]) => sum + cycles, 0); const total = sections.reduce((sum, [cycles]) => sum + cycles, 0);
@@ -1285,7 +1279,7 @@ export function fastcat(...pats) {
return slowcat(...pats)._fast(pats.length); return slowcat(...pats)._fast(pats.length);
} }
/** See `fastcat` */ /** See {@link fastcat} */
export function sequence(...pats) { export function sequence(...pats) {
return fastcat(...pats); return fastcat(...pats);
} }
@@ -1293,8 +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() * seq(e5, b4, [d5, c5]).note() // "e5 b4 [d5 c5]".note()
* // "e5 b4 [d5 c5]".note()
* *
*/ */
export function seq(...pats) { export function seq(...pats) {
@@ -1320,9 +1313,9 @@ function _sequenceCount(x) {
* @param {number} steps how many items are placed in one cycle * @param {number} steps how many items are placed in one cycle
* @param {any[]} sequences one or more arrays of Patterns / values * @param {any[]} sequences one or more arrays of Patterns / values
* @example * @example
* polymeterSteps(4, ["c", "d", "e"]) * polymeterSteps(2, ["c", "d", "e", "f", "g", "f", "e", "d"])
* .note().stack(s("bd")) * .note().stack(s("bd")) // 1 cycle = 1 bd = 2 notes
* // note("{c d e}%4").stack(s("bd")) * // note("{c d e f g f e d}%2").stack(s("bd"))
*/ */
export function polymeterSteps(steps, ...args) { export function polymeterSteps(steps, ...args) {
const seqs = args.map((a) => _sequenceCount(a)); const seqs = args.map((a) => _sequenceCount(a));
@@ -1470,7 +1463,7 @@ export function register(name, func, patternify = true) {
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @example * @example
* n("0.5 1.5 2.5".round()).scale("C:major") * "0.5 1.5 2.5".round().scale('C major').note()
*/ */
export const round = register('round', function (pat) { export const round = register('round', function (pat) {
return pat.asNumber().fmap((v) => Math.round(v)); return pat.asNumber().fmap((v) => Math.round(v));
@@ -1484,7 +1477,7 @@ export const round = register('round', function (pat) {
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @example * @example
* note("42 42.1 42.5 43".floor()) * "42 42.1 42.5 43".floor().note()
*/ */
export const floor = register('floor', function (pat) { export const floor = register('floor', function (pat) {
return pat.asNumber().fmap((v) => Math.floor(v)); return pat.asNumber().fmap((v) => Math.floor(v));
@@ -1498,7 +1491,7 @@ export const floor = register('floor', function (pat) {
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @example * @example
* note("42 42.1 42.5 43".ceil()) * "42 42.1 42.5 43".ceil().note()
*/ */
export const ceil = register('ceil', function (pat) { export const ceil = register('ceil', function (pat) {
return pat.asNumber().fmap((v) => Math.ceil(v)); return pat.asNumber().fmap((v) => Math.ceil(v));
@@ -1531,8 +1524,7 @@ export const fromBipolar = register('fromBipolar', function (pat) {
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @example * @example
* s("[bd sd]*2,hh*8") * s("bd sd,hh*4").cutoff(sine.range(500,2000).slow(4))
* .cutoff(sine.range(500,4000))
*/ */
export const range = register('range', function (min, max, pat) { export const range = register('range', function (min, max, pat) {
return pat.mul(max - min).add(min); return pat.mul(max - min).add(min);
@@ -1546,8 +1538,7 @@ export const range = register('range', function (min, max, pat) {
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @example * @example
* s("[bd sd]*2,hh*8") * s("bd sd,hh*4").cutoff(sine.rangex(500,2000).slow(4))
* .cutoff(sine.rangex(500,4000))
*/ */
export const rangex = register('rangex', function (min, max, pat) { export const rangex = register('rangex', function (min, max, pat) {
return pat._range(Math.log(min), Math.log(max)).fmap(Math.exp); return pat._range(Math.log(min), Math.log(max)).fmap(Math.exp);
@@ -1560,8 +1551,7 @@ export const rangex = register('rangex', function (min, max, pat) {
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @example * @example
* s("[bd sd]*2,hh*8") * s("bd sd,hh*4").cutoff(sine2.range2(500,2000).slow(4))
* .cutoff(sine2.range2(500,4000))
*/ */
export const range2 = register('range2', function (min, max, pat) { export const range2 = register('range2', function (min, max, pat) {
return pat.fromBipolar()._range(min, max); return pat.fromBipolar()._range(min, max);
@@ -1574,8 +1564,7 @@ export const range2 = register('range2', function (min, max, pat) {
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @example * @example
* ratio("1, 5:4, 3:2").mul(110) * ratio("1, 5:4, 3:2").mul(110).freq().s("piano").slow(2)
* .freq().s("piano")
*/ */
export const ratio = register('ratio', (pat) => export const ratio = register('ratio', (pat) =>
pat.fmap((v) => { pat.fmap((v) => {
@@ -1647,7 +1636,7 @@ export const { fastGap, fastgap } = register(['fastGap', 'fastgap'], function (f
}); });
/** /**
* Similar to `compress`, but doesn't leave gaps, and the 'focus' can be bigger than a cycle * Similar to compress, but doesn't leave gaps, and the 'focus' can be bigger than a cycle
* @example * @example
* s("bd hh sd hh").focus(1/4, 3/4) * s("bd hh sd hh").focus(1/4, 3/4)
*/ */
@@ -1678,7 +1667,7 @@ export const ply = register('ply', function (factor, pat) {
* @param {number | Pattern} factor speed up factor * @param {number | Pattern} factor speed up factor
* @returns Pattern * @returns Pattern
* @example * @example
* s("bd hh sd hh").fast(2) // s("[bd hh sd hh]*2") * s("<bd sd> hh").fast(2) // s("[<bd sd> hh]*2")
*/ */
export const { fast, density } = register(['fast', 'density'], function (factor, pat) { export const { fast, density } = register(['fast', 'density'], function (factor, pat) {
if (factor === 0) { if (factor === 0) {
@@ -1707,7 +1696,7 @@ export const hurry = register('hurry', function (r, pat) {
* @param {number | Pattern} factor slow down factor * @param {number | Pattern} factor slow down factor
* @returns Pattern * @returns Pattern
* @example * @example
* s("bd hh sd hh").slow(2) // s("[bd hh sd hh]/2") * s("<bd sd> hh").slow(2) // s("[<bd sd> hh]/2")
*/ */
export const { slow, sparsity } = register(['slow', 'sparsity'], function (factor, pat) { export const { slow, sparsity } = register(['slow', 'sparsity'], function (factor, pat) {
if (factor === 0) { if (factor === 0) {
@@ -1764,7 +1753,7 @@ export const lastOf = register('lastOf', function (n, func, pat) {
*/ */
/** /**
* An alias for `firstOf` * An alias for {@link firstOf}
* @name every * @name every
* @memberof Pattern * @memberof Pattern
* @param {number} n how many cycles * @param {number} n how many cycles
@@ -1796,9 +1785,9 @@ export const apply = register('apply', function (func, pat) {
* @example * @example
* s("<bd sd>,hh*2").cpm(90) // = 90 bpm * s("<bd sd>,hh*2").cpm(90) // = 90 bpm
*/ */
// this is redefined in repl.mjs, using the current cps as divisor // TODO - global clock
export const cpm = register('cpm', function (cpm, pat) { export const cpm = register('cpm', function (cpm, pat) {
return pat._fast(cpm / 60 / 1); return pat._fast(cpm / 60);
}); });
/** /**
@@ -1871,7 +1860,7 @@ export const linger = register('linger', function (t, pat) {
* Samples the pattern at a rate of n events per cycle. Useful for turning a continuous pattern into a discrete one. * Samples the pattern at a rate of n events per cycle. Useful for turning a continuous pattern into a discrete one.
* @param {number} segments number of segments per cycle * @param {number} segments number of segments per cycle
* @example * @example
* note(saw.range(40,52).segment(24)) * note(saw.range(0,12).segment(24)).add(40)
*/ */
export const segment = register('segment', function (rate, pat) { export const segment = register('segment', function (rate, pat) {
return pat.struct(pure(true)._fast(rate)); return pat.struct(pure(true)._fast(rate));
@@ -1897,7 +1886,7 @@ export const { invert, inv } = register(['invert', 'inv'], function (pat) {
* @param {function} func * @param {function} func
* @returns Pattern * @returns Pattern
* @example * @example
* "c3 eb3 g3".when("<0 1>/2", x=>x.sub("5")).note() * "c3 eb3 g3".when("<0 1>/2", x=>x.sub(5)).note()
*/ */
export const when = register('when', function (on, func, pat) { export const when = register('when', function (on, func, pat) {
return on ? func(pat) : pat; return on ? func(pat) : pat;
@@ -1934,7 +1923,7 @@ export const brak = register('brak', function (pat) {
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @example * @example
* note("c d e g").rev() * note("c3 d3 e3 g3").rev()
*/ */
export const rev = register('rev', function (pat) { export const rev = register('rev', function (pat) {
const query = function (state) { const query = function (state) {
@@ -1986,9 +1975,9 @@ export const press = register('press', function (pat) {
* s("hh*3") * s("hh*3")
* ) * )
*/ */
Pattern.prototype.hush = function () { export const hush = register('hush', function (pat) {
return silence; return silence;
}; });
/** /**
* Applies `rev` to a pattern every other cycle, so that the pattern alternates between forwards and backwards. * Applies `rev` to a pattern every other cycle, so that the pattern alternates between forwards and backwards.
@@ -1996,7 +1985,7 @@ Pattern.prototype.hush = function () {
* note("c d e g").palindrome() * note("c d e g").palindrome()
*/ */
export const palindrome = register('palindrome', function (pat) { export const palindrome = register('palindrome', function (pat) {
return pat.lastOf(2, rev); return pat.every(2, rev);
}); });
/** /**
@@ -2004,7 +1993,7 @@ export const palindrome = register('palindrome', function (pat) {
* @name juxBy * @name juxBy
* @synonyms juxby * @synonyms juxby
* @example * @example
* s("bd lt [~ ht] mt cp ~ bd hh").juxBy("<0 .5 1>/2", rev) * s("lt ht mt ht hh").juxBy("<0 .5 1>/2", rev)
*/ */
export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func, pat) { export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func, pat) {
by /= 2; by /= 2;
@@ -2023,11 +2012,7 @@ export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func,
/** /**
* The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel. * The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel.
* @example * @example
* s("bd lt [~ ht] mt cp ~ bd hh").jux(rev) * s("lt ht mt ht hh").jux(rev)
* @example
* s("bd lt [~ ht] mt cp ~ bd hh").jux(press)
* @example
* s("bd lt [~ ht] mt cp ~ bd hh").jux(iter(4))
*/ */
export const jux = register('jux', function (func, pat) { export const jux = register('jux', function (func, pat) {
return pat._juxBy(1, func, pat); return pat._juxBy(1, func, pat);
@@ -2043,7 +2028,7 @@ export const jux = register('jux', function (func, pat) {
* @example * @example
* "<0 [2 4]>" * "<0 [2 4]>"
* .echoWith(4, 1/8, (p,n) => p.add(n*2)) * .echoWith(4, 1/8, (p,n) => p.add(n*2))
* .scale("C:minor").note() * .scale('C minor').note().clip(.2)
*/ */
export const { echoWith, echowith, stutWith, stutwith } = register( export const { echoWith, echowith, stutWith, stutwith } = register(
['echoWith', 'echowith', 'stutWith', 'stutwith'], ['echoWith', 'echowith', 'stutWith', 'stutwith'],
@@ -2136,8 +2121,7 @@ const { repeatCycles } = register('repeatCycles', _repeatCycles);
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @example * @example
* "0 1 2 3".chunk(4, x=>x.add(7)) * "0 1 2 3".chunk(4, x=>x.add(7)).scale('A minor').note()
* .scale("A:minor").note()
*/ */
const _chunk = function (n, func, pat, back = false, fast = false) { const _chunk = function (n, func, pat, back = false, fast = false) {
const binary = Array(n - 1).fill(false); const binary = Array(n - 1).fill(false);
@@ -2162,8 +2146,7 @@ const { chunk, slowchunk, slowChunk } = register(['chunk', 'slowchunk', 'slowChu
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @example * @example
* "0 1 2 3".chunkBack(4, x=>x.add(7)) * "0 1 2 3".chunkBack(4, x=>x.add(7)).scale('A minor').note()
* .scale("A:minor").note()
*/ */
export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], function (n, func, pat) { export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], function (n, func, pat) {
return _chunk(n, func, pat, true); return _chunk(n, func, pat, true);
@@ -2177,11 +2160,10 @@ export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], fun
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @example * @example
* "<0 8> 1 2 3 4 5 6 7" * "<0 8> 1 2 3 4 5 6 7".fastChunk(4, x => x.color('red')).slow(4).scale("C2:major").note()
* .fastChunk(4, x => x.color('red')).slow(2) .s("folkharp")
* .scale("C2:major").note()
*/ */
export const { fastchunk, fastChunk } = register(['fastchunk', 'fastChunk'], function (n, func, pat) { const { fastchunk, fastChunk } = register(['fastchunk', 'fastChunk'], function (n, func, pat) {
return _chunk(n, func, pat, false, true); return _chunk(n, func, pat, false, true);
}); });
@@ -2196,8 +2178,6 @@ export const bypass = register('bypass', function (on, pat) {
* @param {number} offset start point of loop in cycles * @param {number} offset start point of loop in cycles
* @param {number} cycles loop length in cycles * @param {number} cycles loop length in cycles
* @example * @example
* note("<c d e f>").ribbon(1, 2).fast(2)
* @example
* // Looping a portion of randomness * // Looping a portion of randomness
* note(irand(8).segment(4).scale('C3 minor')).ribbon(1337, 2) * note(irand(8).segment(4).scale('C3 minor')).ribbon(1337, 2)
*/ */
@@ -2209,14 +2189,6 @@ export const duration = register('duration', function (value, pat) {
return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value))); return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
}); });
export const hsla = register('hsla', (h, s, l, a, pat) => {
return pat.color(`hsla(${h}turn,${s * 100}%,${l * 100}%,${a})`);
});
export const hsl = register('hsl', (h, s, l, pat) => {
return pat.color(`hsl(${h}turn,${s * 100}%,${l * 100}%)`);
});
/** /**
* Sets the color of the hap in visualizations like pianoroll or highlighting. * Sets the color of the hap in visualizations like pianoroll or highlighting.
* @name color * @name color
@@ -2271,7 +2243,7 @@ export const legato = register('legato', function (value, pat) {
* s("rhodes") * s("rhodes")
* .chop(4) * .chop(4)
* .rev() // reverse order of chops * .rev() // reverse order of chops
* .loopAt(2) // fit sample into 2 cycles * .loopAt(4) // fit sample into 4 cycles
* *
*/ */
export const chop = register('chop', function (n, pat) { export const chop = register('chop', function (n, pat) {
@@ -2289,7 +2261,7 @@ export const chop = register('chop', function (n, pat) {
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @example * @example
* s("numbers:0 numbers:1 numbers:2").striate(6).slow(3) * s("numbers:0 numbers:1 numbers:2").striate(6).slow(6)
*/ */
export const striate = register('striate', function (n, pat) { export const striate = register('striate', function (n, pat) {
const slices = Array.from({ length: n }, (x, i) => i); const slices = Array.from({ length: n }, (x, i) => i);
@@ -2305,10 +2277,10 @@ export const striate = register('striate', function (n, pat) {
* @returns Pattern * @returns Pattern
* @example * @example
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' }) * samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
* s("rhodes").loopAt(2) * s("rhodes").loopAt(4)
*/ */
// TODO - global cps clock // TODO - global cps clock
const _loopAt = function (factor, pat, cps = 0.5) { const _loopAt = function (factor, pat, cps = 1) {
return pat return pat
.speed((1 / factor) * cps) .speed((1 / factor) * cps)
.unit('c') .unit('c')
@@ -2323,10 +2295,10 @@ const _loopAt = function (factor, pat, cps = 0.5) {
* @returns Pattern * @returns Pattern
* @example * @example
* await samples('github:tidalcycles/Dirt-Samples/master') * await samples('github:tidalcycles/Dirt-Samples/master')
* s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(0.75) * s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(1.5)
* @example * @example
* await samples('github:tidalcycles/Dirt-Samples/master') * await samples('github:tidalcycles/Dirt-Samples/master')
* s("breaks125").fit().slice([0,.25,.5,.75], "0 1 1 <2 3>") * s("breaks125/2").fit().slice([0,.25,.5,.75], "0 1 1 <2 3>")
*/ */
export const slice = register( export const slice = register(
@@ -2354,52 +2326,51 @@ export const slice = register(
* await samples('github:tidalcycles/Dirt-Samples/master') * await samples('github:tidalcycles/Dirt-Samples/master')
* s("breaks165") * s("breaks165")
* .splice(8, "0 1 [2 3 0]@2 3 0@2 7") * .splice(8, "0 1 [2 3 0]@2 3 0@2 7")
* .hurry(0.65)
*/ */
export const splice = register( export const splice = register(
'splice', 'splice',
function (npat, ipat, opat) { function (npat, ipat, opat) {
const sliced = slice(npat, ipat, opat); const sliced = slice(npat, ipat, opat);
return new Pattern((state) => { return sliced.withHap(function (hap) {
// TODO - default cps to 0.5 return hap.withValue((v) => ({
const cps = state.controls._cps || 1;
const haps = sliced.query(state);
return haps.map((hap) =>
hap.withValue((v) => ({
...{ ...{
speed: (cps / v._slices / hap.whole.duration) * (v.speed || 1), speed: (1 / v._slices / hap.whole.duration) * (v.speed || 1),
unit: 'c', unit: 'c',
}, },
...v, ...v,
})), }));
);
}); });
}, },
false, // turns off auto-patternification false, // turns off auto-patternification
); );
// this function will be redefined in repl.mjs to use the correct cps value.
// It is still here to work in cases where repl.mjs is not used
export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) { export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) {
return new Pattern((state) => _loopAt(factor, pat, state.controls._cps).query(state)); return _loopAt(factor, pat, 1);
}); });
// the fit function will be redefined in repl.mjs to use the correct cps value.
// It is still here to work in cases where repl.mjs is not used
/** /**
* Makes the sample fit its event duration. Good for rhythmical loops like drum breaks. * Makes the sample fit its event duration. Good for rhythmical loops like drum breaks.
* Similar to `loopAt`. * Similar to loopAt.
* @name fit * @name fit
* @example * @example
* samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' }) * samples({ rhodes: 'https://cdn.freesound.org/previews/132/132051_316502-lq.mp3' })
* s("rhodes/2").fit() * s("rhodes/4").fit()
*/ */
export const fit = register('fit', (pat) => export const fit = register('fit', (pat) =>
pat.withHaps((haps, state) => pat.withHap((hap) =>
haps.map((hap) =>
hap.withValue((v) => ({ hap.withValue((v) => ({
...v, ...v,
speed: (state.controls._cps || 1) / hap.whole.duration, speed: 1 / hap.whole.duration,
unit: 'c', unit: 'c',
})), })),
), ),
),
); );
/** /**
+5 -13
View File
@@ -120,6 +120,7 @@ export function pianoroll({
hideInactive = 0, hideInactive = 0,
colorizeInactive = 1, colorizeInactive = 1,
fontFamily, fontFamily,
clear = true,
ctx, ctx,
} = {}) { } = {}) {
const w = ctx.canvas.width; const w = ctx.canvas.width;
@@ -165,7 +166,7 @@ export function pianoroll({
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent; barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
ctx.fillStyle = background; ctx.fillStyle = background;
ctx.globalAlpha = 1; // reset! ctx.globalAlpha = 1; // reset!
if (!smear) { if (clear && !smear) {
ctx.clearRect(0, 0, w, h); ctx.clearRect(0, 0, w, h);
ctx.fillRect(0, 0, w, h); ctx.fillRect(0, 0, w, h);
} }
@@ -256,21 +257,12 @@ export function getDrawOptions(drawTime, options = {}) {
return { fold: 1, ...options, cycles, playhead }; return { fold: 1, ...options, cycles, playhead };
} }
export const getPunchcardPainter =
(options = {}) =>
(ctx, time, haps, drawTime, paintOptions = {}) =>
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, { ...paintOptions, ...options }) });
Pattern.prototype.punchcard = function (options) { Pattern.prototype.punchcard = function (options) {
return this.onPaint(getPunchcardPainter(options)); return this.onPaint((ctx, time, haps, drawTime, paintOptions = {}) =>
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, { ...paintOptions, ...options }) }),
);
}; };
/**
* Displays a vertical pianoroll with event labels.
* Supports all the same options as pianoroll.
*
* @name wordfall
*/
Pattern.prototype.wordfall = function (options) { Pattern.prototype.wordfall = function (options) {
return this.punchcard({ vertical: 1, labels: 1, stroke: 0, fillActive: 1, active: 'white', ...options }); return this.punchcard({ vertical: 1, labels: 1, stroke: 0, fillActive: 1, active: 'white', ...options });
}; };
+74 -110
View File
@@ -3,7 +3,23 @@ import { evaluate as _evaluate } from './evaluate.mjs';
import { logger } from './logger.mjs'; import { logger } from './logger.mjs';
import { setTime } from './time.mjs'; import { setTime } from './time.mjs';
import { evalScope } from './evaluate.mjs'; import { evalScope } from './evaluate.mjs';
import { register, Pattern, isPattern, silence, stack } from './pattern.mjs'; import { register } from './pattern.mjs';
import { atom, computed } from 'nanostores';
export const $replstate = atom({
schedulerError: undefined,
evalError: undefined,
code: '// LOADING',
activeCode: '// LOADING',
pattern: undefined,
miniLocations: [],
widgets: [],
pending: true,
started: false,
});
export const $repldirty = computed($replstate, (s) => s.code !== s.activeCode);
export const setReplState = (key, value) => $replstate.set({ ...$replstate.get(), [key]: value });
export function repl({ export function repl({
interval, interval,
@@ -16,146 +32,94 @@ export function repl({
transpiler, transpiler,
onToggle, onToggle,
editPattern, editPattern,
onUpdateState,
}) { }) {
const state = {
schedulerError: undefined,
evalError: undefined,
code: '// LOADING',
activeCode: '// LOADING',
pattern: undefined,
miniLocations: [],
widgets: [],
pending: false,
started: false,
};
const updateState = (update) => {
Object.assign(state, update);
state.isDirty = state.code !== state.activeCode;
state.error = state.evalError || state.schedulerError;
onUpdateState?.(state);
};
const scheduler = new Cyclist({ const scheduler = new Cyclist({
interval, interval,
onTrigger: getTrigger({ defaultOutput, getTime }), onTrigger: getTrigger({ defaultOutput, getTime }),
onError: onSchedulerError, onError: onSchedulerError,
getTime, getTime,
onToggle: (started) => { onToggle: (started) => {
updateState({ started }); setReplState('started', started);
onToggle?.(started); onToggle?.(started);
}, },
}); });
let pPatterns = {}; let playPatterns = [];
let allTransform;
const hush = function () {
pPatterns = {};
allTransform = undefined;
return silence;
};
const setPattern = (pattern, autostart = true) => { const setPattern = (pattern, autostart = true) => {
pattern = editPattern?.(pattern) || pattern; pattern = editPattern?.(pattern) || pattern;
scheduler.setPattern(pattern, autostart); scheduler.setPattern(pattern, autostart);
}; };
setTime(() => scheduler.now()); // TODO: refactor? setTime(() => scheduler.now()); // TODO: refactor?
const evaluate = async (code, autostart = true) => {
if (!code) {
throw new Error('no code to evaluate');
}
try {
setReplState('code', code);
setReplState('pending', true);
await beforeEval?.({ code });
playPatterns = [];
let { pattern, meta } = await _evaluate(code, transpiler);
if (playPatterns.length) {
pattern = pattern.stack(...playPatterns);
}
logger(`[eval] code updated`);
setPattern(pattern, autostart);
setReplState('miniLocations', meta?.miniLocations || []);
setReplState('widgets', meta?.widgets || []);
setReplState('activeCode', code);
setReplState('pattern', pattern);
setReplState('evalError', undefined);
setReplState('schedulerError', undefined);
setReplState('pending', false);
afterEval?.({ code, pattern, meta });
return pattern;
} catch (err) {
// console.warn(`[repl] eval error: ${err.message}`);
logger(`[eval] error: ${err.message}`, 'error');
setReplState('evalError', err);
setReplState('pending', false);
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 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);
const all = function (transform) {
allTransform = transform;
return silence;
};
// set pattern methods that use this repl via closure // the following functions use the cps value, which is why they are defined here..
const injectPatternMethods = () => { const loopAt = register('loopAt', (cycles, pat) => {
Pattern.prototype.p = function (id) { return pat.loopAtCps(cycles, scheduler.cps);
pPatterns[id] = this;
return this;
};
Pattern.prototype.q = function (id) {
return silence;
};
try {
for (let i = 1; i < 10; ++i) {
Object.defineProperty(Pattern.prototype, `d${i}`, {
get() {
return this.p(i);
},
configurable: true,
}); });
Object.defineProperty(Pattern.prototype, `p${i}`, {
get() { const play = register('play', (pat) => {
return this.p(i); playPatterns.push(pat);
}, return pat;
configurable: true,
});
Pattern.prototype[`q${i}`] = silence;
}
} catch (err) {
console.warn('injectPatternMethods: error:', err);
}
const cpm = register('cpm', function (cpm, pat) {
return pat._fast(cpm / 60 / scheduler.cps);
}); });
const fit = register('fit', (pat) =>
pat.withHap((hap) =>
hap.withValue((v) => ({
...v,
speed: scheduler.cps / hap.whole.duration, // overwrite speed completely?
unit: 'c',
})),
),
);
evalScope({ evalScope({
all, loopAt,
hush, fit,
cpm, play,
setCps, setCps,
setcps: setCps, setcps: setCps,
setCpm, setCpm,
setcpm: setCpm, setcpm: setCpm,
}); });
};
const evaluate = async (code, autostart = true, shouldHush = true) => { const setCode = (c) => setReplState('code', c);
if (!code) { return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle };
throw new Error('no code to evaluate');
}
try {
updateState({ code, pending: true });
injectPatternMethods();
await beforeEval?.({ code });
shouldHush && hush();
let { pattern, meta } = await _evaluate(code, transpiler);
if (Object.keys(pPatterns).length) {
pattern = stack(...Object.values(pPatterns));
}
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`);
setPattern(pattern, autostart);
updateState({
miniLocations: meta?.miniLocations || [],
widgets: meta?.widgets || [],
activeCode: code,
pattern,
evalError: undefined,
schedulerError: undefined,
pending: false,
});
afterEval?.({ code, pattern, meta });
return pattern;
} catch (err) {
logger(`[eval] error: ${err.message}`, 'error');
updateState({ evalError: err, pending: false });
onEvalError?.(err);
}
};
const setCode = (code) => updateState({ code });
return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle, state };
} }
export const getTrigger = export const getTrigger =
+40 -136
View File
@@ -7,7 +7,7 @@ This program is free software: you can redistribute it and/or modify it under th
import { Hap } from './hap.mjs'; import { Hap } from './hap.mjs';
import { Pattern, fastcat, reify, silence, stack, register } from './pattern.mjs'; import { Pattern, fastcat, reify, silence, stack, register } from './pattern.mjs';
import Fraction from './fraction.mjs'; import Fraction from './fraction.mjs';
import { id, _mod, clamp, objectMap } from './util.mjs'; import { id, _mod, clamp } from './util.mjs';
export function steady(value) { export function steady(value) {
// A continuous value // A continuous value
@@ -27,11 +27,9 @@ export const isaw2 = isaw.toBipolar();
* *
* @return {Pattern} * @return {Pattern}
* @example * @example
* note("<c3 [eb3,g3] g2 [g3,bb3]>*8") * "c3 [eb3,g3] g2 [g3,bb3]".note().clip(saw.slow(4))
* .clip(saw.slow(2))
* @example * @example
* n(saw.range(0,8).segment(8)) * saw.range(0,8).segment(8).scale('C major').slow(4).note()
* .scale('C major')
* *
*/ */
export const saw = signal((t) => t % 1); export const saw = signal((t) => t % 1);
@@ -44,8 +42,7 @@ export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
* *
* @return {Pattern} * @return {Pattern}
* @example * @example
* n(sine.segment(16).range(0,15)) * sine.segment(16).range(0,15).slow(2).scale('C minor').note()
* .scale("C:minor")
* *
*/ */
export const sine = sine2.fromBipolar(); export const sine = sine2.fromBipolar();
@@ -55,8 +52,7 @@ export const sine = sine2.fromBipolar();
* *
* @return {Pattern} * @return {Pattern}
* @example * @example
* n(stack(sine,cosine).segment(16).range(0,15)) * stack(sine,cosine).segment(16).range(0,15).slow(2).scale('C minor').note()
* .scale("C:minor")
* *
*/ */
export const cosine = sine._early(Fraction(1).div(4)); export const cosine = sine._early(Fraction(1).div(4));
@@ -67,7 +63,7 @@ export const cosine2 = sine2._early(Fraction(1).div(4));
* *
* @return {Pattern} * @return {Pattern}
* @example * @example
* n(square.segment(4).range(0,7)).scale("C:minor") * square.segment(2).range(0,7).scale('C minor').note()
* *
*/ */
export const square = signal((t) => Math.floor((t * 2) % 2)); export const square = signal((t) => Math.floor((t * 2) % 2));
@@ -78,7 +74,7 @@ export const square2 = square.toBipolar();
* *
* @return {Pattern} * @return {Pattern}
* @example * @example
* n(tri.segment(8).range(0,7)).scale("C:minor") * tri.segment(8).range(0,7).scale('C minor').note()
* *
*/ */
export const tri = fastcat(isaw, saw); export const tri = fastcat(isaw, saw);
@@ -105,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);
@@ -122,8 +117,8 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
/** /**
* A discrete pattern of numbers from 0 to n-1 * A discrete pattern of numbers from 0 to n-1
* @example * @example
* n(run(4)).scale("C4:pentatonic") * run(4).scale('C4 major').note()
* // n("0 1 2 3").scale("C4:pentatonic") * // "0 1 2 3".scale('C4 major').note()
*/ */
export const run = (n) => saw.range(0, n).floor().segment(n); export const run = (n) => saw.range(0, n).floor().segment(n);
@@ -133,7 +128,7 @@ export const run = (n) => saw.range(0, n).floor().segment(n);
* @name rand * @name rand
* @example * @example
* // randomly change the cutoff * // randomly change the cutoff
* s("bd*4,hh*8").cutoff(rand.range(500,8000)) * s("bd sd,hh*4").cutoff(rand.range(500,2000))
* *
*/ */
export const rand = signal(timeToRand); export const rand = signal(timeToRand);
@@ -155,127 +150,36 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
* @param {number} n max value (exclusive) * @param {number} n max value (exclusive)
* @example * @example
* // randomly select scale notes from 0 - 7 (= C to C) * // randomly select scale notes from 0 - 7 (= C to C)
* n(irand(8)).struct("x x*2 x x*3").scale("C:minor") * irand(8).struct("x(3,8)").scale('C minor').note()
* *
*/ */
export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin(); export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin();
const _pick = function (lookup, pat, modulo = true) { /**
const array = Array.isArray(lookup); * pick from the list of values (or patterns of values) via the index using the given
const len = Object.keys(lookup).length; * pattern of integers
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
* @example
* note(pick("<0 1 [2!2] 3>", ["g a", "e f", "f g f g" , "g a c d"]))
*/
lookup = objectMap(lookup, reify); export const pick = (pat, xs) => {
xs = xs.map(reify);
if (len === 0) { if (xs.length == 0) {
return silence; return silence;
} }
return pat.fmap((i) => { return pat
let key = i; .fmap((i) => {
if (array) { const key = clamp(Math.round(i), 0, xs.length - 1);
key = modulo ? Math.round(key) % len : clamp(Math.round(key), 0, lookup.length - 1); return xs[key];
}
return lookup[key];
});
};
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
* Similar to `inhabit`, but maintains the structure of the original patterns.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
* @example
* note("<0 1 2!2 3>".pick(["g a", "e f", "f g f g" , "g c d"]))
* @example
* sound("<0 1 [2,0]>".pick(["bd sd", "cp cp", "hh hh"]))
* @example
* sound("<0!2 [0,1] 1>".pick(["bd(3,8)", "sd sd"]))
* @example
* s("<a!2 [a,b] b>".pick({a: "bd(3,8)", b: "sd sd"}))
*/
export const pick = function (lookup, pat) {
// backward compatibility - the args used to be flipped
if (Array.isArray(pat)) {
[pat, lookup] = [lookup, pat];
}
return __pick(lookup, pat);
};
const __pick = register('pick', function (lookup, pat) {
return _pick(lookup, pat, false).innerJoin();
});
/** * The same as `pick`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* For example, if you pick the fifth pattern of a list of three, you'll get the
* second one.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmod = register('pickmod', function (lookup, pat) {
return _pick(lookup, pat, true).innerJoin();
});
/** * pickF lets you use a pattern of numbers to pick which function to apply to another pattern.
* @param {Pattern} pat
* @param {Pattern} lookup a pattern of indices
* @param {function[]} funcs the array of functions from which to pull
* @returns {Pattern}
* @example
* s("bd [rim hh]").pickF("<0 1 2>", [rev,jux(rev),fast(2)])
* @example
* note("<c2 d2>(3,8)").s("square")
* .pickF("<0 2> 1", [jux(rev),fast(2),x=>x.lpf(800)])
*/
export const pickF = register('pickF', function (lookup, funcs, pat) {
return pat.apply(pick(lookup, funcs));
});
/** * The same as `pickF`, but if you pick a number greater than the size of the functions list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {Pattern} lookup a pattern of indices
* @param {function[]} funcs the array of functions from which to pull
* @returns {Pattern}
*/
export const pickmodF = register('pickmodF', function (lookup, funcs, pat) {
return pat.apply(pickmod(lookup, funcs));
});
/**
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
* Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
* @example
* "<a b [a,b]>".inhabit({a: s("bd(3,8)"),
b: s("cp sd")
}) })
* @example .innerJoin();
* s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4) };
*/
export const inhabit = register('inhabit', function (lookup, pat) {
return _pick(lookup, pat, true).squeezeJoin();
});
/** * The same as `inhabit`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* For example, if you pick the fifth pattern of a list of three, you'll get the
* second one.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const inhabitmod = register('inhabit', function (lookup, pat) {
return _pick(lookup, pat, false).squeezeJoin();
});
/** /**
* Pick from the list of values (or patterns of values) via the index using the given * pick from the list of values (or patterns of values) via the index using the given
* pattern of integers. The selected pattern will be compressed to fit the duration of the selecting event * pattern of integers. The selected pattern will be compressed to fit the duration of the selecting event
* @param {Pattern} pat * @param {Pattern} pat
* @param {*} xs * @param {*} xs
@@ -364,9 +268,9 @@ Pattern.prototype.choose2 = function (...xs) {
* Picks one of the elements at random each cycle. * Picks one of the elements at random each cycle.
* @returns {Pattern} * @returns {Pattern}
* @example * @example
* chooseCycles("bd", "hh", "sd").s().fast(8) * chooseCycles("bd", "hh", "sd").s().fast(4)
* @example * @example
* s("bd | hh | sd").fast(8) * "bd | hh | sd".s().fast(4)
*/ */
export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs); export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs);
@@ -409,7 +313,7 @@ export const perlinWith = (pat) => {
* @name perlin * @name perlin
* @example * @example
* // randomly change the cutoff * // randomly change the cutoff
* s("bd*4,hh*8").cutoff(perlin.range(500,8000)) * s("bd sd,hh*4").cutoff(perlin.range(500,2000))
* *
*/ */
export const perlin = perlinWith(time.fmap((v) => Number(v))); export const perlin = perlinWith(time.fmap((v) => Number(v)));
@@ -451,7 +355,7 @@ export const degradeBy = register('degradeBy', function (x, pat) {
export const degrade = register('degrade', (pat) => pat._degradeBy(0.5)); export const degrade = register('degrade', (pat) => pat._degradeBy(0.5));
/** /**
* Inverse of `degradeBy`: Randomly removes events from the pattern by a given amount. * Inverse of {@link Pattern#degradeBy}: Randomly removes events from the pattern by a given amount.
* 0 = 100% chance of removal * 0 = 100% chance of removal
* 1 = 0% chance of removal * 1 = 0% chance of removal
* Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example). * Events that would be removed by degradeBy are let through by undegradeBy and vice versa (see second example).
@@ -475,7 +379,7 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5));
/** /**
* *
* Randomly applies the given function by the given probability. * Randomly applies the given function by the given probability.
* Similar to `someCyclesBy` * Similar to {@link Pattern#someCyclesBy}
* *
* @name sometimesBy * @name sometimesBy
* @memberof Pattern * @memberof Pattern
@@ -483,7 +387,7 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5));
* @param {function} function - the transformation to apply * @param {function} function - the transformation to apply
* @returns Pattern * @returns Pattern
* @example * @example
* s("hh*8").sometimesBy(.4, x=>x.speed("0.5")) * s("hh(3,8)").sometimesBy(.4, x=>x.speed("0.5"))
*/ */
export const sometimesBy = register('sometimesBy', function (patx, func, pat) { export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
@@ -501,7 +405,7 @@ export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
* @param {function} function - the transformation to apply * @param {function} function - the transformation to apply
* @returns Pattern * @returns Pattern
* @example * @example
* s("hh*8").sometimes(x=>x.speed("0.5")) * s("hh*4").sometimes(x=>x.speed("0.5"))
*/ */
export const sometimes = register('sometimes', function (func, pat) { export const sometimes = register('sometimes', function (func, pat) {
return pat._sometimesBy(0.5, func); return pat._sometimesBy(0.5, func);
@@ -510,7 +414,7 @@ export const sometimes = register('sometimes', function (func, pat) {
/** /**
* *
* Randomly applies the given function by the given probability on a cycle by cycle basis. * Randomly applies the given function by the given probability on a cycle by cycle basis.
* Similar to `sometimesBy` * Similar to {@link Pattern#sometimesBy}
* *
* @name someCyclesBy * @name someCyclesBy
* @memberof Pattern * @memberof Pattern
@@ -518,7 +422,7 @@ export const sometimes = register('sometimes', function (func, pat) {
* @param {function} function - the transformation to apply * @param {function} function - the transformation to apply
* @returns Pattern * @returns Pattern
* @example * @example
* s("bd,hh*8").someCyclesBy(.3, x=>x.speed("0.5")) * s("hh(3,8)").someCyclesBy(.3, x=>x.speed("0.5"))
*/ */
export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) { export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) {
@@ -540,7 +444,7 @@ export const someCyclesBy = register('someCyclesBy', function (patx, func, pat)
* @memberof Pattern * @memberof Pattern
* @returns Pattern * @returns Pattern
* @example * @example
* s("bd,hh*8").someCycles(x=>x.speed("0.5")) * s("hh(3,8)").someCycles(x=>x.speed("0.5"))
*/ */
export const someCycles = register('someCycles', function (func, pat) { export const someCycles = register('someCycles', function (func, pat) {
return pat._someCyclesBy(0.5, func); return pat._someCyclesBy(0.5, func);
+3 -3
View File
@@ -67,9 +67,9 @@ Pattern.prototype.spiral = function (options = {}) {
// logSpiral = true, // logSpiral = true,
} = options; } = options;
function spiral({ ctx, time, haps, drawTime }) { function spiral({ ctx, time, haps, drawTime, clear }) {
const [w, h] = [ctx.canvas.width, ctx.canvas.height]; const [w, h] = [ctx.canvas.width, ctx.canvas.height];
ctx.clearRect(0, 0, w * 2, h * 2); clear && ctx.clearRect(0, 0, w * 2, h * 2);
const [cx, cy] = [w / 2, h / 2]; const [cx, cy] = [w / 2, h / 2];
const settings = { const settings = {
margin: size / stretch, margin: size / stretch,
@@ -114,5 +114,5 @@ Pattern.prototype.spiral = function (options = {}) {
}); });
} }
return this.onPaint((ctx, time, haps, drawTime) => spiral({ ctx, time, haps, drawTime })); return this.onPaint((ctx, time, haps, drawTime, options) => spiral({ ctx, time, haps, drawTime, ...options }));
}; };
+11 -106
View File
@@ -20,7 +20,6 @@ import {
slowcat, slowcat,
cat, cat,
sequence, sequence,
palindrome,
polymeter, polymeter,
polymeterSteps, polymeterSteps,
polyrhythm, polyrhythm,
@@ -46,7 +45,6 @@ import {
rev, rev,
time, time,
run, run,
pick,
} from '../index.mjs'; } from '../index.mjs';
import { steady } from '../signal.mjs'; import { steady } from '../signal.mjs';
@@ -184,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),
); );
}); });
@@ -236,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),
); );
}); });
@@ -282,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),
); );
}); });
@@ -585,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(
@@ -664,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'),
@@ -699,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'),
@@ -972,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);
@@ -1058,63 +1022,4 @@ describe('Pattern', () => {
expect(slowcat(0, 1).repeatCycles(2).fast(6).firstCycleValues).toStrictEqual([0, 0, 1, 1, 0, 0]); expect(slowcat(0, 1).repeatCycles(2).fast(6).firstCycleValues).toStrictEqual([0, 0, 1, 1, 0, 0]);
}); });
}); });
describe('inhabit', () => {
it('Can pattern named patterns', () => {
expect(
sameFirst(
sequence('a', 'b', stack('a', 'b')).inhabit({ a: sequence(1, 2), b: sequence(10, 20, 30) }),
sequence([1, 2], [10, 20, 30], stack([1, 2], [10, 20, 30])),
),
);
});
it('Can pattern indexed patterns', () => {
expect(
sameFirst(
sequence('0', '1', stack('0', '1')).inhabit([sequence(1, 2), sequence(10, 20, 30)]),
sequence([1, 2], [10, 20, 30], stack([1, 2], [10, 20, 30])),
),
);
});
});
describe('pick', () => {
it('Can pattern named patterns', () => {
expect(
sameFirst(
sequence('a', 'b', 'a', stack('a', 'b')).pick({ a: sequence(1, 2, 3, 4), b: sequence(10, 20, 30, 40) }),
sequence(1, 20, 3, stack(4, 40)),
),
);
});
it('Can pattern indexed patterns', () => {
expect(
sameFirst(
sequence(0, 1, 0, stack(0, 1)).pick([sequence(1, 2, 3, 4), sequence(10, 20, 30, 40)]),
sequence(1, 20, 3, stack(4, 40)),
),
);
});
it('Clamps indexes', () => {
expect(
sameFirst(sequence(0, 1, 2, 3).pick([sequence(1, 2, 3, 4), sequence(10, 20, 30, 40)]), sequence(1, 20, 30, 40)),
);
});
it('Is backwards compatible', () => {
expect(
sameFirst(
pick([sequence('a', 'b'), sequence('c', 'd')], sequence(0, 1)),
pick(sequence(0, 1), [sequence('a', 'b'), sequence('c', 'd')]),
),
);
});
});
describe('pickmod', () => {
it('Wraps indexes', () => {
expect(
sameFirst(
sequence(0, 1, 2, 3).pickmod([sequence(1, 2, 3, 4), sequence(10, 20, 30, 40)]),
sequence(1, 20, 3, 40),
),
);
});
});
}); });
-49
View File
@@ -4,8 +4,6 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { logger } from './logger.mjs';
// returns true if the given string is a note // returns true if the given string is a note
export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name); export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name);
export const isNote = (name) => /^[a-gA-G][#bsf]*[0-9]?$/.test(name); export const isNote = (name) => /^[a-gA-G][#bsf]*[0-9]?$/.test(name);
@@ -86,18 +84,6 @@ export const midi2note = (n) => {
// modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does) // modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does)
export const _mod = (n, m) => ((n % m) + m) % m; export const _mod = (n, m) => ((n % m) + m) % m;
export function nanFallback(value, fallback = 0) {
if (isNaN(Number(value))) {
logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning');
return fallback;
}
return value;
}
// round to nearest int, negative numbers will output a subtracted index
export const getSoundIndex = (n, numSounds) => {
return _mod(Math.round(nanFallback(n ?? 0, 0)), numSounds);
};
export const getPlayableNoteValue = (hap) => { export const getPlayableNoteValue = (hap) => {
let { value, context } = hap; let { value, context } = hap;
let note = value; let note = value;
@@ -288,38 +274,3 @@ export const sol2note = (n, notation = 'letters') => {
const oct = Math.floor(n / 12) - 1; const oct = Math.floor(n / 12) - 1;
return note + oct; return note + oct;
}; };
// code hashing helpers
export function unicodeToBase64(text) {
const utf8Bytes = new TextEncoder().encode(text);
const base64String = btoa(String.fromCharCode(...utf8Bytes));
return base64String;
}
export function base64ToUnicode(base64String) {
const utf8Bytes = new Uint8Array(
atob(base64String)
.split('')
.map((char) => char.charCodeAt(0)),
);
const decodedText = new TextDecoder().decode(utf8Bytes);
return decodedText;
}
export function code2hash(code) {
return encodeURIComponent(unicodeToBase64(code));
//return '#' + encodeURIComponent(btoa(code));
}
export function hash2code(hash) {
return base64ToUnicode(decodeURIComponent(hash));
//return atob(decodeURIComponent(codeParam || ''));
}
export function objectMap(obj, fn) {
if (Array.isArray(obj)) {
return obj.map(fn);
}
return Object.fromEntries(Object.entries(obj).map(([k, v], i) => [k, fn(v, k, i)]));
}
+1 -1
View File
@@ -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
View File
@@ -1 +0,0 @@
# @strudel/csound
+2 -2
View File
@@ -1,5 +1,5 @@
import { getFrequency, logger, register } from '@strudel/core'; import { getFrequency, logger, register } from '@strudel.cycles/core';
import { getAudioContext } from '@strudel/webaudio'; import { getAudioContext } from '@strudel.cycles/webaudio';
import csd from './project.csd?raw'; import csd from './project.csd?raw';
// import livecodeOrc from './livecode.orc?raw'; // import livecodeOrc from './livecode.orc?raw';
import presetsOrc from './presets.orc?raw'; import presetsOrc from './presets.orc?raw';
+5 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/csound", "name": "@strudel.cycles/csound",
"version": "1.0.0", "version": "0.9.0",
"description": "csound bindings for strudel", "description": "csound bindings for strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -33,10 +33,10 @@
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@csound/browser": "6.18.7", "@csound/browser": "6.18.7",
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@strudel/webaudio": "workspace:*" "@strudel.cycles/webaudio": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^4.3.3"
} }
} }
+1 -1
View File
@@ -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 -1
View File
@@ -1,5 +1,5 @@
import { Invoke } from './utils.mjs'; import { Invoke } from './utils.mjs';
import { Pattern, noteToMidi } from '@strudel/core'; import { Pattern, noteToMidi } from '@strudel.cycles/core';
const ON_MESSAGE = 0x90; const ON_MESSAGE = 0x90;
const OFF_MESSAGE = 0x80; const OFF_MESSAGE = 0x80;
+1 -1
View File
@@ -1,4 +1,4 @@
import { parseNumeral, Pattern } from '@strudel/core'; import { parseNumeral, Pattern } from '@strudel.cycles/core';
import { Invoke } from './utils.mjs'; import { Invoke } from './utils.mjs';
Pattern.prototype.osc = function () { Pattern.prototype.osc = function () {
+2 -2
View File
@@ -22,8 +22,8 @@
"url": "https://github.com/tidalcycles/strudel/issues" "url": "https://github.com/tidalcycles/strudel/issues"
}, },
"dependencies": { "dependencies": {
"@strudel/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"
} }
+3 -3
View File
@@ -1,13 +1,13 @@
# @strudel/embed # @strudel.cycles/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
Either install with `npm i @strudel/embed` or just use a cdn to import the script: Either install with `npm i @strudel.cycles/embed` or just use a cdn to import the script:
```html ```html
<script src="https://unpkg.com/@strudel/embed@latest"></script> <script src="https://unpkg.com/@strudel.cycles/embed@latest"></script>
<strudel-repl> <strudel-repl>
<!-- <!--
note(`[[e5 [b4 c5] d5 [c5 b4]] note(`[[e5 [b4 c5] d5 [c5 b4]]
@@ -1,4 +1,4 @@
<script src="https://unpkg.com/@strudel/embed@0.11.0"></script> <script src="https://unpkg.com/@strudel.cycles/embed@latest"></script>
<!-- <script src="./embed.js"></script> --> <!-- <script src="./embed.js"></script> -->
<strudel-repl> <strudel-repl>
<!-- <!--
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/embed", "name": "@strudel.cycles/embed",
"version": "1.0.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",
+1 -7
View File
@@ -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
@@ -27,7 +21,7 @@ npm i @strudel/hydra
Then add the import to your evalScope: Then add the import to your evalScope:
```js ```js
import { evalScope } from '@strudel/core'; import { evalScope } from '@strudel.cycles/core';
evalScope( evalScope(
import('@strudel/hydra') import('@strudel/hydra')
+9 -34
View File
@@ -1,39 +1,14 @@
import { getDrawContext } from '@strudel/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);
} }
} }
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/hydra", "name": "@strudel/hydra",
"version": "1.0.0", "version": "0.9.0",
"description": "Hydra integration for strudel", "description": "Hydra integration for strudel",
"main": "hydra.mjs", "main": "hydra.mjs",
"publishConfig": { "publishConfig": {
@@ -33,11 +33,11 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"hydra-synth": "^1.3.29" "hydra-synth": "^1.3.29"
}, },
"devDependencies": { "devDependencies": {
"pkg": "^5.8.1", "pkg": "^5.8.1",
"vite": "^5.0.10" "vite": "^4.3.3"
} }
} }
+1 -1
View File
@@ -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)],
+2 -2
View File
@@ -1,9 +1,9 @@
# @strudel/midi # @strudel.cycles/midi
This package adds midi functionality to strudel Patterns. This package adds midi functionality to strudel Patterns.
## Install ## Install
```sh ```sh
npm i @strudel/midi --save npm i @strudel.cycles/midi --save
``` ```
+2 -2
View File
@@ -5,8 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import * as _WebMidi from 'webmidi'; import * as _WebMidi from 'webmidi';
import { Pattern, isPattern, logger, ref } from '@strudel/core'; import { Pattern, isPattern, logger, ref } from '@strudel.cycles/core';
import { noteToMidi } from '@strudel/core'; import { noteToMidi } from '@strudel.cycles/core';
import { Note } from 'webmidi'; import { Note } from 'webmidi';
// if you use WebMidi from outside of this package, make sure to import that instance: // if you use WebMidi from outside of this package, make sure to import that instance:
export const { WebMidi } = _WebMidi; export const { WebMidi } = _WebMidi;
+6 -6
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/midi", "name": "@strudel.cycles/midi",
"version": "1.0.0", "version": "0.9.0",
"description": "Midi API for strudel", "description": "Midi API for strudel",
"main": "index.mjs", "main": "index.mjs",
"publishConfig": { "publishConfig": {
@@ -29,11 +29,11 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel.cycles/core": "workspace:*",
"@strudel/webaudio": "workspace:*", "@strudel.cycles/webaudio": "workspace:*",
"webmidi": "^3.1.8" "webmidi": "^3.1.5"
}, },
"devDependencies": { "devDependencies": {
"vite": "^5.0.10" "vite": "^4.3.3"
} }
} }
+1 -1
View File
@@ -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)],
+4 -4
View File
@@ -1,17 +1,17 @@
# @strudel/mini # @strudel.cycles/mini
This package contains the mini notation parser and pattern generator. This package contains the mini notation parser and pattern generator.
## Install ## Install
```sh ```sh
npm i @strudel/mini --save npm i @strudel.cycles/mini --save
``` ```
## Example ## Example
```js ```js
import { mini } from '@strudel/mini'; import { mini } from '@strudel.cycles/mini';
const pattern = mini('a [b c*2]'); const pattern = mini('a [b c*2]');
@@ -28,7 +28,7 @@ yields:
(7/8 -> 1/1, 7/8 -> 1/1, c) (7/8 -> 1/1, 7/8 -> 1/1, c)
``` ```
[Play with @strudel/mini codesandbox](https://codesandbox.io/s/strudel-mini-example-oe9wcu?file=/src/index.js) [Play with @strudel.cycles/mini codesandbox](https://codesandbox.io/s/strudel-mini-example-oe9wcu?file=/src/index.js)
## Mini Notation API ## Mini Notation API
+97 -194
View File
@@ -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);
@@ -288,50 +288,48 @@ function peg$parse(input, options) {
var peg$f0 = function() { return parseFloat(text()); }; var peg$f0 = function() { return parseFloat(text()); };
var peg$f1 = function() { return parseInt(text()); }; var peg$f1 = function() { return parseInt(text()); };
var peg$f2 = function(chars) { const s = chars.join(""); return (s === ".") || (s === "_") }; var peg$f2 = function(chars) { return new AtomStub(chars.join("")) };
var peg$f3 = function(chars) { return new AtomStub(chars.join("")) }; var peg$f3 = function(s) { return s };
var peg$f4 = function(s) { return s }; var peg$f4 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; };
var peg$f5 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; }; var peg$f5 = function(a) { return a };
var peg$f6 = function(a) { return a }; var peg$f6 = function(s) { s.arguments_.alignment = 'slowcat'; return s; };
var peg$f7 = function(s) { s.arguments_.alignment = 'polymeter_slowcat'; return s; }; var peg$f7 = function(a) { return x => x.options_['weight'] = a };
var peg$f8 = function(a) { return x => x.options_['weight'] = (x.options_['weight'] ?? 1) + (a ?? 2) - 1 }; var peg$f8 = function(a) { return x => x.options_['reps'] = a };
var peg$f9 = function(a) { return x => x.options_['reps'] = (x.options_['reps'] ?? 1) + (a ?? 2) - 1 }; var peg$f9 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) };
var peg$f10 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) }; var peg$f10 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) };
var peg$f11 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) }; var peg$f11 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) };
var peg$f12 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) }; var peg$f12 = function(a) { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a, seed: seed++ } }) };
var peg$f13 = function(a) { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a, seed: seed++ } }) }; var peg$f13 = function(s) { return x => x.options_['ops'].push({ type_: "tail", arguments_ :{ element:s } }) };
var peg$f14 = function(s) { return x => x.options_['ops'].push({ type_: "tail", arguments_ :{ element:s } }) }; var peg$f14 = function(s) { return x => x.options_['ops'].push({ type_: "range", arguments_ :{ element:s } }) };
var peg$f15 = function(s) { return x => x.options_['ops'].push({ type_: "range", arguments_ :{ element:s } }) }; var peg$f15 = function(s, ops) { const result = new ElementStub(s, {ops: [], weight: 1, reps: 1});
var peg$f16 = function(s, ops) { const result = new ElementStub(s, {ops: [], weight: 1, reps: 1});
for (const op of ops) { for (const op of ops) {
op(result); op(result);
} }
return result; return result;
}; };
var peg$f17 = function(s) { return new PatternStub(s, 'fastcat'); }; var peg$f16 = function(s) { return new PatternStub(s, 'fastcat'); };
var peg$f18 = function(tail) { return { alignment: 'stack', list: tail }; }; var peg$f17 = function(tail) { return { alignment: 'stack', list: tail }; };
var peg$f19 = function(tail) { return { alignment: 'rand', list: tail, seed: seed++ }; }; var peg$f18 = function(tail) { return { alignment: 'rand', list: tail, seed: seed++ }; };
var peg$f20 = function(tail) { return { alignment: 'feet', list: tail, seed: seed++ }; }; var peg$f19 = function(head, tail) { if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment, tail.seed); } else { return head; } };
var peg$f21 = function(head, tail) { if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment, tail.seed); } else { return head; } }; var peg$f20 = function(head, tail) { return new PatternStub(tail ? [head, ...tail.list] : [head], 'polymeter'); };
var peg$f22 = function(head, tail) { return new PatternStub(tail ? [head, ...tail.list] : [head], 'polymeter'); }; var peg$f21 = function(sc) { return sc; };
var peg$f23 = function(sc) { return sc; }; var peg$f22 = function(s) { return { name: "struct", args: { mini:s }}};
var peg$f24 = function(s) { return { name: "struct", args: { mini:s }}}; var peg$f23 = function(s) { return { name: "target", args : { name:s}}};
var peg$f25 = function(s) { return { name: "target", args : { name:s}}}; var peg$f24 = function(p, s, r) { return { name: "bjorklund", args :{ pulse: p, step:parseInt(s) }}};
var peg$f26 = function(p, s, r) { return { name: "bjorklund", args :{ pulse: p, step:parseInt(s) }}}; var peg$f25 = function(a) { return { name: "stretch", args :{ amount: a}}};
var peg$f27 = function(a) { return { name: "stretch", args :{ amount: a}}}; var peg$f26 = function(a) { return { name: "shift", args :{ amount: "-"+a}}};
var peg$f28 = function(a) { return { name: "shift", args :{ amount: "-"+a}}}; var peg$f27 = function(a) { return { name: "shift", args :{ amount: a}}};
var peg$f29 = function(a) { return { name: "shift", args :{ amount: a}}}; var peg$f28 = function(a) { return { name: "stretch", args :{ amount: "1/"+a}}};
var peg$f30 = function(a) { return { name: "stretch", args :{ amount: "1/"+a}}}; var peg$f29 = function(s) { return { name: "scale", args :{ scale: s.join("")}}};
var peg$f31 = function(s) { return { name: "scale", args :{ scale: s.join("")}}}; var peg$f30 = function(s, v) { return v};
var peg$f32 = function(s, v) { return v}; var peg$f31 = function(s, ss) { ss.unshift(s); return new PatternStub(ss, 'slowcat'); };
var peg$f33 = function(s, ss) { ss.unshift(s); return new PatternStub(ss, 'slowcat'); }; var peg$f32 = function(sg) {return sg};
var peg$f34 = function(sg) {return sg}; var peg$f33 = function(o, soc) { return new OperatorStub(o.name,o.args,soc)};
var peg$f35 = function(o, soc) { return new OperatorStub(o.name,o.args,soc)}; var peg$f34 = function(sc) { return sc };
var peg$f36 = function(sc) { return sc }; var peg$f35 = function(c) { return c };
var peg$f37 = function(c) { return c }; var peg$f36 = function(v) { return new CommandStub("setcps", { value: v})};
var peg$f38 = function(v) { return new CommandStub("setcps", { value: v})}; var peg$f37 = function(v) { return new CommandStub("setcps", { value: (v/120/2)})};
var peg$f39 = function(v) { return new CommandStub("setcps", { value: (v/120/2)})}; var peg$f38 = function() { return new CommandStub("hush")};
var peg$f40 = function() { return new CommandStub("hush")};
var peg$currPos = 0; var peg$currPos = 0;
var peg$savedPos = 0; var peg$savedPos = 0;
var peg$posDetailsCache = [{ line: 1, column: 1 }]; var peg$posDetailsCache = [{ line: 1, column: 1 }];
@@ -823,30 +821,6 @@ function peg$parse(input, options) {
return s0; return s0;
} }
function peg$parsedot() {
var s0, s1, s2, s3;
s0 = peg$currPos;
s1 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 46) {
s2 = peg$c0;
peg$currPos++;
} else {
s2 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e1); }
}
if (s2 !== peg$FAILED) {
s3 = peg$parsews();
s1 = [s1, s2, s3];
s0 = s1;
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
return s0;
}
function peg$parsequote() { function peg$parsequote() {
var s0; var s0;
@@ -939,7 +913,7 @@ function peg$parse(input, options) {
} }
function peg$parsestep() { function peg$parsestep() {
var s0, s1, s2, s3, s4; var s0, s1, s2, s3;
s0 = peg$currPos; s0 = peg$currPos;
s1 = peg$parsews(); s1 = peg$parsews();
@@ -955,20 +929,8 @@ function peg$parse(input, options) {
} }
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
s3 = peg$parsews(); s3 = peg$parsews();
peg$savedPos = peg$currPos;
s4 = peg$f2(s2);
if (s4) {
s4 = peg$FAILED;
} else {
s4 = undefined;
}
if (s4 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f3(s2); s0 = peg$f2(s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1004,7 +966,7 @@ function peg$parse(input, options) {
if (s6 !== peg$FAILED) { if (s6 !== peg$FAILED) {
s7 = peg$parsews(); s7 = peg$parsews();
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f4(s4); s0 = peg$f3(s4);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1052,7 +1014,7 @@ function peg$parse(input, options) {
} }
s8 = peg$parsews(); s8 = peg$parsews();
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f5(s4, s7); s0 = peg$f4(s4, s7);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1084,7 +1046,7 @@ function peg$parse(input, options) {
s2 = peg$parseslice(); s2 = peg$parseslice();
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f6(s2); s0 = peg$f5(s2);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1111,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) {
@@ -1124,7 +1086,7 @@ function peg$parse(input, options) {
if (s6 !== peg$FAILED) { if (s6 !== peg$FAILED) {
s7 = peg$parsews(); s7 = peg$parsews();
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f7(s4); s0 = peg$f6(s4);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1188,33 +1150,25 @@ function peg$parse(input, options) {
} }
function peg$parseop_weight() { function peg$parseop_weight() {
var s0, s1, s2, s3; var s0, s1, s2;
s0 = peg$currPos; s0 = peg$currPos;
s1 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 64) { if (input.charCodeAt(peg$currPos) === 64) {
s2 = peg$c18; s1 = peg$c18;
peg$currPos++; peg$currPos++;
} else { } else {
s2 = peg$FAILED; s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e26); } if (peg$silentFails === 0) { peg$fail(peg$e26); }
} }
if (s2 === peg$FAILED) { if (s1 !== peg$FAILED) {
if (input.charCodeAt(peg$currPos) === 95) { s2 = peg$parsenumber();
s2 = peg$c10;
peg$currPos++;
} else {
s2 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e18); }
}
}
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
s3 = peg$parsenumber();
if (s3 === peg$FAILED) {
s3 = null;
}
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f8(s3); s0 = peg$f7(s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1224,24 +1178,25 @@ function peg$parse(input, options) {
} }
function peg$parseop_replicate() { function peg$parseop_replicate() {
var s0, s1, s2, s3; var s0, s1, s2;
s0 = peg$currPos; s0 = peg$currPos;
s1 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 33) { if (input.charCodeAt(peg$currPos) === 33) {
s2 = peg$c19; s1 = peg$c19;
peg$currPos++; peg$currPos++;
} else { } else {
s2 = peg$FAILED; s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e27); } if (peg$silentFails === 0) { peg$fail(peg$e27); }
} }
if (s1 !== peg$FAILED) {
s2 = peg$parsenumber();
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
s3 = peg$parsenumber();
if (s3 === peg$FAILED) {
s3 = null;
}
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f9(s3); s0 = peg$f8(s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
}
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1291,7 +1246,7 @@ function peg$parse(input, options) {
} }
if (s13 !== peg$FAILED) { if (s13 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f10(s3, s7, s11); s0 = peg$f9(s3, s7, s11);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1331,7 +1286,7 @@ function peg$parse(input, options) {
s2 = peg$parseslice(); s2 = peg$parseslice();
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f11(s2); s0 = peg$f10(s2);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1359,7 +1314,7 @@ function peg$parse(input, options) {
s2 = peg$parseslice(); s2 = peg$parseslice();
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f12(s2); s0 = peg$f11(s2);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1389,7 +1344,7 @@ function peg$parse(input, options) {
s2 = null; s2 = null;
} }
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f13(s2); s0 = peg$f12(s2);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1413,7 +1368,7 @@ function peg$parse(input, options) {
s2 = peg$parseslice(); s2 = peg$parseslice();
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f14(s2); s0 = peg$f13(s2);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1441,7 +1396,7 @@ function peg$parse(input, options) {
s2 = peg$parseslice(); s2 = peg$parseslice();
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f15(s2); s0 = peg$f14(s2);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1467,7 +1422,7 @@ function peg$parse(input, options) {
s3 = peg$parseslice_op(); s3 = peg$parseslice_op();
} }
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f16(s1, s2); s0 = peg$f15(s1, s2);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1492,7 +1447,7 @@ function peg$parse(input, options) {
} }
if (s1 !== peg$FAILED) { if (s1 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s1 = peg$f17(s1); s1 = peg$f16(s1);
} }
s0 = s1; s0 = s1;
@@ -1541,7 +1496,7 @@ function peg$parse(input, options) {
} }
if (s1 !== peg$FAILED) { if (s1 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s1 = peg$f18(s1); s1 = peg$f17(s1);
} }
s0 = s1; s0 = s1;
@@ -1590,56 +1545,7 @@ function peg$parse(input, options) {
} }
if (s1 !== peg$FAILED) { if (s1 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s1 = peg$f19(s1); s1 = peg$f18(s1);
}
s0 = s1;
return s0;
}
function peg$parsedot_tail() {
var s0, s1, s2, s3, s4;
s0 = peg$currPos;
s1 = [];
s2 = peg$currPos;
s3 = peg$parsedot();
if (s3 !== peg$FAILED) {
s4 = peg$parsesequence();
if (s4 !== peg$FAILED) {
s2 = s4;
} else {
peg$currPos = s2;
s2 = peg$FAILED;
}
} else {
peg$currPos = s2;
s2 = peg$FAILED;
}
if (s2 !== peg$FAILED) {
while (s2 !== peg$FAILED) {
s1.push(s2);
s2 = peg$currPos;
s3 = peg$parsedot();
if (s3 !== peg$FAILED) {
s4 = peg$parsesequence();
if (s4 !== peg$FAILED) {
s2 = s4;
} else {
peg$currPos = s2;
s2 = peg$FAILED;
}
} else {
peg$currPos = s2;
s2 = peg$FAILED;
}
}
} else {
s1 = peg$FAILED;
}
if (s1 !== peg$FAILED) {
peg$savedPos = s0;
s1 = peg$f20(s1);
} }
s0 = s1; s0 = s1;
@@ -1655,15 +1561,12 @@ function peg$parse(input, options) {
s2 = peg$parsestack_tail(); s2 = peg$parsestack_tail();
if (s2 === peg$FAILED) { if (s2 === peg$FAILED) {
s2 = peg$parsechoose_tail(); s2 = peg$parsechoose_tail();
if (s2 === peg$FAILED) {
s2 = peg$parsedot_tail();
}
} }
if (s2 === peg$FAILED) { if (s2 === peg$FAILED) {
s2 = null; s2 = null;
} }
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f21(s1, s2); s0 = peg$f19(s1, s2);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1683,7 +1586,7 @@ function peg$parse(input, options) {
s2 = null; s2 = null;
} }
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f22(s1, s2); s0 = peg$f20(s1, s2);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1706,7 +1609,7 @@ function peg$parse(input, options) {
s6 = peg$parsequote(); s6 = peg$parsequote();
if (s6 !== peg$FAILED) { if (s6 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f23(s4); s0 = peg$f21(s4);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1768,7 +1671,7 @@ function peg$parse(input, options) {
s3 = peg$parsemini_or_operator(); s3 = peg$parsemini_or_operator();
if (s3 !== peg$FAILED) { if (s3 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f24(s3); s0 = peg$f22(s3);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1801,7 +1704,7 @@ function peg$parse(input, options) {
s5 = peg$parsequote(); s5 = peg$parsequote();
if (s5 !== peg$FAILED) { if (s5 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f25(s4); s0 = peg$f23(s4);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1846,7 +1749,7 @@ function peg$parse(input, options) {
s7 = null; s7 = null;
} }
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f26(s3, s5, s7); s0 = peg$f24(s3, s5, s7);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1879,7 +1782,7 @@ function peg$parse(input, options) {
s3 = peg$parsenumber(); s3 = peg$parsenumber();
if (s3 !== peg$FAILED) { if (s3 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f27(s3); s0 = peg$f25(s3);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1908,7 +1811,7 @@ function peg$parse(input, options) {
s3 = peg$parsenumber(); s3 = peg$parsenumber();
if (s3 !== peg$FAILED) { if (s3 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f28(s3); s0 = peg$f26(s3);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1937,7 +1840,7 @@ function peg$parse(input, options) {
s3 = peg$parsenumber(); s3 = peg$parsenumber();
if (s3 !== peg$FAILED) { if (s3 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f29(s3); s0 = peg$f27(s3);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -1966,7 +1869,7 @@ function peg$parse(input, options) {
s3 = peg$parsenumber(); s3 = peg$parsenumber();
if (s3 !== peg$FAILED) { if (s3 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f30(s3); s0 = peg$f28(s3);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -2008,7 +1911,7 @@ function peg$parse(input, options) {
s5 = peg$parsequote(); s5 = peg$parsequote();
if (s5 !== peg$FAILED) { if (s5 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f31(s4); s0 = peg$f29(s4);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -2100,7 +2003,7 @@ function peg$parse(input, options) {
s9 = peg$parsemini_or_operator(); s9 = peg$parsemini_or_operator();
if (s9 !== peg$FAILED) { if (s9 !== peg$FAILED) {
peg$savedPos = s7; peg$savedPos = s7;
s7 = peg$f32(s5, s9); s7 = peg$f30(s5, s9);
} else { } else {
peg$currPos = s7; peg$currPos = s7;
s7 = peg$FAILED; s7 = peg$FAILED;
@@ -2117,7 +2020,7 @@ function peg$parse(input, options) {
s9 = peg$parsemini_or_operator(); s9 = peg$parsemini_or_operator();
if (s9 !== peg$FAILED) { if (s9 !== peg$FAILED) {
peg$savedPos = s7; peg$savedPos = s7;
s7 = peg$f32(s5, s9); s7 = peg$f30(s5, s9);
} else { } else {
peg$currPos = s7; peg$currPos = s7;
s7 = peg$FAILED; s7 = peg$FAILED;
@@ -2137,7 +2040,7 @@ function peg$parse(input, options) {
} }
if (s8 !== peg$FAILED) { if (s8 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f33(s5, s6); s0 = peg$f31(s5, s6);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -2183,7 +2086,7 @@ function peg$parse(input, options) {
s4 = peg$parsecomment(); s4 = peg$parsecomment();
} }
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f34(s1); s0 = peg$f32(s1);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -2205,7 +2108,7 @@ function peg$parse(input, options) {
s5 = peg$parsemini_or_operator(); s5 = peg$parsemini_or_operator();
if (s5 !== peg$FAILED) { if (s5 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f35(s1, s5); s0 = peg$f33(s1, s5);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -2230,7 +2133,7 @@ function peg$parse(input, options) {
s1 = peg$parsemini_or_operator(); s1 = peg$parsemini_or_operator();
if (s1 !== peg$FAILED) { if (s1 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s1 = peg$f36(s1); s1 = peg$f34(s1);
} }
s0 = s1; s0 = s1;
if (s0 === peg$FAILED) { if (s0 === peg$FAILED) {
@@ -2263,7 +2166,7 @@ function peg$parse(input, options) {
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
s3 = peg$parsews(); s3 = peg$parsews();
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f37(s2); s0 = peg$f35(s2);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -2288,7 +2191,7 @@ function peg$parse(input, options) {
s3 = peg$parsenumber(); s3 = peg$parsenumber();
if (s3 !== peg$FAILED) { if (s3 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f38(s3); s0 = peg$f36(s3);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -2317,7 +2220,7 @@ function peg$parse(input, options) {
s3 = peg$parsenumber(); s3 = peg$parsenumber();
if (s3 !== peg$FAILED) { if (s3 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s0 = peg$f39(s3); s0 = peg$f37(s3);
} else { } else {
peg$currPos = s0; peg$currPos = s0;
s0 = peg$FAILED; s0 = peg$FAILED;
@@ -2343,7 +2246,7 @@ function peg$parse(input, options) {
} }
if (s1 !== peg$FAILED) { if (s1 !== peg$FAILED) {
peg$savedPos = s0; peg$savedPos = s0;
s1 = peg$f40(); s1 = peg$f38();
} }
s0 = s1; s0 = s1;
+9 -15
View File
@@ -95,10 +95,9 @@ 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
dot = ws "." ws
quote = '"' / "'" quote = '"' / "'"
// ------------------ steps and cycles --------------------------- // ------------------ steps and cycles ---------------------------
@@ -106,8 +105,7 @@ quote = '"' / "'"
// single step definition (e.g bd) // single step definition (e.g bd)
step_char "a letter, a number, \"-\", \"#\", \".\", \"^\", \"_\"" = step_char "a letter, a number, \"-\", \"#\", \".\", \"^\", \"_\"" =
unicode_letter / [0-9~] / "-" / "#" / "." / "^" / "_" unicode_letter / [0-9~] / "-" / "#" / "." / "^" / "_"
step = ws chars:step_char+ ws { return new AtomStub(chars.join("")) }
step = ws chars:step_char+ ws !{ const s = chars.join(""); return (s === ".") || (s === "_") } { return new AtomStub(chars.join("")) }
// define a sub cycle e.g. [1 2, 3 [4]] // define a sub cycle e.g. [1 2, 3 [4]]
sub_cycle = ws "[" ws s:stack_or_choose ws "]" ws { return s } sub_cycle = ws "[" ws s:stack_or_choose ws "]" ws { return s }
@@ -121,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
@@ -131,11 +129,11 @@ slice = step / sub_cycle / polymeter / slow_sequence
// at this point, we assume we can represent them as regular sequence operators // at this point, we assume we can represent them as regular sequence operators
slice_op = op_weight / op_bjorklund / op_slow / op_fast / op_replicate / op_degrade / op_tail / op_range slice_op = op_weight / op_bjorklund / op_slow / op_fast / op_replicate / op_degrade / op_tail / op_range
op_weight = ws ("@" / "_") a:number? op_weight = "@" a:number
{ return x => x.options_['weight'] = (x.options_['weight'] ?? 1) + (a ?? 2) - 1 } { return x => x.options_['weight'] = a }
op_replicate = ws "!" a:number? op_replicate = "!"a:number
{ return x => x.options_['reps'] = (x.options_['reps'] ?? 1) + (a ?? 2) - 1 } { return x => x.options_['reps'] = a }
op_bjorklund = "(" ws p:slice_with_ops ws comma ws s:slice_with_ops ws comma? ws r:slice_with_ops? ws ")" op_bjorklund = "(" ws p:slice_with_ops ws comma ws s:slice_with_ops ws comma? ws r:slice_with_ops? ws ")"
{ return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) } { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) }
@@ -177,13 +175,9 @@ stack_tail = tail:(comma @sequence)+
choose_tail = tail:(pipe @sequence)+ choose_tail = tail:(pipe @sequence)+
{ return { alignment: 'rand', list: tail, seed: seed++ }; } { return { alignment: 'rand', list: tail, seed: seed++ }; }
// a foot separates subsequences, as an alternative to wrapping them in []
dot_tail = tail:(dot @sequence)+
{ return { alignment: 'feet', list: tail, seed: seed++ }; }
// if the stack contains only one element, we don't create a stack but return the // if the stack contains only one element, we don't create a stack but return the
// underlying element // underlying element
stack_or_choose = head:sequence tail:(stack_tail / choose_tail / dot_tail)? stack_or_choose = head:sequence tail:(stack_tail / choose_tail)?
{ if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment, tail.seed); } else { return head; } } { if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment, tail.seed); } else { return head; } }
polymeter_stack = head:sequence tail:stack_tail? polymeter_stack = head:sequence tail:stack_tail?
+13 -22
View File
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import * as krill from './krill-parser.js'; import * as krill from './krill-parser.js';
import * as strudel from '@strudel/core'; import * as strudel from '@strudel.cycles/core';
const randOffset = 0.0003; const randOffset = 0.0003;
@@ -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
@@ -107,13 +103,16 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
if (alignment === 'rand') { if (alignment === 'rand') {
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);
} }
if (alignment === 'feet') {
return strudel.fastcat(...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;
} }
@@ -161,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,
@@ -189,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 "")
+5 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "@strudel/mini", "name": "@strudel.cycles/mini",
"version": "1.0.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",
@@ -32,11 +32,11 @@
}, },
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*" "@strudel.cycles/core": "workspace:*"
}, },
"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"
} }
} }
+1 -15
View File
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import { getLeafLocation, getLeafLocations, mini, mini2ast } from '../mini.mjs'; import { getLeafLocation, getLeafLocations, mini, mini2ast } from '../mini.mjs';
import '@strudel/core/euclid.mjs'; import '@strudel.cycles/core/euclid.mjs';
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
describe('mini', () => { describe('mini', () => {
@@ -73,10 +73,6 @@ describe('mini', () => {
expect(minS('a!3 b')).toEqual(['a: 0 - 1/4', 'a: 1/4 - 1/2', 'a: 1/2 - 3/4', 'b: 3/4 - 1']); expect(minS('a!3 b')).toEqual(['a: 0 - 1/4', 'a: 1/4 - 1/2', 'a: 1/2 - 3/4', 'b: 3/4 - 1']);
expect(minS('[<a b c>]!3 d')).toEqual(minS('<a b c> <a b c> <a b c> d')); expect(minS('[<a b c>]!3 d')).toEqual(minS('<a b c> <a b c> <a b c> d'));
}); });
it('supports replication via repeated !', () => {
expect(minS('a ! ! b')).toEqual(['a: 0 - 1/4', 'a: 1/4 - 1/2', 'a: 1/2 - 3/4', 'b: 3/4 - 1']);
expect(minS('[<a b c>]!! d')).toEqual(minS('<a b c> <a b c> <a b c> d'));
});
it('supports euclidean rhythms', () => { it('supports euclidean rhythms', () => {
expect(minS('a(3, 8)')).toEqual(['a: 0 - 1/8', 'a: 3/8 - 1/2', 'a: 3/4 - 7/8']); expect(minS('a(3, 8)')).toEqual(['a: 0 - 1/8', 'a: 3/8 - 1/2', 'a: 3/4 - 7/8']);
}); });
@@ -194,16 +190,6 @@ describe('mini', () => {
it('supports patterned ranges', () => { it('supports patterned ranges', () => {
expect(minS('[<0 1> .. <2 4>]*2')).toEqual(minS('[0 1 2] [1 2 3 4]')); expect(minS('[<0 1> .. <2 4>]*2')).toEqual(minS('[0 1 2] [1 2 3 4]'));
}); });
it('supports the . operator', () => {
expect(minS('a . b c')).toEqual(minS('a [b c]'));
expect(minS('a . b c . [d e f . g h]')).toEqual(minS('a [b c] [[d e f] [g h]]'));
});
it('supports the _ operator', () => {
expect(minS('a _ b _ _')).toEqual(minS('a@2 b@3'));
});
it('_ and @ are almost interchangeable', () => {
expect(minS('a @ b @ @')).toEqual(minS('a _2 b _3'));
});
}); });
describe('getLeafLocation', () => { describe('getLeafLocation', () => {

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