mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-13 22:35:15 -04:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d649d3e940 | |||
| 5101bb298e | |||
| f2c8b76d75 | |||
| 07c9515de1 | |||
| 62c121da42 | |||
| 37a2ee5973 | |||
| 60deb461b5 | |||
| 6381a8deca |
@@ -0,0 +1,20 @@
|
||||
krill-parser.js
|
||||
krill.pegjs
|
||||
.eslintrc.json
|
||||
server.js
|
||||
tidal-sniffer.js
|
||||
*.jsx
|
||||
tunejs.js
|
||||
out/**
|
||||
postcss.config.js
|
||||
postcss.config.cjs
|
||||
tailwind.config.js
|
||||
tailwind.config.cjs
|
||||
vite.config.js
|
||||
/**/dist/**/*
|
||||
!**/*.mjs
|
||||
**/*.tsx
|
||||
**/*.ts
|
||||
**/*.json
|
||||
**/dev-dist
|
||||
**/dist
|
||||
+1
-3
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"env": {
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
@@ -14,6 +13,5 @@
|
||||
"rules": {
|
||||
"no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }],
|
||||
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}]
|
||||
},
|
||||
"files": ["**/*.mjs", "**/*.js"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
name: Build and Deploy to beta (warm.strudel.cc)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: docker
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.2
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
# cache: "pnpm"
|
||||
- name: Install Dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Deploy
|
||||
run: |
|
||||
eval $(ssh-agent -s)
|
||||
echo "$SSH_PRIVATE_KEY" | ssh-add -
|
||||
apt update && apt install -y rsync
|
||||
mkdir ~/.ssh
|
||||
ssh-keyscan matrix.toplap.org > ~/.ssh/known_hosts
|
||||
rsync -atv --delete --delete-after --progress ./website/dist/ strudel@matrix.toplap.org:/home/strudel/deploy/warm.strudel.cc
|
||||
@@ -1,37 +0,0 @@
|
||||
name: Build and Deploy to live (strudel.cc)
|
||||
|
||||
on: [workflow_dispatch]
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: docker
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.12.2
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
# cache: "pnpm"
|
||||
- name: Install Dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Deploy
|
||||
run: |
|
||||
eval $(ssh-agent -s)
|
||||
echo "$SSH_PRIVATE_KEY" | ssh-add -
|
||||
apt update && apt install -y rsync
|
||||
mkdir ~/.ssh
|
||||
ssh-keyscan matrix.toplap.org > ~/.ssh/known_hosts
|
||||
rsync -atv --delete --delete-after --progress ./website/dist/ strudel@matrix.toplap.org:/home/strudel/deploy/strudel.cc
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Build and Deploy
|
||||
|
||||
on: [workflow_dispatch]
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
deployments: write
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 7
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install Dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v2
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
# Upload entire repository
|
||||
path: "./website/dist"
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
||||
@@ -0,0 +1,62 @@
|
||||
name: Tauri Builder
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [macos-latest, ubuntu-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
rust_target: x86_64-unknown-linux-gnu
|
||||
- os: macos-latest
|
||||
rust_target: x86_64-apple-darwin
|
||||
- os: macos-latest
|
||||
rust_target: aarch64-apple-darwin
|
||||
- os: windows-latest
|
||||
rust_target: x86_64-pc-windows-msvc
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8.6.2
|
||||
|
||||
- name: Node.js setup
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: latest
|
||||
# node-version-file: '.nvmrc'
|
||||
|
||||
- name: Install Rust (Stable)
|
||||
run:
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Install app dependencies from lockfile and build web
|
||||
run: pnpm install
|
||||
|
||||
- name: Build the app
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
# tauri-action replaces \_\_VERSION\_\_ with the app version
|
||||
tagName: ${{ github.ref_name }}
|
||||
releaseName: "Strudel v__VERSION__"
|
||||
releaseBody: |
|
||||
See the assets to download this version and install.
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
@@ -4,22 +4,20 @@ on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: docker
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20]
|
||||
node-version: [18]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: apt install ztd
|
||||
run: apt update && apt install -y zstd
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/checkout@v2
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 9.12.2
|
||||
- uses: actions/setup-node@v4
|
||||
version: 7
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "pnpm"
|
||||
cache: 'pnpm'
|
||||
- run: pnpm install
|
||||
- run: pnpm run format-check
|
||||
- run: pnpm run lint
|
||||
+1
-102
@@ -43,105 +43,4 @@ dev-dist
|
||||
Dirt-Samples
|
||||
tidal-drum-machines
|
||||
webaudiofontdata
|
||||
src-tauri/target
|
||||
|
||||
|
||||
|
||||
### BEGIN Visual Studio Code ###
|
||||
# Blanket, recursive exclude for .vscode directory and files
|
||||
.vscode/**/*
|
||||
# Unexclude specific files and directories within .vscode
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
### END Visual Studio Code ###
|
||||
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
samples/*
|
||||
!samples/README.md
|
||||
.idea/
|
||||
src-tauri/target
|
||||
+1
-5
@@ -9,8 +9,4 @@ packages/xen/tunejs.js
|
||||
paper
|
||||
pnpm-lock.yaml
|
||||
pnpm-workspace.yaml
|
||||
**/dev-dist
|
||||
website/.astro
|
||||
!tidal-drum-machines.json
|
||||
!tidal-drum-machines-alias.json
|
||||
.pnpm-store
|
||||
**/dev-dist
|
||||
-1081
File diff suppressed because it is too large
Load Diff
+21
-118
@@ -2,21 +2,18 @@
|
||||
|
||||
Thanks for wanting to contribute!!! There are many ways you can add value to this project
|
||||
|
||||
## Move to codeberg
|
||||
|
||||
Along with many other live coding projects, we have moved from Microsoft's Github platform to Codeberg for ethical reasons. Please don't fork the project back to github.
|
||||
|
||||
## Communication Channels
|
||||
|
||||
To get in touch with the community, either
|
||||
To get in touch with the contributors, either
|
||||
|
||||
- [join the Uzulang Discord Server](https://discord.com/invite/HGEdXmRkzT) and go to the strudel channels (Uzulangs are a family of live coding languages inspired by each other, including TidalCycles as well as Strudel)
|
||||
- open a [github discussion](https://github.com/tidalcycles/strudel/discussions) or
|
||||
- [join the Tidal Discord Channel](https://discord.gg/remJ6gQA) and go to the #strudel channel
|
||||
- Find related discussions on the [tidal club forum](https://club.tidalcycles.org/)
|
||||
|
||||
## Ask a Question
|
||||
|
||||
If you have any questions about strudel, make sure you've glanced through the
|
||||
[docs](https://strudel.cc/learn/) to find out if it answers your question.
|
||||
[docs](https://strudel.tidalcycles.org/learn/) to find out if it answers your question.
|
||||
If not, use one of the Communication Channels above!
|
||||
|
||||
Don't be afraid to ask! Your question might be of great value for other people too.
|
||||
@@ -29,35 +26,22 @@ Use one of the Communication Channels listed above and drop us a line or two!
|
||||
## Share Music
|
||||
|
||||
If you made some music with strudel, you can give back some love and share what you've done!
|
||||
Your creation could also be part of the random selection in the REPL if you want.
|
||||
Use one of the Communication Channels listed above.
|
||||
(There used to be a random selection of contributed patterns in the REPL, but that is unfortunately disabled for now due to abuse)
|
||||
|
||||
## Improve the Docs
|
||||
|
||||
If you find some weak spots in the [docs](https://strudel.cc/workshop/getting-started/), you can edit each file directly on codeberg. There are "Edit this page" links in the right sidebar that take you to the right place.
|
||||
If you find some weak spots in the [docs](https://strudel.tidalcycles.org/workshop/getting-started/),
|
||||
you can edit each file directly on github via the "Edit this page" link located in the right sidebar.
|
||||
|
||||
## Propose a Feature
|
||||
|
||||
If you want a specific feature that is not part of strudel yet, feel free to use one of the communication channels above. Please bear in mind that this is a free/open source project, oriented around collaborative discussion. Maybe you even want to help with the implementation of that feature!
|
||||
|
||||
## Contribute a feature
|
||||
|
||||
Pull requests welcome! Consider starting with discussion or proof-of-concept first, rather than do loads of work on something and then find it doesn't fit the collective goals of the project, or something like that.
|
||||
|
||||
At the time of writing we have a PR backlog, and generally prioritise bugfixes and contributions that have arisen through community discussion.
|
||||
|
||||
### AI/LLM policy
|
||||
|
||||
Strudel is a project handmade by humans, with thought and nuance.
|
||||
|
||||
If you have used LLMs (so called 'AI'), please detail that in the pull request. We are still developing our response to the onslaught of LLM technology, but for practical and legal reasons are currently not accepting wholly LLM-generated code. We are also not accepting PRs that add LLM features to strudel itself.
|
||||
|
||||
There are #llm-chat and #llm-share channels on our discord. Please do not discuss or share LLM-related things outside of those channels.
|
||||
If you want a specific feature that is not part of strudel yet, feel free to use one of the communication channels above.
|
||||
Maybe you even want to help with the implementation of that feature!
|
||||
|
||||
## Report a Bug
|
||||
|
||||
If you've found a bug, or some behaviour that does not seem right, you are welcome to file an [issue](https://codeberg.org/uzu/strudel/issues).
|
||||
|
||||
If you've found a bug, or some behaviour that does not seem right, you are welcome to file an [issue](https://github.com/tidalcycles/strudel/issues).
|
||||
Please check that it has not been reported before.
|
||||
|
||||
## Fix a Bug
|
||||
@@ -82,12 +66,12 @@ To get the project up and running for development, make sure you have installed:
|
||||
|
||||
- [git](https://git-scm.com/)
|
||||
- [node](https://nodejs.org/en/) >= 18
|
||||
- [pnpm](https://pnpm.io/) (`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:
|
||||
|
||||
```sh
|
||||
git clone https://codeberg.org/uzu/strudel.git && cd strudel
|
||||
git clone https://github.com/tidalcycles/strudel.git && cd strudel
|
||||
pnpm i # install at root to symlink packages
|
||||
pnpm start # start repl
|
||||
```
|
||||
@@ -99,7 +83,7 @@ Please report any problems you've had with the setup instructions!
|
||||
|
||||
To make sure the code changes only where it should, we are using prettier to unify the code style.
|
||||
|
||||
- You can format all files at once by running `pnpm codeformat` from the project root
|
||||
- You can format all files at once by running `pnpm prettier` from the project root
|
||||
- Run `pnpm format-check` from the project root to check if all files are well formatted
|
||||
|
||||
If you use VSCode, you can
|
||||
@@ -124,12 +108,13 @@ There are also eslint extensions / plugins for most editors.
|
||||
|
||||
## Running all CI Checks
|
||||
|
||||
When opening a PR, the CI runner will (once approved by a human) check the code style and eslint, as well as run all tests. You can run the same check with `pnpm check`.
|
||||
When opening a PR, the CI runner will automatically check the code style and eslint, as well as run all tests.
|
||||
You can run the same check with `pnpm check`
|
||||
|
||||
## Package Workflow
|
||||
|
||||
The project is split into multiple [packages](https://codeberg.org/uzu/strudel/src/branch/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,
|
||||
The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning.
|
||||
When you run `pnpm i` on the root folder, [pnpm workspaces](https://pnpm.io/workspaces) will install all dependencies of all subpackages. This will allow any js file to import `@strudel.cycles/<package-name>` to get the local version,
|
||||
allowing to develop multiple packages at the same time.
|
||||
|
||||
## Package Publishing
|
||||
@@ -145,102 +130,20 @@ npx lerna version --no-private
|
||||
# publish all packages inside /packages using pnpm! don't use lerna to publish!!
|
||||
pnpm --filter "./packages/**" publish --dry-run
|
||||
|
||||
# the last command was only a dry-run. if everything looks ok, run this:
|
||||
|
||||
pnpm --filter "./packages/**" publish --access public
|
||||
# the last command was only a dry-run, make sure everything looks ok, if yes, run the same command without flag
|
||||
```
|
||||
|
||||
To manually publish a single package, increase the version in the `package.json`, then run `pnpm publish`.
|
||||
Important: Always publish with `pnpm`, as `npm` does not support overriding main files in `publishConfig`, which is done in all the packages.
|
||||
|
||||
### New Packages
|
||||
|
||||
## useful commands
|
||||
To add a new package, you have to publish it manually the first time, using:
|
||||
|
||||
```sh
|
||||
#regenerate the test snapshots (ex: when updating or creating new pattern functions)
|
||||
pnpm snapshot
|
||||
|
||||
#start the OSC server
|
||||
pnpm run osc
|
||||
|
||||
#build the standalone version
|
||||
pnpm tauri build
|
||||
cd packages/<package-name> && pnpm publish --access public
|
||||
```
|
||||
|
||||
## version tag patching
|
||||
|
||||
here's a little guide on how to patch patterns in the database to prevent breaking old patterns due to breaking changes in newer versions.
|
||||
|
||||
the general tactic is to use `// @version x.y` to tag a pattern with a specific strudel version. when a pattern is evaluated, this metadata will de-activate any breaking changes that came after the specified version.
|
||||
for example, in version 1.1, the default value for `fanchor` was changed from `0.5` to `0`.
|
||||
if play a pattern that was made before that change, sounds that use filter evenlopes can sound very different, so by adding `// @version 1.0` will make it sound like it used to.
|
||||
before releasing a new version with breaking changes, we can edit all patterns in the database, inserting the version tag they were created under:
|
||||
|
||||
as an example, to release version 1.2, do the following:
|
||||
|
||||
1. get date range
|
||||
|
||||
```sh
|
||||
# get date of last version:
|
||||
git log -1 --format=%aI @strudel/core@1.1.0
|
||||
# 2024-05-31T23:07:26+02:00
|
||||
|
||||
# get date of current version:
|
||||
git log -1 --format=%aI @strudel/core@1.2.0
|
||||
# 2025-05-01T12:39:24+02:00
|
||||
# might also use todays timestamp if version is not yet released
|
||||
```
|
||||
|
||||
now we know, all patterns between these 2 dates have to receive a version tag (unless they already have one).
|
||||
|
||||
2. get patterns in question
|
||||
|
||||
```sql
|
||||
SELECT *
|
||||
FROM code_v1
|
||||
WHERE code NOT LIKE '%@version%'
|
||||
AND created_at > '2024-05-31T23:07:26+02:00'
|
||||
AND created_at < '2025-05-01T12:39:24+02:00'
|
||||
ORDER BY created_at ASC;
|
||||
```
|
||||
|
||||
this gives us all unversioned patterns that were saved between 1.1.0 and 1.2.0. in this case, it's 9373 patterns!
|
||||
|
||||
3. insert version tags
|
||||
|
||||
we are now ready to insert the version tag to these patterns.
|
||||
before updating thousands of patterns, it's probably a good idea to test if a single one gets udpated:
|
||||
|
||||
```sql
|
||||
UPDATE code_v1
|
||||
SET code = code || E'\n// @version 1.1'
|
||||
WHERE hash = 'Ns2sMB40yIw4';
|
||||
```
|
||||
|
||||
after [verifying](https://strudel.cc/?Ns2sMB40yIw4) that the version tag has been added, let's insert it everywhere:
|
||||
|
||||
```sql
|
||||
UPDATE code_v1
|
||||
SET code = code || E'\n// @version 1.1'
|
||||
WHERE code NOT LIKE '%@version%'
|
||||
AND created_at > '2024-05-31T23:07:26+02:00'
|
||||
AND created_at < '2025-05-01T12:39:24+02:00'
|
||||
```
|
||||
|
||||
4. verify
|
||||
|
||||
we can verify that the edits worked by querying all patterns that contain the new version tag:
|
||||
|
||||
```sql
|
||||
SELECT *
|
||||
FROM code_v1
|
||||
WHERE code LIKE '%@version 1.1%'
|
||||
AND created_at > '2024-05-31T23:07:26+02:00'
|
||||
AND created_at < '2025-05-01T12:39:24+02:00'
|
||||
ORDER BY created_at ASC;
|
||||
```
|
||||
|
||||
|
||||
## Have Fun
|
||||
|
||||
Remember to have fun, and that this project is driven by the passion of volunteers!
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
FROM node:24
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN npm install pnpm --global
|
||||
|
||||
COPY pnpm-workspace.yaml ./
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
COPY packages/ ./packages/
|
||||
COPY examples/ ./examples/
|
||||
RUN mkdir -p website/public
|
||||
COPY website/package.json ./website/
|
||||
|
||||
RUN pnpm install
|
||||
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 4321
|
||||
|
||||
CMD ["pnpm", "dev"]
|
||||
@@ -1,45 +1,49 @@
|
||||
# strudel
|
||||
|
||||
Live coding patterns on the web
|
||||
[](https://github.com/tidalcycles/strudel/actions)
|
||||
|
||||
An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using web technologies. This software is slowly stabilising, but please continue to tread carefully.
|
||||
|
||||
- Try it here: <https://strudel.cc>
|
||||
- Docs: <https://strudel.cc/learn>
|
||||
- Source: https://codeberg.org/uzu/strudel/
|
||||
* Along with many other live coding projects, we have moved from Microsoft's Github platform to Codeberg for ethical reasons. **Please don't fork the project back to github**.
|
||||
- Try it here: <https://strudel.tidalcycles.org/>
|
||||
- Docs: <https://strudel.tidalcycles.org/learn/>
|
||||
- Technical Blog Post: <https://loophole-letters.vercel.app/strudel>
|
||||
- 1 Year of Strudel Blog Post: <https://loophole-letters.vercel.app/strudel1year>
|
||||
- 2 Years of Strudel Blog Post: <https://strudel.cc/blog/#year-2>
|
||||
|
||||
|
||||
## Running Locally
|
||||
|
||||
After cloning the project, you can run the REPL locally:
|
||||
|
||||
1. Install [Node.js](https://nodejs.org/) 18 or newer
|
||||
2. Install [pnpm](https://pnpm.io/installation)
|
||||
3. Install dependencies by running the following command:
|
||||
```bash
|
||||
pnpm i
|
||||
```
|
||||
4. Run the development server:
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
```bash
|
||||
pnpm run setup
|
||||
pnpm run repl
|
||||
```
|
||||
|
||||
## Using Strudel In Your Project
|
||||
|
||||
This project is organized into many [packages](./packages), which are also available on [npm](https://www.npmjs.com/search?q=%40strudel).
|
||||
There are multiple npm packages you can use to use strudel, or only parts of it, in your project:
|
||||
|
||||
Read more about how to use these in your own project [here](https://strudel.cc/technical-manual/project-start).
|
||||
- [`core`](./packages/core/): tidal pattern engine
|
||||
- [`mini`](./packages/mini): mini notation parser + core binding
|
||||
- [`transpiler`](./packages/transpiler): user code transpiler
|
||||
- [`webaudio`](./packages/webaudio): webaudio output
|
||||
- [`osc`](./packages/osc): bindings to communicate via OSC
|
||||
- [`midi`](./packages/midi): webmidi bindings
|
||||
- [`serial`](./packages/serial): webserial bindings
|
||||
- [`tonal`](./packages/tonal): tonal functions
|
||||
- [`xen`](./packages/xen): microtonal / xenharmonic functions
|
||||
- ... [and there are more](./packages/)
|
||||
|
||||
You will need to abide by the terms of the [GNU Affero Public Licence v3](LICENSE). As such, Strudel code can only be shared within free/open source projects under the same license -- see the license for details.
|
||||
|
||||
Licensing info for the default sound banks can be found over on the [dough-samples](https://github.com/felixroos/dough-samples/blob/main/README.md) repository.
|
||||
Click on the package names to find out more about each one.
|
||||
|
||||
## Contributing
|
||||
|
||||
There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md). You can find the full list of contributors [here](https://codeberg.org/uzu/strudel/activity/contributors).
|
||||
There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md).
|
||||
|
||||
<a href="https://github.com/tidalcycles/strudel/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=tidalcycles/strudel" />
|
||||
</a>
|
||||
|
||||
Made with [contrib.rocks](https://contrib.rocks).
|
||||
|
||||
## Community
|
||||
|
||||
@@ -48,5 +52,3 @@ There is a #strudel channel on the TidalCycles discord: <https://discord.com/inv
|
||||
You can also ask questions and find related discussions on the tidal club forum: <https://club.tidalcycles.org/>
|
||||
|
||||
The discord and forum is shared with the haskell (tidal) and python (vortex) siblings of this project.
|
||||
|
||||
We also have a mastodon account: <a rel="me" href="https://social.toplap.org/@strudel">social.toplap.org/@strudel</a>
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { queryCode, testCycles } from '../test/runtime.mjs';
|
||||
import * as tunes from '../website/src/repl/tunes.mjs';
|
||||
import { describe, bench } from 'vitest';
|
||||
import { calculateSteps } from '../packages/core/index.mjs';
|
||||
|
||||
const tuneKeys = Object.keys(tunes);
|
||||
|
||||
describe('renders tunes', () => {
|
||||
tuneKeys.forEach((key) => {
|
||||
describe(key, () => {
|
||||
calculateSteps(true);
|
||||
bench(`+steps`, async () => {
|
||||
await queryCode(tunes[key], testCycles[key] || 1);
|
||||
});
|
||||
calculateSteps(false);
|
||||
bench(`-steps`, async () => {
|
||||
await queryCode(tunes[key], testCycles[key] || 1);
|
||||
});
|
||||
calculateSteps(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
+1806
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 150 KiB |
@@ -1,954 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="Content-Style-Type" content="text/css" />
|
||||
<meta name="generator" content="pandoc" />
|
||||
<meta name="date" content="2022-12-14" />
|
||||
<title>Strudel: live coding patterns on the Web</title>
|
||||
<style type="text/css">
|
||||
code {
|
||||
white-space: pre;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
pre > code.sourceCode {
|
||||
white-space: pre;
|
||||
position: relative;
|
||||
}
|
||||
pre > code.sourceCode > span {
|
||||
display: inline-block;
|
||||
line-height: 1.25;
|
||||
}
|
||||
pre > code.sourceCode > span:empty {
|
||||
height: 1.2em;
|
||||
}
|
||||
.sourceCode {
|
||||
overflow: visible;
|
||||
}
|
||||
code.sourceCode > span {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
div.sourceCode {
|
||||
margin: 1em 0;
|
||||
}
|
||||
pre.sourceCode {
|
||||
margin: 0;
|
||||
}
|
||||
@media screen {
|
||||
div.sourceCode {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
@media print {
|
||||
pre > code.sourceCode {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
pre > code.sourceCode > span {
|
||||
text-indent: -5em;
|
||||
padding-left: 5em;
|
||||
}
|
||||
}
|
||||
pre.numberSource code {
|
||||
counter-reset: source-line 0;
|
||||
}
|
||||
pre.numberSource code > span {
|
||||
position: relative;
|
||||
left: -4em;
|
||||
counter-increment: source-line;
|
||||
}
|
||||
pre.numberSource code > span > a:first-child::before {
|
||||
content: counter(source-line);
|
||||
position: relative;
|
||||
left: -1em;
|
||||
text-align: right;
|
||||
vertical-align: baseline;
|
||||
border: none;
|
||||
display: inline-block;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
padding: 0 4px;
|
||||
width: 4em;
|
||||
color: #aaaaaa;
|
||||
}
|
||||
pre.numberSource {
|
||||
margin-left: 3em;
|
||||
border-left: 1px solid #aaaaaa;
|
||||
padding-left: 4px;
|
||||
}
|
||||
div.sourceCode {
|
||||
}
|
||||
@media screen {
|
||||
pre > code.sourceCode > span > a:first-child::before {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
code span.al {
|
||||
color: #ff0000;
|
||||
font-weight: bold;
|
||||
} /* Alert */
|
||||
code span.an {
|
||||
color: #60a0b0;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
} /* Annotation */
|
||||
code span.at {
|
||||
color: #7d9029;
|
||||
} /* Attribute */
|
||||
code span.bn {
|
||||
color: #40a070;
|
||||
} /* BaseN */
|
||||
code span.bu {
|
||||
color: #008000;
|
||||
} /* BuiltIn */
|
||||
code span.cf {
|
||||
color: #007020;
|
||||
font-weight: bold;
|
||||
} /* ControlFlow */
|
||||
code span.ch {
|
||||
color: #4070a0;
|
||||
} /* Char */
|
||||
code span.cn {
|
||||
color: #880000;
|
||||
} /* Constant */
|
||||
code span.co {
|
||||
color: #60a0b0;
|
||||
font-style: italic;
|
||||
} /* Comment */
|
||||
code span.cv {
|
||||
color: #60a0b0;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
} /* CommentVar */
|
||||
code span.do {
|
||||
color: #ba2121;
|
||||
font-style: italic;
|
||||
} /* Documentation */
|
||||
code span.dt {
|
||||
color: #902000;
|
||||
} /* DataType */
|
||||
code span.dv {
|
||||
color: #40a070;
|
||||
} /* DecVal */
|
||||
code span.er {
|
||||
color: #ff0000;
|
||||
font-weight: bold;
|
||||
} /* Error */
|
||||
code span.ex {
|
||||
} /* Extension */
|
||||
code span.fl {
|
||||
color: #40a070;
|
||||
} /* Float */
|
||||
code span.fu {
|
||||
color: #06287e;
|
||||
} /* Function */
|
||||
code span.im {
|
||||
color: #008000;
|
||||
font-weight: bold;
|
||||
} /* Import */
|
||||
code span.in {
|
||||
color: #60a0b0;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
} /* Information */
|
||||
code span.kw {
|
||||
color: #007020;
|
||||
font-weight: bold;
|
||||
} /* Keyword */
|
||||
code span.op {
|
||||
color: #666666;
|
||||
} /* Operator */
|
||||
code span.ot {
|
||||
color: #007020;
|
||||
} /* Other */
|
||||
code span.pp {
|
||||
color: #bc7a00;
|
||||
} /* Preprocessor */
|
||||
code span.sc {
|
||||
color: #4070a0;
|
||||
} /* SpecialChar */
|
||||
code span.ss {
|
||||
color: #bb6688;
|
||||
} /* SpecialString */
|
||||
code span.st {
|
||||
color: #4070a0;
|
||||
} /* String */
|
||||
code span.va {
|
||||
color: #19177c;
|
||||
} /* Variable */
|
||||
code span.vs {
|
||||
color: #4070a0;
|
||||
} /* VerbatimString */
|
||||
code span.wa {
|
||||
color: #60a0b0;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
} /* Warning */
|
||||
</style>
|
||||
<link rel="stylesheet" href="css/iclc.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<h1 class="title">Strudel: live coding patterns on the Web</h1>
|
||||
<ul id="authorlist">
|
||||
<li>true</li>
|
||||
<li>true</li>
|
||||
</ul>
|
||||
<h3 class="date">2022-12-14</h3>
|
||||
</div>
|
||||
|
||||
<h2 class="abstract">Abstract</h2>
|
||||
<div id="abstract">
|
||||
<p>
|
||||
This paper introduces Strudel, which brings the TidalCycles approach to live coding algorithmic patterns to
|
||||
native JavaScript and the web. We begin by giving a little background of the first year of development, before
|
||||
sharing some detail about its implementation and examples of use. We go on to outline the wide range of
|
||||
synthesis and other outputs available in Strudel, including WebAudio, MIDI, OSC (for SuperDirt), WebSerial and
|
||||
CSound, and introduce Strudel’s REPL live editor, including its built-in visualisations. We then compare Strudel
|
||||
with Tidal, the trade-offs involved between JavaScript and Haskell, and the unique capabilities offered by
|
||||
Strudel for aligning patterns.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h1 data-number="1" id="introduction"><span class="header-section-number">1</span> Introduction</h1>
|
||||
<p>
|
||||
In the following paper, we introduce <em>Strudel</em>, an alternative implementation of the TidalCycles (or
|
||||
‘Tidal’ for short) live coding system, using the JavaScript programming language. Strudel is an attempt to make
|
||||
live coding more accessible, by creating a system that runs entirely in the browser, while opening Tidal’s
|
||||
approach to algorithmic patterns
|
||||
<span class="citation" data-cites="mcleanAlgorithmicPattern2020a">(Mclean 2020)</span> up to modern audio/visual
|
||||
web technologies. The Strudel REPL is a live code editor dedicated to manipulating patterns while they play, with
|
||||
builtin visual feedback. While Strudel is written in JavaScript, the API is optimized for simplicity and
|
||||
readability by applying code transformations on the syntax tree level, allowing language operations that would
|
||||
otherwise be impossible. The application supports multiple ways to output sound, including Tone.js, Web Audio
|
||||
Nodes, OSC (Open Sound Control) messages, Web Serial, Web MIDI and Csound. The project is split into multiple
|
||||
packages, allowing granular reuse in other applications. Apart from TidalCycles, Strudel draws inspiration from
|
||||
many prior existing projects like TidalVortex
|
||||
<span class="citation" data-cites="mcleanTidalVortexZero2022">(McLean et al. 2022)</span>, Gibber
|
||||
<span class="citation" data-cites="robertsGibberLiveCoding2012">(Roberts and Kuchera-morin 2012)</span>, Estuary
|
||||
<span class="citation" data-cites="ogbornEstuaryBrowserbasedCollaborative2017">(Ogborn et al. 2017)</span>, Hydra
|
||||
<span class="citation" data-cites="jackHydra2022">(Jack [2022] 2022)</span>, Ocarina
|
||||
<span class="citation" data-cites="solomonPurescriptocarina2022">(Solomon [2021] 2022)</span> and Feedforward
|
||||
<span class="citation" data-cites="mcleanFeedforward2020">(McLean 2020)</span>. This paper expands the Strudel
|
||||
Demo paper for the Web Audio Conference 2022
|
||||
<span class="citation" data-cites="StrudelWAC2022">(Roos and McLean 2022)</span>.
|
||||
</p>
|
||||
<p>
|
||||
The first tentative commit to the Strudel project was on 22nd January 2022 by Alex McLean, with the core
|
||||
representation implemented over the following few days. Although this was his first attempt at a JavaScript-based
|
||||
application, by 27th January, Alex had managed to upload the initial version to the ‘npm’ javascript package
|
||||
database, sharing with the wider community for comment. By 4th February, Felix Roos had discovered Strudel and
|
||||
contributed a ‘REPL’ user interface to it, and then contributed a scheduler the next day, so that Strudel could
|
||||
already make sound. At this point, Alex and Felix shared ownership to the repository, and the project has since
|
||||
proved to be a productive confluence of Felix’s own work into music representation and visualisation, with Alex’s
|
||||
experience with making Tidal. Felix has since become the primary contributor to Strudel, with Alex continuing to
|
||||
jump between developing both Strudel and Tidal. Aspects of Strudel’s development have therefore fed back into
|
||||
TidalCycles, and both systems have maintained a shared conceptual underpinning. We plan to continue working
|
||||
towards feature parity between these systems, although within the syntactical trade-offs and library ecosystems of
|
||||
JavaScript and Haskell, some divergence is inevitable and healthy.
|
||||
</p>
|
||||
<p>
|
||||
Over the first year of its life, Strudel is now a fully-fledged live coding environment, porting Tidal’s core
|
||||
represention of patterns, pattern transformations, and mininotation for polymetric sequences, combined with a
|
||||
wealth of features for synthesising and visualising those patterns.
|
||||
</p>
|
||||
<h1 data-number="2" id="from-tidal-to-strudel-and-back">
|
||||
<span class="header-section-number">2</span> From Tidal to Strudel and back
|
||||
</h1>
|
||||
<p>
|
||||
As mentioned above, the original Tidal is implemented as a domain specific language (DSL) embedded in the Haskell
|
||||
pure functional programming language, and takes advantage of Haskell’s terse syntax and advanced, ‘strong’ type
|
||||
system. JavaScript on the other hand, is a multi-paradigm programming language, with a dynamic type system.
|
||||
Because Tidal leans heavily on many of Haskell’s more unique features, it was not always clear that it could
|
||||
meaningfully be ported to a multi-paradigm scripting language. However, this possibility was already demonstrated
|
||||
with an earlier port to Python [TidalVortex;
|
||||
<span class="citation" data-cites="mcleanTidalVortexZero2022">McLean et al. (2022)</span>], and we have now
|
||||
successfully implemented Tidal’s pure functional representation of patterns in Strudel, including partial
|
||||
application, currying, and the functor, applicative and monadic structures that underlie Tidal’s expressive
|
||||
pattern transformations. The result is a terse and highly composable system, where everything is either a pattern,
|
||||
or a function for combining and manipulating patterns, offering a rich creative ground for exploration.
|
||||
</p>
|
||||
<p>
|
||||
This development process has been far from a one-way port, however. The process of porting Tidal’s concepts has
|
||||
also opened up new possibilities, some just from revisiting every design decision, and some from the particular
|
||||
affordances and constraints offered by JavaScript. This has lead to new features (and indeed bugfixes) that have
|
||||
found their way back to Tidal where appropriate, and ongoing work that we will return to in the conclusion of this
|
||||
paper.
|
||||
</p>
|
||||
<h1 data-number="3" id="representing-patterns">
|
||||
<span class="header-section-number">3</span> Representing Patterns
|
||||
</h1>
|
||||
<p>
|
||||
Patterns are the essence of Tidal. Its patterns are abstract entities that represent flows of time as functions,
|
||||
adapting a technique called pure functional reactive programming. Taking a time span as its input, a Pattern can
|
||||
output a set of events that happen within that time span. It depends on the structure of the Pattern how the
|
||||
events are located in time. From now on, this process of generating events from a time span will be called
|
||||
<strong>querying</strong>. Example:
|
||||
</p>
|
||||
<div class="sourceCode" id="cb1">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> pattern <span class="op">=</span> <span class="fu">sequence</span>(c3<span class="op">,</span> [e3<span class="op">,</span> g3])</span>
|
||||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> events <span class="op">=</span> pattern<span class="op">.</span><span class="fu">queryArc</span>(<span class="dv">0</span><span class="op">,</span> <span class="dv">1</span>)</span>
|
||||
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="bu">console</span><span class="op">.</span><span class="fu">log</span>(events<span class="op">.</span><span class="fu">map</span>(e <span class="kw">=></span> e<span class="op">.</span><span class="fu">show</span>()))</span></code></pre>
|
||||
</div>
|
||||
<p>
|
||||
In this example, we create a pattern using the <code>sequence</code> function and <strong>query</strong> it for
|
||||
the time span from <code>0</code> to <code>1</code>. Those numbers represent units of time called
|
||||
<strong>cycles</strong>. The length of one cycle depends on the tempo, which defaults to one cycle per second. The
|
||||
resulting events are:
|
||||
</p>
|
||||
<div class="sourceCode" id="cb2">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>[{ <span class="dt">value</span><span class="op">:</span> <span class="st">'c3'</span><span class="op">,</span> <span class="dt">begin</span><span class="op">:</span> <span class="dv">0</span><span class="op">,</span> <span class="dt">end</span><span class="op">:</span> <span class="dv">1</span><span class="op">/</span><span class="dv">2</span> }<span class="op">,</span></span>
|
||||
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>{ <span class="dt">value</span><span class="op">:</span> <span class="st">'e3'</span><span class="op">,</span> <span class="dt">begin</span><span class="op">:</span> <span class="dv">1</span><span class="op">/</span><span class="dv">2</span><span class="op">,</span> <span class="dt">end</span><span class="op">:</span> <span class="dv">3</span><span class="op">/</span><span class="dv">4</span> }<span class="op">,</span></span>
|
||||
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>{ <span class="dt">value</span><span class="op">:</span> <span class="st">'g3'</span><span class="op">,</span> <span class="dt">begin</span><span class="op">:</span> <span class="dv">3</span><span class="op">/</span><span class="dv">4</span><span class="op">,</span> <span class="dt">end</span><span class="op">:</span> <span class="dv">1</span> }]</span></code></pre>
|
||||
</div>
|
||||
<p>
|
||||
Each event has a value, a begin time and an end time, where time is represented as a fraction. In the above case,
|
||||
the events are placed in sequential order, where c3 takes the first half, and e3 and g3 together take the second
|
||||
half. This temporal placement is the result of the <code>sequence</code> function, which divides its arguments
|
||||
equally over one cycle. If an argument is an array, the same rule applies to that part of the cycle. In the
|
||||
example, e3 and g3 are divided equally over the second half of the whole cycle.
|
||||
</p>
|
||||
<p>
|
||||
The above examples do not represent how Strudel is used in practice. In the live coding editor, the user only has
|
||||
to type in the pattern itself, the querying will be handled by the scheduler. The scheduler will repeatedly query
|
||||
the pattern for events, which are then scheduled as sound synthesis or other event triggers. Also, the above event
|
||||
data structure has been simplified for readability.
|
||||
</p>
|
||||
<figure>
|
||||
<img
|
||||
src="images/strudel-screenshot2.png"
|
||||
style="width: 60%"
|
||||
alt="Screenshot of the Strudel ‘REPL’ live coding editor, including piano-roll visualisation."
|
||||
/>
|
||||
<figcaption aria-hidden="true">
|
||||
Screenshot of the Strudel ‘REPL’ live coding editor, including piano-roll visualisation.
|
||||
</figcaption>
|
||||
</figure>
|
||||
<h1 data-number="4" id="making-patterns"><span class="header-section-number">4</span> Making Patterns</h1>
|
||||
<p>
|
||||
In practice, the end-user live coder will not deal with constructing patterns directly, but will rather build
|
||||
patterns using Strudel’s extensive combinator library to create, combine and transform patterns.
|
||||
</p>
|
||||
<p>
|
||||
The live coder will rarely use the <code>sequence</code> function as seen above, as sequencing is implicit in many
|
||||
functions. For example in the following, the <code>note</code> function constructs a pattern of notes, sequencing
|
||||
its arguments in the same manner as the previous example.
|
||||
</p>
|
||||
<div class="sourceCode" id="cb3">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="fu">note</span>(c3<span class="op">,</span> [e3<span class="op">,</span> g3])</span></code></pre>
|
||||
</div>
|
||||
<p>
|
||||
Perhaps more often, they will use the mini-notation for even terser notation of rhythmic sequences: [^This last
|
||||
example is also valid Tidal code, albeit the parenthesis is not required in its Haskell syntax in this case. Tidal
|
||||
does not support passing sequences as lists directly to the <code>note</code> function, however.].
|
||||
</p>
|
||||
<div class="sourceCode" id="cb4">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="fu">note</span>(<span class="st">"c3 [e3 g3]"</span>)</span></code></pre>
|
||||
</div>
|
||||
<p>
|
||||
Such sequences are often treated only a starting point for manipulation, where they then undergo pattern
|
||||
transformations such as repetition, symmetry, interference/combination or randomisation, potentially at multiple
|
||||
timescales. Because Strudel patterns are represented as pure functions of time rather than as data structures,
|
||||
very long and complex generative results can be represented and manipulated without having to store the resulting
|
||||
sequences in memory.
|
||||
</p>
|
||||
<h1 data-number="5" id="pattern-example"><span class="header-section-number">5</span> Pattern Example</h1>
|
||||
<p>
|
||||
The following example showcases how patterns can be utilized to create musical complexity from simple parts, using
|
||||
repetition and interference:
|
||||
</p>
|
||||
<div class="sourceCode" id="cb5">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="st">"<0 2 [4 6](3,4,1) 3>"</span></span>
|
||||
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="op">.</span><span class="fu">off</span>(<span class="dv">1</span><span class="op">/</span><span class="dv">4</span><span class="op">,</span> <span class="fu">add</span>(<span class="dv">2</span>))</span>
|
||||
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="op">.</span><span class="fu">off</span>(<span class="dv">1</span><span class="op">/</span><span class="dv">2</span><span class="op">,</span> <span class="fu">add</span>(<span class="dv">6</span>))</span>
|
||||
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="op">.</span><span class="fu">scale</span>(<span class="st">'D minor'</span>)</span>
|
||||
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="op">.</span><span class="fu">legato</span>(<span class="op">.</span><span class="dv">25</span>)</span>
|
||||
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="op">.</span><span class="fu">note</span>()<span class="op">.</span><span class="fu">s</span>(<span class="st">"sawtooth square"</span>)</span>
|
||||
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="op">.</span><span class="fu">delay</span>(<span class="op">.</span><span class="dv">8</span>)<span class="op">.</span><span class="fu">delaytime</span>(<span class="op">.</span><span class="dv">125</span>)</span></code></pre>
|
||||
</div>
|
||||
<p>
|
||||
The pattern starts with a rhythm of numbers in mini notation, which are later interpreted inside the scale of D
|
||||
minor. The first line could also be expressed without mini notation:
|
||||
</p>
|
||||
<div class="sourceCode" id="cb6">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="fu">cat</span>(<span class="dv">0</span><span class="op">,</span> <span class="dv">2</span><span class="op">,</span> [<span class="dv">4</span><span class="op">,</span> <span class="dv">6</span>]<span class="op">.</span><span class="fu">euclid</span>(<span class="dv">3</span><span class="op">,</span> <span class="dv">4</span><span class="op">,</span> <span class="dv">1</span>)<span class="op">,</span> <span class="dv">3</span>)</span></code></pre>
|
||||
</div>
|
||||
<p>
|
||||
These numbers then undergo various pattern transformations. Here is a short description of all the functions used:
|
||||
</p>
|
||||
<ul>
|
||||
<li><code>cat</code>: play elements sequentially, where each lasts one cycle</li>
|
||||
<li><code>brackets</code>: elements inside brackets are divided equally over the time of their parent</li>
|
||||
<li>
|
||||
<code>.euclid(p, s, o)</code>: place p pulses evenly over s steps, with offset o
|
||||
<span class="citation" data-cites="toussaintEuclideanAlgorithmGenerates2005">(Toussaint 2005)</span>
|
||||
</li>
|
||||
<li>
|
||||
<code>.off(n, f)</code>: layers a pattern on top of itself, with the new layer offset by n cycles, and with
|
||||
function f applied
|
||||
</li>
|
||||
<li><code>.legato(n)</code>: multiply the duration of all events in a pattern by a factor of n</li>
|
||||
<li>
|
||||
<code>.echo(t, n, v)</code>: copy each event t times, with n cycles in between each copy, decreasing velocity by
|
||||
v
|
||||
</li>
|
||||
<li><code>.note()</code>: interpretes values as notes</li>
|
||||
<li><code>.s(name)</code>: play back each event with the given sound</li>
|
||||
<li><code>.delay(wet)</code>: add delay</li>
|
||||
<li><code>.delaytime(t)</code>: set delay time</li>
|
||||
</ul>
|
||||
<p>Much of the above will be familiar to Tidal users.</p>
|
||||
<!-- This example shows some of Strudel's unique support for chords and transposition familiar to students of Western music theory. This differs a little from Tidal's approach and thanks to the integration of the javascript library XXX (*TODO* ? or is this all your work Felix?), Strudel's support for tonal transformations such as voice leading is perhaps respects more advanced than Tidal. -->
|
||||
<h1 data-number="6" id="ways-to-make-sound-and-other-events">
|
||||
<span class="header-section-number">6</span> Ways to make Sound (and other events)
|
||||
</h1>
|
||||
<p>To generate sound, Strudel supports bindings for different outputs:</p>
|
||||
<ul>
|
||||
<li>Tone.js (deprecated)</li>
|
||||
<li>Web Audio API</li>
|
||||
<li>WebDirt, a js recreation of Tidal’s <em>Dirt</em> sample engine (deprecated)</li>
|
||||
<li>OSC via osc-js, compatible with superdirt</li>
|
||||
<li>Csound via the Csound WebAssembly build</li>
|
||||
<li>MIDI via WebMIDI</li>
|
||||
<li>Serial via WebSerial</li>
|
||||
</ul>
|
||||
<p>
|
||||
At first, we used Tone.js as sound output, but it proved to be limited for the use case of Strudel, where each
|
||||
individual event could potentially have a completely different audio graph. While the Web Audio API takes a
|
||||
<em>fire-and-forget</em> approach, creating a lot of Tone.js instruments and effects causes performance issues
|
||||
quickly. For that reason, we chose to search for alternatives.
|
||||
</p>
|
||||
<p>
|
||||
Strudel’s new default output uses the Web Audio API to create a new audio graph for each event. It currently
|
||||
supports basic oscillators, sample playback, various effects and an experimental support for soundfonts.
|
||||
</p>
|
||||
<p>
|
||||
WebDirt <span class="citation" data-cites="ogbornDktr0WebDirt2022">(Ogborn [2016] 2022)</span> was created as part
|
||||
of the Estuary Live Coding System
|
||||
<span class="citation" data-cites="ogbornEstuaryBrowserbasedCollaborative2017">(Ogborn et al. 2017)</span>, and
|
||||
proved to be a solid choice for handling samples in Strudel as well. We are however focused on working more
|
||||
directly with the Web Audio API to be able to integrate new features more tightly.
|
||||
</p>
|
||||
<p>
|
||||
Using the OSC protocol via Strudel’s provided Node.js-based OSC proxy server, it is possible to send network
|
||||
messages to trigger events. This is mainly used to render sound using SuperDirt
|
||||
<span class="citation" data-cites="SuperDirt2022">(<em>SuperDirt</em> [2015] 2022)</span>, which is the
|
||||
well-developed Supercollider-based synthesis framework that Tidal live coders generally use as standard.
|
||||
</p>
|
||||
<p>
|
||||
Recently, the experimental integration of Csound proved to bring a new dimension of sound design capabilities to
|
||||
Strudel. Thanks to the WebAssembly distribution of this classic system
|
||||
<span class="citation" data-cites="CsoundWebAssembly">(Yi, Lazzarini, and Costello 2018)</span>, Csound
|
||||
‘orchestra’ synthesisers can be embedded in and then patterned with Strudel code.
|
||||
</p>
|
||||
<p>
|
||||
MIDI output can also be used to send MIDI messages to either external instruments or to other programs on the same
|
||||
device. Unlike OSC, Strudel is able to send MIDI directly without requiring additional proxy software, but only
|
||||
from web browsers that support it (at the time of writing, this means Chromium-based browsers).
|
||||
</p>
|
||||
<p>
|
||||
Finally, Strudel supports Serial output, for example to trigger events via microcontrollers. This has already been
|
||||
explored for robot choreography by Kate Sicchio and Alex McLean, via a performance presented at the International
|
||||
Conference on Live Interfaces 2022.
|
||||
</p>
|
||||
<h1 data-number="7" id="the-strudel-repl"><span class="header-section-number">7</span> The Strudel REPL</h1>
|
||||
<p>
|
||||
While Strudel can be used as a library in any JavaScript codebase, its main, reference user interface is the
|
||||
Strudel REPL[^REPL stands for read, evaluate, print/play, loop. It is friendly jargon for an interactive
|
||||
programming interface from computing heritage, usually for a commandline interface but also applied to live coding
|
||||
editors.], which is a browser-based live coding environment. This live code editor is dedicated to manipulating
|
||||
Strudel patterns while they play. The REPL features built-in visual feedback, which highlights which elements in
|
||||
the patterned (mini-notation) sequences are influencing the event that is currently being played. This feedback is
|
||||
designed to support both learning and live use of Strudel.
|
||||
</p>
|
||||
<p>
|
||||
Besides a UI for playback control and meta information, the main part of the REPL interface is the code editor
|
||||
powered by CodeMirror. In it, the user can edit and evaluate pattern code live, using one of the available
|
||||
synthesis outputs to create music and/or sound art. The control flow of the REPL follows 3 basic steps:
|
||||
</p>
|
||||
<ol type="1">
|
||||
<li>
|
||||
The user writes and updates code. Each update transpiles and evaluates it to create a
|
||||
<code>Pattern</code> instance
|
||||
</li>
|
||||
<li>
|
||||
While the REPL is running, the <code>Scheduler</code> queries the active <code>Pattern</code> by a regular
|
||||
interval, generating <code>Events</code> (also known as <code>Haps</code> in Strudel) for the next time span.
|
||||
</li>
|
||||
<li>
|
||||
For each scheduling tick, all generated <code>Events</code> are triggered by calling their
|
||||
<code>onTrigger</code> method, which is set by the output.
|
||||
</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<img
|
||||
src="https://github.com/tidalcycles/strudel/raw/talk/talk/public/strudelflow.png?raw=true"
|
||||
style="width: 43%"
|
||||
alt="REPL control flow"
|
||||
/>
|
||||
<figcaption aria-hidden="true">REPL control flow</figcaption>
|
||||
</figure>
|
||||
<h2 data-number="7.1" id="user-code"><span class="header-section-number">7.1</span> User Code</h2>
|
||||
<p>To create a <code>Pattern</code> from the user code, two steps are needed:</p>
|
||||
<ol type="1">
|
||||
<li>Transpile the JS input code to make it functional</li>
|
||||
<li>Evaluate the transpiled code</li>
|
||||
</ol>
|
||||
<h3 data-number="7.1.1" id="transpilation-evaluation">
|
||||
<span class="header-section-number">7.1.1</span> Transpilation & Evaluation
|
||||
</h3>
|
||||
<p>
|
||||
In the JavaScript world, using transpilation is a common practise to be able to use language features that are not
|
||||
supported by the base language. Tools like <code>babel</code> will transpile code that contains unsupported
|
||||
language features into a version of the code without those features.
|
||||
</p>
|
||||
<p>
|
||||
In the same tradition, Strudel can add a transpilation step to simplify the user code in the context of live
|
||||
coding. For example, the Strudel REPL lets the user create mini notation patterns using just double quoted
|
||||
strings, while single quoted strings remain what they are:
|
||||
</p>
|
||||
<div class="sourceCode" id="cb7">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="st">"c3 [e3 g3]*2"</span></span></code></pre>
|
||||
</div>
|
||||
<p>is transpiled to:</p>
|
||||
<div class="sourceCode" id="cb8">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="fu">mini</span>(<span class="st">"c3 [e3 g3]*2"</span>)<span class="op">.</span><span class="fu">withMiniLocation</span>([<span class="dv">1</span><span class="op">,</span><span class="dv">0</span><span class="op">,</span><span class="dv">0</span>]<span class="op">,</span>[<span class="dv">1</span><span class="op">,</span><span class="dv">14</span><span class="op">,</span><span class="dv">14</span>])</span></code></pre>
|
||||
</div>
|
||||
<p>
|
||||
Here, the string is wrapped in <code>mini</code>, which will create a pattern from a mini notation string.
|
||||
Additionally, the <code>withMiniLocation</code> method passes the original source code location of the string to
|
||||
the pattern, which enables highlighting active events.
|
||||
</p>
|
||||
<p>
|
||||
Other convenient features like pseudo variables, operator overloading and top level await are possible with
|
||||
transpilation.
|
||||
</p>
|
||||
<p>After the transpilation, the code is ready to be evaluated into a <code>Pattern</code>.</p>
|
||||
<p>
|
||||
Behind the scenes, the user code string is parsed with <code>acorn</code>, turning it into an Abstract Syntax Tree
|
||||
(AST). The AST allows changing the structure of the code before generating the transpiled version using
|
||||
<code>escodegen</code>.
|
||||
</p>
|
||||
<h3 data-number="7.1.2" id="mini-notation"><span class="header-section-number">7.1.2</span> Mini Notation</h3>
|
||||
<p>
|
||||
While the transpilation allows JavaScript to express Patterns in a less verbose way, it is still preferable to use
|
||||
the Mini Notation as a more compact way to express rhythm. Strudel aims to provide the same Mini Notation features
|
||||
and syntax as used in Tidal.
|
||||
</p>
|
||||
<p>
|
||||
The Mini Notation parser is implemented using <code>peggy</code>, which allows generating performant parsers for
|
||||
Domain Specific Languages (DSLs) using a concise grammar notation. The generated parser turns the Mini Notation
|
||||
string into an AST which is used to call the respective Strudel functions with the given structure. For example,
|
||||
<code>"c3 [e3 g3]*2"</code> will result in the following calls:
|
||||
</p>
|
||||
<div class="sourceCode" id="cb9">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="fu">seq</span>(</span>
|
||||
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a> <span class="fu">reify</span>(<span class="st">'c3'</span>)<span class="op">.</span><span class="fu">withLocation</span>([<span class="dv">1</span><span class="op">,</span><span class="dv">1</span><span class="op">,</span><span class="dv">1</span>]<span class="op">,</span> [<span class="dv">1</span><span class="op">,</span><span class="dv">4</span><span class="op">,</span><span class="dv">4</span>])<span class="op">,</span></span>
|
||||
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a> <span class="fu">seq</span>(</span>
|
||||
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a> <span class="fu">reify</span>(<span class="st">'e3'</span>)<span class="op">.</span><span class="fu">withLocation</span>([<span class="dv">1</span><span class="op">,</span><span class="dv">5</span><span class="op">,</span><span class="dv">5</span>]<span class="op">,</span> [<span class="dv">1</span><span class="op">,</span><span class="dv">8</span><span class="op">,</span><span class="dv">8</span>])<span class="op">,</span></span>
|
||||
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a> <span class="fu">reify</span>(<span class="st">'g3'</span>)<span class="op">.</span><span class="fu">withLocation</span>([<span class="dv">1</span><span class="op">,</span><span class="dv">8</span><span class="op">,</span><span class="dv">8</span>]<span class="op">,</span> [<span class="dv">1</span><span class="op">,</span><span class="dv">10</span><span class="op">,</span><span class="dv">10</span>])<span class="op">,</span></span>
|
||||
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a> )<span class="op">.</span><span class="fu">fast</span>(<span class="dv">2</span>)</span>
|
||||
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a>)</span></code></pre>
|
||||
</div>
|
||||
<h3 data-number="7.1.3" id="highlighting-locations">
|
||||
<span class="header-section-number">7.1.3</span> Highlighting Locations
|
||||
</h3>
|
||||
<p>
|
||||
As seen in the examples above, both the JS and the Mini Notation parser add source code locations using
|
||||
<code>withMiniLocation</code> and <code>withLocation</code> methods. While the JS parser adds locations relative
|
||||
to the user code as a whole, the Mini Notation adds locations relative to the position of the mini notation
|
||||
string. The absolute location of elements within Mini Notation can be calculated by simply adding both locations
|
||||
together. This absolute location can be used to highlight active events in real time.
|
||||
</p>
|
||||
<h2 data-number="7.2" id="scheduling-events"><span class="header-section-number">7.2</span> Scheduling Events</h2>
|
||||
<p>
|
||||
After an instance of <code>Pattern</code> is obtained from the user code, it is used by the scheduler to get
|
||||
queried for events. Once started, the scheduler runs at a fixed interval to query active pattern for events
|
||||
withing the current interval’s time span. A simplified implementation looks like this:
|
||||
</p>
|
||||
<div class="sourceCode" id="cb10">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> pattern <span class="op">=</span> <span class="fu">seq</span>(<span class="st">'c3'</span><span class="op">,</span> [<span class="st">'e3'</span><span class="op">,</span> <span class="st">'g3'</span>])<span class="op">;</span> <span class="co">// pattern from user</span></span>
|
||||
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> interval <span class="op">=</span> <span class="fl">0.5</span><span class="op">;</span> <span class="co">// query interval in seconds</span></span>
|
||||
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> time <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> <span class="co">// beginning of current time span</span></span>
|
||||
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> minLatency <span class="op">=</span> <span class="op">.</span><span class="dv">1</span><span class="op">;</span> <span class="co">// min time before a hap should trigger</span></span>
|
||||
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a><span class="pp">setInterval</span>(() <span class="kw">=></span> {</span>
|
||||
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> haps <span class="op">=</span> pattern<span class="op">.</span><span class="fu">queryArc</span>(time<span class="op">,</span> time <span class="op">+</span> interval)<span class="op">;</span></span>
|
||||
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a> time <span class="op">+=</span> interval<span class="op">;</span> <span class="co">// increment time</span></span>
|
||||
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a> haps<span class="op">.</span><span class="fu">forEach</span>((hap) <span class="kw">=></span> {</span>
|
||||
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> deadline <span class="op">=</span> hap<span class="op">.</span><span class="at">whole</span><span class="op">.</span><span class="at">begin</span> <span class="op">-</span> time <span class="op">+</span> minLatency<span class="op">;</span></span>
|
||||
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a> <span class="fu">onTrigger</span>(hap<span class="op">,</span> deadline<span class="op">,</span> duration)<span class="op">;</span></span>
|
||||
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a> })<span class="op">;</span></span>
|
||||
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a>}<span class="op">,</span> interval <span class="op">*</span> <span class="dv">1000</span>)<span class="op">;</span> <span class="co">// query each "interval" seconds</span></span></code></pre>
|
||||
</div>
|
||||
<p>
|
||||
Note that the above code is simplified for illustrative purposes. The actual implementation has to work around
|
||||
imprecise callbacks of <code>setInterval</code>. More about the implementation details can be read in
|
||||
<a href="https://loophole-letters.vercel.app/web-audio-scheduling">this blog post</a>.
|
||||
</p>
|
||||
<p>
|
||||
The fact that <code>Pattern.queryArc</code> is a pure function that maps a time span to a set of events allows us
|
||||
to choose any interval we like without changing the resulting output. It also means that when the pattern is
|
||||
changed from outside, the next scheduling callback will work with the new pattern, keeping its clock running.
|
||||
</p>
|
||||
<p>
|
||||
The latency between the time the pattern is evaluated and the change is heard is between
|
||||
<code>minLatency</code> and <code>interval + minLatency</code>, in our example between 100ms and 600ms. In
|
||||
Strudel, the current query interval is 50ms with a minLatency of 100ms, meaning the latency is between 50ms and
|
||||
150ms.
|
||||
</p>
|
||||
<h2 data-number="7.3" id="output"><span class="header-section-number">7.3</span> Output</h2>
|
||||
<p>
|
||||
The last step is to trigger each event in the chosen output. This is where the given time and value of each event
|
||||
is used to generate audio or any other form of time based output. The default output of the Strudel REPL is the
|
||||
WebAudio output. To understand what an output does, we first have to understand what control parameters are.
|
||||
</p>
|
||||
<h3 data-number="7.3.1" id="control-parameters">
|
||||
<span class="header-section-number">7.3.1</span> Control Parameters
|
||||
</h3>
|
||||
<p>
|
||||
To be able to manipulate multiple aspects of sound in parallel, so called control parameters are used to shape the
|
||||
value of each event. Example:
|
||||
</p>
|
||||
<div class="sourceCode" id="cb11">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="fu">note</span>(<span class="st">"c3 e3"</span>)<span class="op">.</span><span class="fu">cutoff</span>(<span class="dv">1000</span>)<span class="op">.</span><span class="fu">s</span>(<span class="st">'sawtooth'</span>)</span>
|
||||
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="fu">queryArc</span>(<span class="dv">0</span><span class="op">,</span> <span class="dv">1</span>)<span class="op">.</span><span class="fu">map</span>(hap <span class="kw">=></span> hap<span class="op">.</span><span class="at">value</span>)</span>
|
||||
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a><span class="co">/* [</span></span>
|
||||
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a><span class="co"> { note: 'c3', cutoff: 1000, s: 'sawtooth' }</span></span>
|
||||
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a><span class="co"> { note: 'e3', cutoff: 1000, s: 'sawtooth' }</span></span>
|
||||
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a><span class="co">] */</span></span></code></pre>
|
||||
</div>
|
||||
<p>
|
||||
Here, the control parameter functions <code>note</code>, <code>cutoff</code> and <code>s</code> are used, where
|
||||
each controls a different property in the value object. Each control parameter function accepts a primitive value,
|
||||
a list of values to be sequenced into a <code>Pattern</code>, or a <code>Pattern</code>. In the example,
|
||||
<code>note</code> gets a <code>Pattern</code> from a Mini Notation expression (double quoted), while
|
||||
<code>cutoff</code> and <code>s</code> are given a <code>Number</code> and a (single quoted)
|
||||
<code>String</code> respectively.
|
||||
</p>
|
||||
<p>
|
||||
Strudel comes with a large default set of control parameter functions that are based on the ones used by Tidal and
|
||||
SuperDirt, focusing on music and audio terminology. It is however possible to create custom control paramters for
|
||||
any purpose:
|
||||
</p>
|
||||
<div class="sourceCode" id="cb12">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> { x<span class="op">,</span> y } <span class="op">=</span> <span class="fu">createParams</span>(<span class="st">'x'</span><span class="op">,</span> <span class="st">'y'</span>)</span>
|
||||
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a><span class="fu">x</span>(sine<span class="op">.</span><span class="fu">range</span>(<span class="dv">0</span><span class="op">,</span> <span class="dv">200</span>))<span class="op">.</span><span class="fu">y</span>(cosine<span class="op">.</span><span class="fu">range</span>(<span class="dv">0</span><span class="op">,</span><span class="dv">200</span>))</span></code></pre>
|
||||
</div>
|
||||
<p>
|
||||
This example creates the custom control parameters <code>x</code> and <code>y</code> which are then used to form a
|
||||
pattern that descibes the coordinates of a circle.
|
||||
</p>
|
||||
<h3 data-number="7.3.2" id="outputs"><span class="header-section-number">7.3.2</span> Outputs</h3>
|
||||
<p>
|
||||
Now that we know how the value of an event is manipulated using control parameters, we can look at how outputs can
|
||||
use that value to generate anything. The scheduler above was calling the <code>onTrigger</code> function which is
|
||||
used to implement the output. A very simple version of the web audio output could look like this:
|
||||
</p>
|
||||
<div class="sourceCode" id="cb13">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> <span class="fu">onTrigger</span>(hap<span class="op">,</span> deadline<span class="op">,</span> duration) {</span>
|
||||
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> { note } <span class="op">=</span> hap<span class="op">.</span><span class="at">value</span><span class="op">;</span></span>
|
||||
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> time <span class="op">=</span> <span class="fu">getAudioContext</span>()<span class="op">.</span><span class="at">currentTime</span> <span class="op">+</span> deadline<span class="op">;</span></span>
|
||||
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> o <span class="op">=</span> <span class="fu">getAudioContext</span>()<span class="op">.</span><span class="fu">createOscillator</span>()<span class="op">;</span></span>
|
||||
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a> o<span class="op">.</span><span class="at">frequency</span><span class="op">.</span><span class="at">value</span> <span class="op">=</span> <span class="fu">getFreq</span>(note)<span class="op">;</span></span>
|
||||
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a> o<span class="op">.</span><span class="fu">start</span>(time)<span class="op">;</span></span>
|
||||
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a> o<span class="op">.</span><span class="fu">stop</span>(time <span class="op">+</span> <span class="bu">event</span><span class="op">.</span><span class="at">duration</span>)<span class="op">;</span></span>
|
||||
<span id="cb13-8"><a href="#cb13-8" aria-hidden="true" tabindex="-1"></a> o<span class="op">.</span><span class="fu">connect</span>(<span class="fu">getAudioContext</span>()<span class="op">.</span><span class="at">destination</span>)<span class="op">;</span></span>
|
||||
<span id="cb13-9"><a href="#cb13-9" aria-hidden="true" tabindex="-1"></a>}</span></code></pre>
|
||||
</div>
|
||||
<p>
|
||||
The above example will create an <code>OscillatorNode</code> for each event, where the frequency is controlled by
|
||||
the <code>note</code> param. In essence, this is how the WebAudio API output of Strudel works, only with many more
|
||||
parameters to control synths, samples and effects.
|
||||
</p>
|
||||
<h1 data-number="8" id="pattern-alignment-and-combination">
|
||||
<span class="header-section-number">8</span> Pattern alignment and combination
|
||||
</h1>
|
||||
<p>
|
||||
One core aspect of Strudel, inherited from Tidal, is the flexible way that patterns can be combined, irrespective
|
||||
of their structure. Its declarative approach means a live coder does not have to think about the details of
|
||||
<em>how</em> this is done, only <em>what</em> is to be done.
|
||||
</p>
|
||||
<p>
|
||||
As a simple example, consider two number patterns <code>"0 [1 2] 3"</code>, and <code>"10 20"</code>. The first
|
||||
has three contiguous steps of equal lengths, with the second step broken down into two substeps, giving four
|
||||
events in total. There are a very large number of ways in which the structure of these two patterns could be
|
||||
combined, but the default method in both Strudel and Tidal is to line up the cycles of the two patterns, and then
|
||||
take events from the first pattern and match them with those in the second pattern. Therefore, the following two
|
||||
lines are equivalent:
|
||||
</p>
|
||||
<div class="sourceCode" id="cb14">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="st">"0 [1 2] 3"</span><span class="op">.</span><span class="fu">add</span>(<span class="st">"10 20"</span>)</span>
|
||||
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a><span class="st">"10 [11 22] 23"</span></span></code></pre>
|
||||
</div>
|
||||
<p>
|
||||
Where the events only partially overlap, they are treated as fragments of the event in the first pattern. This is
|
||||
a little difficult to conceptualise, but lets start by comparing the two patterns in the following example:
|
||||
</p>
|
||||
<div class="sourceCode" id="cb15">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="st">"0 1 2"</span><span class="op">.</span><span class="fu">add</span>(<span class="st">"10 20"</span>)</span>
|
||||
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a><span class="st">"10 [11 21] 20"</span></span></code></pre>
|
||||
</div>
|
||||
<p>
|
||||
They are similar to the previous example in that the number <code>1</code> is split in two, with its two halves
|
||||
added to <code>10</code> and <code>20</code> respectively. However, the <code>11</code> ‘remembers’ that it is a
|
||||
fragment of that original <code>1</code> event, and so is treated as having a duration of a third of a cycle,
|
||||
despite only being active for a sixth of a cycle. Likewise, the <code>21</code> is also a fragment of that
|
||||
original <code>1</code> event, but a fragment of its second half. Because the start of its event is missing, it
|
||||
wouldn’t actually trigger a sound (unless it underwent further pattern transformations/combinations).
|
||||
</p>
|
||||
<p>
|
||||
In practice, the effect of this default, implicit method for combining two patterns is that the second pattern is
|
||||
added <em>in</em> to the first one, and indeed this can be made explicit:
|
||||
</p>
|
||||
<div class="sourceCode" id="cb16">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="st">"0 1 2"</span><span class="op">.</span><span class="at">add</span><span class="op">.</span><span class="fu">in</span>(<span class="st">"10 20"</span>)</span></code></pre>
|
||||
</div>
|
||||
<p>This makes way for other ways to align the pattern, and several are already defined, in particular:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<code>in</code> - as explained above, aligns cycles, and applies values from the pattern on the right
|
||||
<em>in</em> to the pattern on the left.
|
||||
</li>
|
||||
<li>
|
||||
<code>out</code> - as with <code>in</code>, but values are applied <em>out</em> of the pattern on the left
|
||||
(i.e. <em>in</em> to the one on the right).
|
||||
</li>
|
||||
<li>
|
||||
<code>mix</code> - structures from both patterns are combined, so that the new events are not fragments but are
|
||||
created at intersections of events from both sides.
|
||||
</li>
|
||||
<li>
|
||||
<code>squeeze</code> - cycles from the pattern on the right are squeezed into events on the left. So that
|
||||
e.g. <code>"0 1 2".add.squeeze("10 20")</code> is equivalent to <code>"[10 20] [11 21] [12 22]"</code>.
|
||||
</li>
|
||||
<li>
|
||||
<code>squeezeout</code> - as with <code>squeeze</code>, but cycles from the left are squeezed into events on the
|
||||
right. So, <code>"0 1 2".add.squeezeout("10 20")</code> is equivalent to <code>[10 11 12] [20 21 22]</code>.
|
||||
</li>
|
||||
<li>
|
||||
<code>trig</code> is similar to <code>squeezeout</code> in that cycles from the right are aligned with events on
|
||||
the left. However those cycles are not ‘squeezed’, rather they are truncated to fit the event. So
|
||||
<code>"0 1 2 3 4 5 6 7".add.trig("10 [20 30]")</code> would be equivalent to
|
||||
<code>10 11 12 13 20 21 30 31</code>. In effect, events on the right ‘trigger’ cycles on the left.
|
||||
</li>
|
||||
<li>
|
||||
<code>trigzero</code> is similar to <code>trig</code>, but the pattern is ‘triggered’ from its very first cycle,
|
||||
rather than from the current cycle. <code>trig</code> and <code>trigzero</code> therefore only give different
|
||||
results where the leftmost pattern differs from one cycle to the next.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
We will save going deeper into the background, design and practicalities of these alignment functions for future
|
||||
publications. However in the next section, we take them as a case study for looking at the different design
|
||||
affordances offered by Haskell to Tidal, and JavaScript to Strudel.
|
||||
</p>
|
||||
<h1 data-number="9" id="comparing-strudel-and-haskell-in-use">
|
||||
<span class="header-section-number">9</span> Comparing Strudel and Haskell in use
|
||||
</h1>
|
||||
<p>
|
||||
Unlike Haskell, JavaScript lacks the ability to define custom infix operators, or change the meaning of existing
|
||||
ones. So the above Strudel example of <code>"0 1 2".add.out("10 20")</code> is equivalent to the Tidal expression
|
||||
<code>"0 1 2" +| "10 20"</code>, where the vertical bar in the operator <code>+|</code> stands for
|
||||
<code>out</code> (where <code>a |+ b</code> would be equivalent of <code>a.add.in(b)</code>).
|
||||
</p>
|
||||
<p>
|
||||
From this we can already see that Tidal tends towards brevity through mixing infix operators with functions, and
|
||||
Strudel tends towards spelling out operations which are joined together with the <code>.</code> operator. This
|
||||
then is the design trade-off of Tidal’s tersity, versus Strudel’s simplicity.
|
||||
</p>
|
||||
<p>To demonstrate this, consider the following Tidal pattern:</p>
|
||||
<pre
|
||||
class="tidal"
|
||||
><code>iter 4 $ every 3 (||+ n "10 20") $ (n "0 1 3") # s "triangle" # crush 4</code></pre>
|
||||
<p>This can be directly translated to the Strudel equivalent:</p>
|
||||
<div class="sourceCode" id="cb18">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="fu">iter</span>(<span class="dv">4</span><span class="op">,</span> <span class="fu">every</span>(<span class="dv">3</span><span class="op">,</span> add<span class="op">.</span><span class="fu">squeeze</span>(<span class="st">"10 20"</span>)<span class="op">,</span> <span class="fu">n</span>(<span class="st">"0 1 3"</span>)<span class="op">.</span><span class="fu">s</span>(<span class="st">"triangle"</span>)<span class="op">.</span><span class="fu">crush</span>(<span class="dv">4</span>)))</span></code></pre>
|
||||
</div>
|
||||
<p>Although for a more canonical Strudel expression, we would reorder it as:</p>
|
||||
<div class="sourceCode" id="cb19">
|
||||
<pre
|
||||
class="sourceCode js"
|
||||
><code class="sourceCode javascript"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="fu">n</span>(<span class="st">"0 1 3"</span>)<span class="op">.</span><span class="fu">every</span>(<span class="dv">3</span><span class="op">,</span> add<span class="op">.</span><span class="fu">squeeze</span>(<span class="st">"10 20"</span>))<span class="op">.</span><span class="fu">iter</span>(<span class="dv">4</span>)<span class="op">.</span><span class="fu">s</span>(<span class="st">"triangle"</span>)<span class="op">.</span><span class="fu">crush</span>(<span class="dv">4</span>)</span></code></pre>
|
||||
</div>
|
||||
<p>
|
||||
The Strudel example uses the <code>.</code> method call operator for all operations and combinations, whereas the
|
||||
Tidal example has <code>#</code> for the default method for combining patterns and uses infix operators for other
|
||||
methods. The lack of parenthesis in the Tidal example is partly due to the way that arguments are applied to
|
||||
Haskell’s functions, and partly due to the use of the <code>$</code> operator as an alternative way to establish
|
||||
precedence and control the order of evaluation.
|
||||
</p>
|
||||
<p>
|
||||
Considering the above, we argue that the Haskell syntax is a little cleaner, but that the Strudel syntax is easier
|
||||
to learn. Our informal observation is that while Haskell’s dollar <code>$</code> operator is very useful in making
|
||||
code easier to work with, it is one of the most difficult aspects of Tidal use for beginners to learn. On the
|
||||
other hand, the deeper levels of parenthesis in Strudel code can be difficult to keep track of, especially while
|
||||
coding under pressure of live musical performance. However this difficulty can be largely be mitigated by
|
||||
reordering expressions, and further mitigated by supporting editor features.
|
||||
</p>
|
||||
<p>
|
||||
With Strudel, we have little choice but to embrace the affordances and constraints offered by JavaScript, and
|
||||
while designing a domain-specific language entirely based on method calls is a challenge, through creative
|
||||
adoption of functional programming techniques like partial application, we are so far very happy with the results.
|
||||
Tidal’s functional reactive approach to pattern-making has in general translated well to JavaScript, and
|
||||
opportunities and constraints have overall traded off to create a very approachable and useable live coding
|
||||
environment.
|
||||
</p>
|
||||
<h2 data-number="9.1" id="the-trade-off-of-flexible-typing">
|
||||
<span class="header-section-number">9.1</span> The trade-off of flexible typing
|
||||
</h2>
|
||||
<p>
|
||||
We have identified one problem with porting Tidal to JavaScript where we have missed Haskell’s strict typing and
|
||||
type inference. In both Tidal and Strudel, time is rational, where any point in time is represented as the ratio
|
||||
of two integers. This allows representation of musical ratios such that are impossible to represent accurately
|
||||
using the more common floating point numbers. However while libraries are available that support rational numbers
|
||||
in JavaScript, the lack of strict typing means that it is easy to implement pattern methods where computationally
|
||||
expensive conversion from floating point to rational numbers are performed late, and therefore often enough to
|
||||
overload the CPUs, due to the large number of iterative calculations required to estimate a ratio for a given
|
||||
floating point number. To mitigate this problem, we might consider moving to TypeScript in the future.
|
||||
</p>
|
||||
<h1 data-number="10" id="future-outlook"><span class="header-section-number">10</span> Future Outlook</h1>
|
||||
<p>
|
||||
The project is still young, with many features on the horizon. As general guiding principles, Strudel aims to be
|
||||
</p>
|
||||
<ol type="1">
|
||||
<li>accessible</li>
|
||||
<li>consistent with Tidal’s approach to pattern</li>
|
||||
<li>modular and extensible</li>
|
||||
</ol>
|
||||
<p>
|
||||
While Haskell’s type system makes it a great language for the ongoing development of Tidal’s inner representation
|
||||
of pattern, JavaScript’s vibrant ecosystem, flexibility and accessibility makes it a great host for more ad-hoc
|
||||
experiments, including interface design. For the future, it is planned to integrate additional alternative sound
|
||||
engines such as Glicol <span class="citation" data-cites="lanChaosprintGlicol2022">(Lan [2020] 2022)</span> and
|
||||
Faust
|
||||
<span class="citation" data-cites="FaustProgrammingLanguage2022"
|
||||
>(<em>Faust - Programming Language for Audio Applications and Plugins</em> [2016] 2022)</span
|
||||
>. Strudel is already approaching feature parity with Tidal, but there are more Tidal functions to be ported, and
|
||||
work to be done to improve compatibility with Tidal’s mininotation. Tidal version 2.0 is under development, which
|
||||
brings a new representation for sequences to its patterns, which will then be brought to Strudel. Besides sound,
|
||||
other ways to render events are being explored, such as graphical, and choreographic output. We are also looking
|
||||
into alternative ways of editing patterns, including multi-user editing for network music, parsing a novel syntax
|
||||
to escape the constraints of javascript, and developing hardware/e-textile interfaces. In summary, there is a lot
|
||||
of fun ahead.
|
||||
</p>
|
||||
<h1 data-number="11" id="links"><span class="header-section-number">11</span> Links</h1>
|
||||
<p>
|
||||
The Strudel REPL is available at <a href="https://strudel.cc" class="uri">https://strudel.cc</a>, including an
|
||||
interactive tutorial. The repository is at
|
||||
<a href="https://github.com/tidalcycles/strudel" class="uri">https://github.com/tidalcycles/strudel</a>, all the
|
||||
code is open source under the AGPL-3.0 License.
|
||||
</p>
|
||||
<h1 data-number="12" id="acknowledgments"><span class="header-section-number">12</span> Acknowledgments</h1>
|
||||
<p>
|
||||
Thanks to the Strudel and wider Tidal, live coding, WebAudio and free/open source software communities for
|
||||
inspiration and support. Alex McLean’s work on this project is supported by a UKRI Future Leaders Fellowship
|
||||
[grant number MR/V025260/1].
|
||||
</p>
|
||||
<h1 class="unnumbered" id="references">References</h1>
|
||||
<div id="refs" class="references csl-bib-body hanging-indent" role="doc-bibliography">
|
||||
<div id="ref-FaustProgrammingLanguage2022" class="csl-entry" role="doc-biblioentry">
|
||||
<em>Faust - Programming Language for Audio Applications and Plugins</em>. (2016) 2022. C++. GRAME.
|
||||
<a href="https://github.com/grame-cncm/faust">https://github.com/grame-cncm/faust</a>.
|
||||
</div>
|
||||
<div id="ref-jackHydra2022" class="csl-entry" role="doc-biblioentry">
|
||||
Jack, Olivia. (2022) 2022. <em>Hydra</em>.
|
||||
<a href="https://github.com/ojack/hydra">https://github.com/ojack/hydra</a>.
|
||||
</div>
|
||||
<div id="ref-lanChaosprintGlicol2022" class="csl-entry" role="doc-biblioentry">
|
||||
Lan, Qichao. (2020) 2022. <em>Chaosprint/Glicol</em>. Rust.
|
||||
<a href="https://github.com/chaosprint/glicol">https://github.com/chaosprint/glicol</a>.
|
||||
</div>
|
||||
<div id="ref-mcleanAlgorithmicPattern2020a" class="csl-entry" role="doc-biblioentry">
|
||||
Mclean, Alex. 2020. <span>“Algorithmic Pattern.”</span> In
|
||||
<em>Proceedings of the International Conference on New Interfaces for Musical Expression</em>, 265--270.
|
||||
Birmingham, UK. <a href="https://zenodo.org/record/4813352">https://zenodo.org/record/4813352</a>.
|
||||
</div>
|
||||
<div id="ref-mcleanFeedforward2020" class="csl-entry" role="doc-biblioentry">
|
||||
McLean, Alex. 2020. <span>“Feedforward.”</span> In
|
||||
<em>Proceedings of New Interfaces for Musical Expression</em>. Birmingham.
|
||||
<a href="https://zenodo.org/record/6353969">https://zenodo.org/record/6353969</a>.
|
||||
</div>
|
||||
<div id="ref-mcleanTidalVortexZero2022" class="csl-entry" role="doc-biblioentry">
|
||||
McLean, Alex, Raphaël Forment, Sylvain Le Beux, and Damián Silvani. 2022. <span>“TidalVortex Zero.”</span> In
|
||||
<em>Proceedings of the 7th International Conference on Live Coding</em>. Limerick, Ireland: Zenodo.
|
||||
<a href="https://doi.org/10.5281/zenodo.6456380">https://doi.org/10.5281/zenodo.6456380</a>.
|
||||
</div>
|
||||
<div id="ref-ogbornDktr0WebDirt2022" class="csl-entry" role="doc-biblioentry">
|
||||
Ogborn, David. (2016) 2022. <em>Dktr0/WebDirt</em>. JavaScript.
|
||||
<a href="https://github.com/dktr0/WebDirt">https://github.com/dktr0/WebDirt</a>.
|
||||
</div>
|
||||
<div id="ref-ogbornEstuaryBrowserbasedCollaborative2017" class="csl-entry" role="doc-biblioentry">
|
||||
Ogborn, David, Jamie Beverley, Luis Navarro del Angel, Eldad Tsabary, and Alex McLean. 2017.
|
||||
<span>“Estuary: Browser-Based Collaborative Projectional Live Coding of Musical Patterns.”</span> In
|
||||
<em>Proceedings of the International Conference on Live Coding</em>, 11. Morelia.
|
||||
</div>
|
||||
<div id="ref-robertsGibberLiveCoding2012" class="csl-entry" role="doc-biblioentry">
|
||||
Roberts, Charles, and Joann Kuchera-morin. 2012. <span>“Gibber: Live Coding Audio in the Browser.”</span> In
|
||||
<em>In Proceedings of the 2012 International Computer Music Conference</em>.
|
||||
</div>
|
||||
<div id="ref-StrudelWAC2022" class="csl-entry" role="doc-biblioentry">
|
||||
Roos, Felix, and Alex McLean. 2022. <span>“Strudel: Algorithmic Patterns for the Web.”</span> In. Zenodo.
|
||||
<a href="https://doi.org/10.5281/zenodo.6768844">https://doi.org/10.5281/zenodo.6768844</a>.
|
||||
</div>
|
||||
<div id="ref-solomonPurescriptocarina2022" class="csl-entry" role="doc-biblioentry">
|
||||
Solomon, Mike. (2021) 2022. <em>Purescript-Ocarina</em>. PureScript.
|
||||
<a href="https://github.com/mikesol/purescript-ocarina">https://github.com/mikesol/purescript-ocarina</a>.
|
||||
</div>
|
||||
<div id="ref-SuperDirt2022" class="csl-entry" role="doc-biblioentry">
|
||||
<em>SuperDirt</em>. (2015) 2022. SuperCollider. musikinformatik.
|
||||
<a href="https://github.com/musikinformatik/SuperDirt">https://github.com/musikinformatik/SuperDirt</a>.
|
||||
</div>
|
||||
<div id="ref-toussaintEuclideanAlgorithmGenerates2005" class="csl-entry" role="doc-biblioentry">
|
||||
Toussaint, Godfried. 2005. <span>“The Euclidean Algorithm Generates Traditional Musical Rhythms.”</span> In
|
||||
<em>In Proceedings of BRIDGES: Mathematical Connections in Art, Music and Science</em>, 47–56.
|
||||
<a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.62.231"
|
||||
>http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.62.231</a
|
||||
>.
|
||||
</div>
|
||||
<div id="ref-CsoundWebAssembly" class="csl-entry" role="doc-biblioentry">
|
||||
Yi, Steven, Victor Lazzarini, and Edward Costello. 2018.
|
||||
<span>“WebAssembly AudioWorklet Csound.”</span> In. Berlin, Germany.
|
||||
<a href="https://mural.maynoothuniversity.ie/16018/">https://mural.maynoothuniversity.ie/16018/</a>.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,63 +0,0 @@
|
||||
$if(false)$ This is a pandoc template and should not be edited. $endif$
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" $if(lang)$ lang="$lang$" xml:lang="$lang$" $endif$>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="Content-Style-Type" content="text/css" />
|
||||
<meta name="generator" content="pandoc" />
|
||||
$for(author-meta)$
|
||||
<meta name="author" content="$author-meta$" />
|
||||
$endfor$ $if(date-meta)$
|
||||
<meta name="date" content="$date-meta$" />
|
||||
$endif$
|
||||
<title>$if(title-prefix)$$title-prefix$ - $endif$$pagetitle$</title>
|
||||
<style type="text/css">
|
||||
code {
|
||||
white-space: pre;
|
||||
}
|
||||
</style>
|
||||
$if(quotes)$
|
||||
<style type="text/css">
|
||||
q {
|
||||
quotes: '“' '”' '‘' '’';
|
||||
}
|
||||
</style>
|
||||
$endif$ $if(highlighting-css)$
|
||||
<style type="text/css">
|
||||
$highlighting-css$
|
||||
</style>
|
||||
$endif$
|
||||
<link rel="stylesheet" href="css/iclc.css" $if(html5)$$else$type="text/css" $endif$ />
|
||||
$for(css)$
|
||||
<link rel="stylesheet" href="$css$" $if(html5)$$else$type="text/css" $endif$ />
|
||||
$endfor$ $if(math)$ $math$ $endif$ $for(header-includes)$ $header-includes$ $endfor$
|
||||
</head>
|
||||
<body>
|
||||
$for(include-before)$ $include-before$ $endfor$ $if(title)$
|
||||
<div id="$idprefix$header">
|
||||
<h1 class="title">$title$</h1>
|
||||
$if(subtitle)$
|
||||
<h1 class="subtitle">$subtitle$</h1>
|
||||
$endif$
|
||||
<ul id="authorlist">
|
||||
$for(author)$
|
||||
<li>$author$</li>
|
||||
$endfor$
|
||||
</ul>
|
||||
$if(date)$
|
||||
<h3 class="date">$date$</h3>
|
||||
$endif$
|
||||
</div>
|
||||
$endif$ $if(toc)$
|
||||
<div id="$idprefix$TOC">$toc$</div>
|
||||
$endif$
|
||||
|
||||
<h2 class="abstract">Abstract</h2>
|
||||
<div id="abstract">
|
||||
$if(abstract)$ $abstract$ $else$ Please provide an abstract in the metadata block at the top of your markdown
|
||||
document. Refer to template.txt for details. $endif$
|
||||
</div>
|
||||
|
||||
$body$ $for(include-after)$ $include-after$ $endfor$
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,197 +0,0 @@
|
||||
This document introduces you to Strudel in a technical sense.
|
||||
|
||||
It is rather out of date, but there might still be useful info below.
|
||||
|
||||
If you just want to *use* Strudel, have a look at the [Tutorial](https://strudel.tidalcycles.org/tutorial/).
|
||||
|
||||
## Strudel Packages
|
||||
|
||||
There are different packages for different purposes. They..
|
||||
|
||||
- split up the code into smaller chunks
|
||||
- can be selectively used to implement some sort of time based system
|
||||
|
||||
Please refer to the individual README files in the [packages folder](https://codeberg.org/uzu/strudel/src/branch/main/packages)
|
||||
|
||||
## REPL
|
||||
|
||||
The [REPL](https://strudel.tidalcycles.org/) is the place where all packages come together to form a live coding system. It can also be seen as a reference implementation for users of the library.
|
||||
|
||||
More info in the [REPL README](https://codeberg.org/uzu/strudel/src/branch/main/packages/repl/README.md)
|
||||
|
||||
# High Level Overview
|
||||
|
||||
<img src="strudelflow.png" width="600" />
|
||||
|
||||
## 1. End User Code
|
||||
|
||||
The End User Code is written in JavaScript with added syntax sugar. The [eval package](https://github.com/tidalcycles/strudel/tree/main/packages/eval#strudelcycleseval) evaluates the user code
|
||||
after a transpilation step, which resolves the syntax sugar. If you don't want the syntax sugar, you can omit the eval package and call the native javascript `eval` instead.
|
||||
|
||||
### 🍭 Syntax Sugar
|
||||
|
||||
JavaScript Transpilation = converting valid JavaScript to valid JavaScript:
|
||||
|
||||
```js
|
||||
"c3 [e3 g3]".fast(2)
|
||||
```
|
||||
|
||||
becomes
|
||||
|
||||
```js
|
||||
mini('c3 [e3 g3]')
|
||||
.withMiniLocation([1, 0, 0], [1, 11, 11]) // source location
|
||||
.fast(2);
|
||||
```
|
||||
|
||||
- double quoted strings and backtick strings are turned into `mini` calls (single quoted strings are left as is)
|
||||
- The source location is added by chaining `withMiniLocation`, which enables the real time highlighting later
|
||||
- (psuedo) variable names that look like notes (like `c4`, `bb2` or `fs3`) are turned into strings
|
||||
- support for top level await
|
||||
- operator overloading could be implemented in the future
|
||||
|
||||
This is how it works:
|
||||
|
||||
<img src="https://github.com/tidalcycles/strudel/blob/talk/talk/public/shiftflow.png?raw=true" width="800" />
|
||||
|
||||
- The user code is parsed with a [shift parser](https://github.com/shapesecurity/shift-parser-js), generating an AST
|
||||
- The AST is transformed to resolve the syntax sugar
|
||||
- The AST is used to generate code again (shift-codegen)
|
||||
|
||||
Shift will most likely be replaced with acorn in the future, see https://github.com/tidalcycles/strudel/issues/174
|
||||
|
||||
### Mini Notation
|
||||
|
||||
Another important part of the user code is the mini notation, which allows to express rhythms in a short manner.
|
||||
|
||||
- the mini notation is [implemented as a PEG grammar](https://github.com/tidalcycles/strudel/blob/main/packages/mini/krill.pegjs), living in the [mini package](https://github.com/tidalcycles/strudel/tree/main/packages/mini)
|
||||
- it is based on [krill](https://github.com/Mdashdotdashn/krill) by Mdashdotdashn
|
||||
- the peg grammar is used to generate a parser with [peggyjs](https://peggyjs.org/)
|
||||
- the generated parser takes a mini notation string and outputs an AST
|
||||
- the AST can then be used to construct a pattern using the regular Strudel API
|
||||
|
||||
Here's an example AST:
|
||||
|
||||
```json
|
||||
{
|
||||
"type_": "pattern",
|
||||
"arguments_": { "alignment": "h" },
|
||||
"source_": [
|
||||
{
|
||||
"type_": "element", "source_": "c3",
|
||||
"location_": { "start": { "offset": 1, "line": 1, "column": 2 }, "end": { "offset": 4, "line": 1, "column": 5 } }
|
||||
},
|
||||
{
|
||||
"type_": "element",
|
||||
"location_": { "start": { "offset": 4, "line": 1, "column": 5 }, "end": { "offset": 11, "line": 1, "column": 12 } }
|
||||
"source_": {
|
||||
"type_": "pattern", "arguments_": { "alignment": "h" },
|
||||
"source_": [
|
||||
{
|
||||
"type_": "element", "source_": "e3",
|
||||
"location_": { "start": { "offset": 5, "line": 1, "column": 6 }, "end": { "offset": 8, "line": 1, "column": 9 } }
|
||||
},
|
||||
{
|
||||
"type_": "element", "source_": "g3",
|
||||
"location_": { "start": { "offset": 8, "line": 1, "column": 9 }, "end": { "offset": 10, "line": 1, "column": 11 } }
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
which translates to `seq(c3, seq(e3, g3))`
|
||||
|
||||
## 2. Querying & Scheduling
|
||||
|
||||
When the user code has been evaluated, we hopefully get a Pattern instance, which we can use to query events from.
|
||||
These events can then be used to trigger side effects in the real world. On that note, Events are mostly called Hap(s) in the codebase, because JS already has a built in `Event` class.
|
||||
|
||||
### Querying
|
||||
|
||||
> Querying = Asking a Pattern for Events within a certain time span
|
||||
|
||||
```js
|
||||
seq('c3', ['e3', 'g3']) // <--- Pattern
|
||||
.queryArc(0, 2) // query events within 0 and 2 cycles
|
||||
.map((hap) => hap.showWhole()); // make readable
|
||||
```
|
||||
|
||||
yields
|
||||
|
||||
```js
|
||||
[
|
||||
'0/1 -> 1/2: c3', // cycle 0
|
||||
'1/2 -> 3/4: e3',
|
||||
'3/4 -> 1/1: g3',
|
||||
'1/1 -> 3/2: c3', // cycle 1
|
||||
'3/2 -> 7/4: e3',
|
||||
'7/4 -> 2/1: g3',
|
||||
];
|
||||
```
|
||||
|
||||
### 🗓️ Scheduling
|
||||
|
||||
The scheduler will query events repeatedly, creating a possibly endless loop of time slices.
|
||||
Here is a simplified example of how it works
|
||||
|
||||
```js
|
||||
let step = 0.5; // query interval in seconds
|
||||
let tick = 0; // how many intervals have passed
|
||||
let pattern = seq('c3', ['e3', 'g3']); // pattern from user
|
||||
setInterval(() => {
|
||||
const events = pattern.queryArc(tick * step, ++tick * step);
|
||||
events.forEach((event) => {
|
||||
console.log(event.showWhole());
|
||||
const o = getAudioContext().createOscillator();
|
||||
o.frequency.value = getFreq(event.value);
|
||||
o.start(event.whole.begin);
|
||||
o.stop(event.whole.begin + event.duration);
|
||||
o.connect(getAudioContext().destination);
|
||||
});
|
||||
}, step * 1000); // query each "step" seconds
|
||||
```
|
||||
|
||||
## 3. Sound Output
|
||||
|
||||
The third and last step is to use the scheduled events to make sound.
|
||||
Patterns are wrapped with param functions to compose different properties of the sound.
|
||||
|
||||
```js
|
||||
note("[c2(3,8) [<eb2 g1> bb1]]") // sets frequency
|
||||
.s("<sawtooth square>") // sound source
|
||||
.gain(.5) // turn down volume
|
||||
.cutoff(sine.range(200,1000).slow(4)) // modulated cutoff
|
||||
.slow(2)
|
||||
.out().logValues()`,
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
Here is an example Hap value with different properties:
|
||||
|
||||
```js
|
||||
{ note: 'a4', s: 'sawtooth', gain: 0.5, cutoff: 267 }
|
||||
```
|
||||
<img src="waa-nodes.png" width="600" />
|
||||
|
||||
<div className="text-left">
|
||||
|
||||
- Patterns represent just values in time!
|
||||
- Suitable for any time based output (music, visuals, movement, .. ?)
|
||||
|
||||
### Supported Outputs
|
||||
|
||||
At the time of writing this doc, the following outputs are supported:
|
||||
|
||||
- Web Audio API `.out()` see [/webaudio](https://github.com/tidalcycles/strudel/tree/main/packages/webaudio)
|
||||
- MIDI `.midi()` see [/midi](https://github.com/tidalcycles/strudel/tree/main/packages/midi)
|
||||
- OSC `.osc()` see [/osc](https://github.com/tidalcycles/strudel/tree/main/packages/osc)
|
||||
- Serial `.serial()` see [/serial](https://github.com/tidalcycles/strudel/tree/main/packages/serial)
|
||||
- Tone.js `.tone()` (deprecated?) [/tone](https://github.com/tidalcycles/strudel/tree/main/packages/tone)
|
||||
- WebDirt `.webdirt()` (deprecated?) [/webdirt](https://github.com/tidalcycles/strudel/tree/main/packages/webdirt)
|
||||
- Speech `.speak()` (experimental) part of [/core](https://github.com/tidalcycles/strudel/tree/main/packages/core)
|
||||
|
||||
These could change, so make sure to check the [packages folder](https://github.com/tidalcycles/strudel/tree/main/packages).
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 88 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 83 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 49 KiB |
@@ -1,97 +0,0 @@
|
||||
import _import from 'eslint-plugin-import';
|
||||
import { fixupPluginRules } from '@eslint/compat';
|
||||
import globals from 'globals';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import js from '@eslint/js';
|
||||
import { FlatCompat } from '@eslint/eslintrc';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all,
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
'**/krill-parser.js',
|
||||
'**/krill.pegjs',
|
||||
'**/.eslintrc.json',
|
||||
'**/server.js',
|
||||
'**/tidal-sniffer.js',
|
||||
'**/*.jsx',
|
||||
'**/tunejs.js',
|
||||
'out/**/*',
|
||||
'**/postcss.config.js',
|
||||
'**/postcss.config.cjs',
|
||||
'**/tailwind.config.js',
|
||||
'**/tailwind.config.cjs',
|
||||
'**/vite.config.js',
|
||||
'**/dist/**/*',
|
||||
'!**/*.mjs',
|
||||
'**/*.tsx',
|
||||
'**/*.ts',
|
||||
'**/*.json',
|
||||
'**/dev-dist',
|
||||
'**/dist',
|
||||
'src-tauri/target/**/*',
|
||||
'**/reverbGen.mjs',
|
||||
'**/hydra.mjs',
|
||||
'**/jsdoc-synonyms.js',
|
||||
'packages/hs2js/src/hs2js.mjs',
|
||||
'packages/supradough/dough-export.mjs',
|
||||
'**/samples',
|
||||
],
|
||||
},
|
||||
...compat.extends('eslint:recommended').map((config) => ({
|
||||
...config,
|
||||
files: ['**/*.mjs', '**/*.js'],
|
||||
})),
|
||||
{
|
||||
files: ['**/*.mjs', '**/*.js'],
|
||||
|
||||
plugins: {
|
||||
import: fixupPluginRules(_import),
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.browser,
|
||||
},
|
||||
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
|
||||
rules: {
|
||||
'no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
destructuredArrayIgnorePattern: '.',
|
||||
ignoreRestSiblings: false,
|
||||
},
|
||||
],
|
||||
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
// Properties provided by AudioWorkletGlobalScope
|
||||
files: ['packages/superdough/worklets.mjs'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
currentTime: 'readonly',
|
||||
sampleRate: 'readonly',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -1,3 +0,0 @@
|
||||
# examples
|
||||
|
||||
This folder contains usage examples for different scenarios.
|
||||
@@ -1,5 +0,0 @@
|
||||
# buildless examples
|
||||
|
||||
These examples show you how strudel can be used in a regular html file, without the need for a build tool.
|
||||
|
||||
Most examples are using [skypack](https://www.skypack.dev/)
|
||||
@@ -1,31 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- <script src="../../packages/web/dist/index.js"></script> -->
|
||||
<script src="https://unpkg.com/@strudel/web@1.0.3"></script>
|
||||
</head>
|
||||
<body style="background: #222">
|
||||
<button id="play">play</button>
|
||||
<button id="stop">stop</button>
|
||||
<script>
|
||||
strudel.initStrudel();
|
||||
document.getElementById('play').addEventListener('click', () =>
|
||||
evaluate(`
|
||||
//@title washover @by Switch Angel
|
||||
//@social https://www.instagram.com/_switch_angel/
|
||||
|
||||
n("{0 1 3 5 2 }%16")
|
||||
.add(n(tri.range(0, 6).slow(3)))
|
||||
.scale("C4:pentatonic")
|
||||
.sound("sawtooth").att(saw.range(0, 0.05).fast(6))
|
||||
.release(3).pan(rand).decay(rand.range(0.1, 0.3))
|
||||
.lpf(tri.range(200, 8000).slow(5)).lpq(0)
|
||||
.gain(.4).vib(1).vibmod(.1)
|
||||
.scope({pos:.5})
|
||||
`),
|
||||
);
|
||||
document.getElementById('stop').addEventListener('click', () => strudel.hush());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,24 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- <script src="../../packages/web/dist/index.js"></script> -->
|
||||
<script src="https://unpkg.com/@strudel/web@1.0.3"></script>
|
||||
</head>
|
||||
<body style="background: #222">
|
||||
<button id="a">A</button>
|
||||
<button id="b">B</button>
|
||||
<button id="c">C</button>
|
||||
<button id="stop">stop</button>
|
||||
<script>
|
||||
initStrudel({
|
||||
prebake: () => samples('github:tidalcycles/dirt-samples'),
|
||||
});
|
||||
const click = (id, action) => document.getElementById(id).addEventListener('click', action);
|
||||
click('a', () => evaluate(`s('bd,jvbass(3,8)').jux(rev)`));
|
||||
click('b', () => s('bd*2,hh(3,4),jvbass(5,8,1)').jux(rev).play());
|
||||
click('c', () => s('bd*2,hh(3,4),jvbass:[0 4](5,8,1)').jux(rev).stack(s('~ sd')).play());
|
||||
click('stop', () => hush());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,9 +0,0 @@
|
||||
<script src="https://unpkg.com/hs2js@0.0.4"></script>
|
||||
<button id="hello">hello</button>
|
||||
<script>
|
||||
hs2js.setBase('https://unpkg.com/hs2js@0.0.4/dist/');
|
||||
hs2js.loadParser();
|
||||
document.getElementById('hello').addEventListener('click', () => {
|
||||
hs2js.evaluate('alert "hello from haskell!"');
|
||||
});
|
||||
</script>
|
||||
@@ -1,98 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>tidal.html</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0">
|
||||
<textarea id="code" style="width: 100%; height: 200px"></textarea>
|
||||
<pre id="result"></pre>
|
||||
<script src="https://unpkg.com/hs2js@0.0.8"></script>
|
||||
<script src="https://unpkg.com/@strudel/web@1.0.3"></script>
|
||||
<script>
|
||||
const { initStrudel } = strudel;
|
||||
const { run, parse, setBase, loadParser } = hs2js;
|
||||
setBase('https://unpkg.com/hs2js@0.0.4/dist/');
|
||||
const ready = loadParser();
|
||||
initStrudel({
|
||||
prebake: () => samples('github:tidalcycles/dirt-samples'),
|
||||
});
|
||||
|
||||
const textarea = document.getElementById('code');
|
||||
if (window.location.hash) {
|
||||
textarea.value = atob(window.location.hash.slice(1));
|
||||
} else {
|
||||
textarea.value = 'd1 $ s "jvbass(3,8)"';
|
||||
}
|
||||
textarea.addEventListener('input', (e) => {
|
||||
window.location.hash = btoa(e.target.value);
|
||||
update();
|
||||
});
|
||||
update();
|
||||
|
||||
function getInfixOperators() {
|
||||
let operators = {
|
||||
'>': 'set',
|
||||
'#': 'set',
|
||||
'+': 'add',
|
||||
'-': 'sub',
|
||||
'*': 'mul',
|
||||
'/': 'div',
|
||||
};
|
||||
let alignments = {
|
||||
in: (s) => '|' + s,
|
||||
out: (s) => s + '|',
|
||||
mix: (s) => '|' + s + '|',
|
||||
};
|
||||
let ops = {};
|
||||
Object.entries(operators).forEach(([o, name]) => {
|
||||
// operator without alignment
|
||||
ops[o] = (l, r) => reify(l)[name](reify(r));
|
||||
Object.entries(alignments).forEach(([a, getSymbol]) => {
|
||||
// get symbol with alignment
|
||||
let symbol = getSymbol(o);
|
||||
ops[symbol] = (l, r) => reify(l)[name][a](reify(r));
|
||||
});
|
||||
});
|
||||
ops['~>'] = (l, r) => reify(l).late(reify(r));
|
||||
ops['<~'] = (l, r) => reify(l).early(reify(r));
|
||||
ops['<$>'] = (l, r) => reify(r).fmap(l).outerJoin(); // is this right?
|
||||
return ops;
|
||||
}
|
||||
const ops = getInfixOperators();
|
||||
|
||||
async function update() {
|
||||
let result, tree;
|
||||
await ready;
|
||||
try {
|
||||
tree = parse(textarea.value);
|
||||
} catch (err) {
|
||||
console.warn('parse error');
|
||||
console.error(err);
|
||||
}
|
||||
console.log('parsed tree');
|
||||
console.log(tree.rootNode.toString());
|
||||
try {
|
||||
let patterns = {};
|
||||
window.p = (name, pattern) => {
|
||||
patterns[name] = pattern;
|
||||
};
|
||||
window.d1 = (pat) => window.p(1, pat);
|
||||
window.d2 = (pat) => window.p(2, pat);
|
||||
window.d3 = (pat) => window.p(3, pat);
|
||||
window.rot = late;
|
||||
result = run(tree.rootNode, window, ops);
|
||||
if (Object.values(patterns).length) {
|
||||
stack(...Object.values(patterns)).play();
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('eval error');
|
||||
console.error(err);
|
||||
result = 'ERROR: ' + err.message;
|
||||
}
|
||||
document.getElementById('result').innerHTML = 'Result: ' + result;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,32 +0,0 @@
|
||||
<script src="https://unpkg.com/@strudel/repl@1.2.7"></script>
|
||||
<!-- <script src="../../packages/repl/dist/index.js"></script> -->
|
||||
<strudel-editor>
|
||||
<!--
|
||||
// @date 23-08-15
|
||||
// "golf rolf" @by froos @license CC BY-NC-SA 4.0
|
||||
|
||||
setcps(1)
|
||||
stack(
|
||||
s("bd*2, ~ rim*<1!3 2>, hh*4").bank('RolandTR909')
|
||||
.off(-1/8, set(speed("1.5").gain(.25)))
|
||||
.mask("<0!16 1!64>")
|
||||
,
|
||||
note("g1(3,8)")
|
||||
.s("gm_synth_bass_2:<0 2>")
|
||||
.delay(".8:.25:.25")
|
||||
.clip("<.5!16 2!32>")
|
||||
.off(1/8, add(note("12?0.7")))
|
||||
.lpf(sine.range(500,2000).slow(32)).lpq(8)
|
||||
.add(note("0,.05"))
|
||||
.mask("<0!8 1!32>")
|
||||
,
|
||||
n("<0 1 2 3 4>*8").scale('G4 minor')
|
||||
.s("gm_lead_6_voice")
|
||||
.clip(sine.range(.2,.8).slow(8))
|
||||
.jux(rev)
|
||||
.room(2)
|
||||
.sometimes(add(note("12")))
|
||||
.lpf(perlin.range(200,20000).slow(4))
|
||||
).reset("<x@15 x(5,8)>")
|
||||
-->
|
||||
</strudel-editor>
|
||||
@@ -1,8 +0,0 @@
|
||||
<script src="https://unpkg.com/@strudel/web@1.0.3"></script>
|
||||
<button id="play">PLAY</button>
|
||||
<script>
|
||||
initStrudel({
|
||||
prebake: () => samples('github:tidalcycles/dirt-samples'),
|
||||
});
|
||||
document.getElementById('play').addEventListener('click', () => s('bd sd').play());
|
||||
</script>
|
||||
@@ -1,8 +0,0 @@
|
||||
# codemirror-repl example
|
||||
|
||||
This folder demonstrates how to set up a full strudel repl with the `@strudel/codemirror` package. Run it using:
|
||||
|
||||
```sh
|
||||
pnpm i
|
||||
pnpm dev
|
||||
```
|
||||
@@ -1,27 +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": "^6.0.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strudel/codemirror": "workspace:*",
|
||||
"@strudel/core": "workspace:*",
|
||||
"@strudel/draw": "workspace:*",
|
||||
"@strudel/mini": "workspace:*",
|
||||
"@strudel/soundfonts": "workspace:*",
|
||||
"@strudel/tonal": "workspace:*",
|
||||
"@strudel/transpiler": "workspace:*",
|
||||
"@strudel/webaudio": "workspace:*"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
# headless-repl demo
|
||||
|
||||
This demo shows how to use strudel in "headless mode".
|
||||
Buttons A / B / C will switch between different patterns.
|
||||
It showcases the usage of the `@strudel/web` package, using [vite](https://vitejs.dev/) as the dev server.
|
||||
|
||||
## Running
|
||||
|
||||
```sh
|
||||
pnpm i && cd examples/headless-repl
|
||||
pnpm dev
|
||||
# open http://localhost:5173/
|
||||
```
|
||||
@@ -1 +0,0 @@
|
||||
dist
|
||||
@@ -1,10 +0,0 @@
|
||||
# minimal repl
|
||||
|
||||
This folder demonstrates how to set up a minimal strudel repl using vite and vanilla JS. Run it using:
|
||||
|
||||
```sh
|
||||
npm i
|
||||
npm run dev
|
||||
```
|
||||
|
||||
If you're looking for a more feature rich alternative, have a look at the [../codemirror-repl](codemirror-repl example)
|
||||
@@ -1,11 +0,0 @@
|
||||
# superdough demo
|
||||
|
||||
This demo shows how to use [superdough](https://www.npmjs.com/package/superdough) with [vite](https://vitejs.dev/).
|
||||
|
||||
## Running
|
||||
|
||||
```sh
|
||||
pnpm i && cd examples/headless-repl
|
||||
pnpm dev
|
||||
# open http://localhost:5173/
|
||||
```
|
||||
@@ -1,38 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Superdough Example</title>
|
||||
<meta charset="UTF-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<button id="play">PLAAAAAAAY</button>
|
||||
<script type="module">
|
||||
import { superdough, samples, initAudioOnFirstClick, registerSynthSounds } from 'superdough';
|
||||
|
||||
const init = Promise.all([
|
||||
initAudioOnFirstClick(),
|
||||
samples('github:tidalcycles/dirt-samples'),
|
||||
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>
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "superdough-example",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"superdough": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^6.0.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
# @strudel/tidal
|
||||
|
||||
This is an experiment in implementing tree-sitter for parsing haskell.
|
||||
|
||||
```sh
|
||||
pnpm i
|
||||
cd haskell
|
||||
pnpm copy-wasm
|
||||
pnpm dev
|
||||
```
|
||||
@@ -1,32 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tree sitter test</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: #121213;
|
||||
color: white;
|
||||
}
|
||||
textarea {
|
||||
padding: 10px;
|
||||
color: white;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
}
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0">
|
||||
<textarea id="code" style="width: 100%; height: 200px"></textarea>
|
||||
<pre id="result"></pre>
|
||||
<script
|
||||
type="module"
|
||||
src="/main.js
|
||||
"
|
||||
></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,83 +0,0 @@
|
||||
import { run, parse, loadParser } from 'hs2js';
|
||||
import { initStrudel, reify, late, samples, stack } from '@strudel/web';
|
||||
|
||||
initStrudel({
|
||||
prebake: () => samples('github:tidalcycles/dirt-samples'),
|
||||
});
|
||||
|
||||
const ready = loadParser();
|
||||
|
||||
const textarea = document.getElementById('code');
|
||||
if (window.location.hash) {
|
||||
textarea.value = atob(window.location.hash.slice(1));
|
||||
} else {
|
||||
textarea.value = 'd1 $ s "jvbass(3,8)"';
|
||||
}
|
||||
textarea.addEventListener('input', (e) => {
|
||||
window.location.hash = btoa(e.target.value);
|
||||
update();
|
||||
});
|
||||
update();
|
||||
|
||||
function getInfixOperators() {
|
||||
let operators = {
|
||||
'>': 'set',
|
||||
'#': 'set',
|
||||
'+': 'add',
|
||||
'-': 'sub',
|
||||
'*': 'mul',
|
||||
'/': 'div',
|
||||
};
|
||||
let alignments = {
|
||||
in: (s) => '|' + s,
|
||||
out: (s) => s + '|',
|
||||
mix: (s) => '|' + s + '|',
|
||||
};
|
||||
let ops = {};
|
||||
Object.entries(operators).forEach(([o, name]) => {
|
||||
// operator without alignment
|
||||
ops[o] = (l, r) => reify(l)[name](reify(r));
|
||||
Object.entries(alignments).forEach(([a, getSymbol]) => {
|
||||
// get symbol with alignment
|
||||
let symbol = getSymbol(o);
|
||||
ops[symbol] = (l, r) => reify(l)[name][a](reify(r));
|
||||
});
|
||||
});
|
||||
ops['~>'] = (l, r) => reify(l).late(reify(r));
|
||||
ops['<~'] = (l, r) => reify(l).early(reify(r));
|
||||
ops['<$>'] = (l, r) => reify(r).fmap(l).outerJoin(); // is this right?
|
||||
return ops;
|
||||
}
|
||||
const ops = getInfixOperators();
|
||||
|
||||
async function update() {
|
||||
let result, tree;
|
||||
await ready;
|
||||
try {
|
||||
tree = parse(textarea.value);
|
||||
} catch (err) {
|
||||
console.warn('parse error');
|
||||
console.error(err);
|
||||
}
|
||||
console.log('parsed tree');
|
||||
console.log(tree.rootNode.toString());
|
||||
try {
|
||||
let patterns = {};
|
||||
window.p = (name, pattern) => {
|
||||
patterns[name] = pattern;
|
||||
};
|
||||
window.d1 = (pat) => window.p(1, pat);
|
||||
window.d2 = (pat) => window.p(2, pat);
|
||||
window.d3 = (pat) => window.p(3, pat);
|
||||
window.rot = late;
|
||||
result = run(tree.rootNode, window, ops);
|
||||
if (Object.values(patterns).length) {
|
||||
stack(...Object.values(patterns)).play();
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('eval error');
|
||||
console.error(err);
|
||||
result = 'ERROR: ' + err.message;
|
||||
}
|
||||
document.getElementById('result').innerHTML = 'Result: ' + result;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"name": "tidal-repl",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "pnpm copy-wasm && vite build",
|
||||
"preview": "vite preview",
|
||||
"copy-wasm": "cp node_modules/hs2js/dist/tree-sitter.wasm public && cp node_modules/hs2js/dist/tree-sitter-haskell.wasm public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://codeberg.org/uzu/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"titdalcycles",
|
||||
"strudel",
|
||||
"pattern",
|
||||
"livecoding",
|
||||
"algorave"
|
||||
],
|
||||
"author": "Felix Roos <flix91@gmail.com>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://codeberg.org/uzu/strudel/issues"
|
||||
},
|
||||
"homepage": "https://codeberg.org/uzu/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel/web": "workspace:*",
|
||||
"hs2js": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^6.0.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# public
|
||||
|
||||
this file is just here to make sure the public folder exists
|
||||
@@ -1,20 +1,14 @@
|
||||
// this barrel export is currently only used to find undocumented exports
|
||||
export * from './packages/codemirror/index.mjs';
|
||||
export * from './packages/core/index.mjs';
|
||||
export * from './packages/csound/index.mjs';
|
||||
export * from './packages/desktopbridge/index.mjs';
|
||||
export * from './packages/draw/index.mjs';
|
||||
export * from './packages/embed/index.mjs';
|
||||
export * from './packages/hydra/index.mjs';
|
||||
export * from './packages/midi/index.mjs';
|
||||
export * from './packages/mini/index.mjs';
|
||||
export * from './packages/osc/index.mjs';
|
||||
export * from './packages/repl/index.mjs';
|
||||
export * from './packages/react/index.mjs';
|
||||
export * from './packages/serial/index.mjs';
|
||||
export * from './packages/soundfonts/index.mjs';
|
||||
export * from './packages/superdough/index.mjs';
|
||||
export * from './packages/tonal/index.mjs';
|
||||
export * from './packages/transpiler/index.mjs';
|
||||
export * from './packages/web/index.mjs';
|
||||
export * from './packages/webaudio/index.mjs';
|
||||
export * from './packages/xen/index.mjs';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"includePattern": ".+\\.(js(doc|x)?|mjs)$",
|
||||
"excludePattern": "node_modules|shift-parser|shift-reducer|shift-traverser|dist"
|
||||
},
|
||||
"plugins": ["plugins/markdown", "jsdoc/jsdoc-synonyms"],
|
||||
"plugins": ["plugins/markdown"],
|
||||
"opts": {
|
||||
"destination": "./out/",
|
||||
"recurse": true
|
||||
-211
@@ -1,211 +0,0 @@
|
||||
0 silly argv {
|
||||
0 silly argv _: [ 'version' ],
|
||||
0 silly argv private: false,
|
||||
0 silly argv lernaVersion: '8.1.9',
|
||||
0 silly argv '$0': 'node_modules/lerna/dist/cli.js'
|
||||
0 silly argv }
|
||||
1 notice cli v8.1.9
|
||||
2 verbose packageConfigs Explicit "packages" configuration found in lerna.json. Resolving packages using the configured glob(s): ["packages/*"]
|
||||
3 verbose rootPath /Users/jaderose/Documents/Github/cstrudel/strudel
|
||||
4 info versioning independent
|
||||
5 silly isAnythingCommitted
|
||||
6 verbose isAnythingCommitted 1
|
||||
7 silly getCurrentBranch
|
||||
8 verbose currentBranch main
|
||||
9 silly remoteBranchExists
|
||||
10 silly isBehindUpstream
|
||||
11 silly isBehindUpstream main is behind origin/main by 0 commit(s) and ahead by 4
|
||||
12 silly hasTags
|
||||
13 verbose hasTags true
|
||||
14 silly git-describe.sync "@strudel/codemirror@1.2.7-4-ga3ac9646" => {"lastTagName":"@strudel/codemirror@1.2.7","lastVersion":"1.2.7","refCount":"4","sha":"a3ac9646","isDirty":false}
|
||||
15 info Looking for changed packages since @strudel/codemirror@1.2.7
|
||||
16 silly checking diff packages/codemirror
|
||||
17 silly no diff found in @strudel/codemirror
|
||||
18 silly checking diff packages/core
|
||||
19 silly no diff found in @strudel/core
|
||||
20 silly checking diff packages/csound
|
||||
21 silly no diff found in @strudel/csound
|
||||
22 silly checking diff packages/desktopbridge
|
||||
23 silly no diff found in @strudel/desktopbridge
|
||||
24 silly checking diff packages/draw
|
||||
25 silly no diff found in @strudel/draw
|
||||
26 silly checking diff packages/embed
|
||||
27 silly no diff found in @strudel/embed
|
||||
28 silly checking diff packages/gamepad
|
||||
29 silly no diff found in @strudel/gamepad
|
||||
30 silly checking diff packages/hs2js
|
||||
31 silly no diff found in hs2js
|
||||
32 silly checking diff packages/hydra
|
||||
33 silly no diff found in @strudel/hydra
|
||||
34 silly checking diff packages/midi
|
||||
35 silly no diff found in @strudel/midi
|
||||
36 silly checking diff packages/mini
|
||||
37 silly no diff found in @strudel/mini
|
||||
38 silly checking diff packages/mondo
|
||||
39 silly no diff found in mondolang
|
||||
40 silly checking diff packages/mondough
|
||||
41 silly no diff found in @strudel/mondo
|
||||
42 silly checking diff packages/motion
|
||||
43 silly no diff found in @strudel/motion
|
||||
44 silly checking diff packages/mqtt
|
||||
45 silly no diff found in @strudel/mqtt
|
||||
46 silly checking diff packages/osc
|
||||
47 silly found diff in packages/osc/server.js
|
||||
48 verbose filtered diff [ 'packages/osc/server.js' ]
|
||||
49 silly checking diff packages/reference
|
||||
50 silly no diff found in @strudel/reference
|
||||
51 silly checking diff packages/repl
|
||||
52 silly no diff found in @strudel/repl
|
||||
53 silly checking diff packages/sampler
|
||||
54 silly no diff found in @strudel/sampler
|
||||
55 silly checking diff packages/serial
|
||||
56 silly no diff found in @strudel/serial
|
||||
57 silly checking diff packages/soundfonts
|
||||
58 silly no diff found in @strudel/soundfonts
|
||||
59 silly checking diff packages/superdough
|
||||
60 silly found diff in packages/superdough/package.json
|
||||
61 verbose filtered diff [ 'packages/superdough/package.json' ]
|
||||
62 silly checking diff packages/supradough
|
||||
63 silly found diff in packages/supradough/package.json
|
||||
64 verbose filtered diff [ 'packages/supradough/package.json' ]
|
||||
65 silly checking diff packages/tidal
|
||||
66 silly no diff found in @strudel/tidal
|
||||
67 silly checking diff packages/tonal
|
||||
68 silly no diff found in @strudel/tonal
|
||||
69 silly checking diff packages/transpiler
|
||||
70 silly no diff found in @strudel/transpiler
|
||||
71 silly checking diff packages/vite-plugin-bundle-audioworklet
|
||||
72 silly no diff found in vite-plugin-bundle-audioworklet
|
||||
73 silly checking diff packages/web
|
||||
74 silly no diff found in @strudel/web
|
||||
75 silly checking diff packages/webaudio
|
||||
76 silly found diff in packages/webaudio/package.json
|
||||
77 verbose filtered diff [ 'packages/webaudio/package.json' ]
|
||||
78 silly checking diff packages/xen
|
||||
79 silly no diff found in @strudel/xen
|
||||
80 verbose updated @strudel/codemirror
|
||||
81 verbose updated @strudel/csound
|
||||
82 verbose updated @strudel/midi
|
||||
83 verbose updated @strudel/osc
|
||||
84 verbose updated @strudel/repl
|
||||
85 verbose updated @strudel/soundfonts
|
||||
86 verbose updated superdough
|
||||
87 verbose updated supradough
|
||||
88 verbose updated @strudel/web
|
||||
89 verbose updated @strudel/webaudio
|
||||
90 verbose git-describe undefined => "@strudel/codemirror@1.2.7-4-ga3ac9646"
|
||||
91 silly git-describe parsed => {"lastTagName":"@strudel/codemirror@1.2.7","lastVersion":"1.2.7","refCount":"4","sha":"a3ac9646","isDirty":false}
|
||||
92 info execute Skipping releases
|
||||
93 silly lifecycle No script for "preversion" in "@strudel/monorepo", continuing
|
||||
94 silly lifecycle No script for "preversion" in "@strudel/osc", continuing
|
||||
95 silly lifecycle No script for "preversion" in "superdough", continuing
|
||||
96 silly lifecycle No script for "preversion" in "supradough", continuing
|
||||
97 verbose version supradough has no lockfile. Skipping lockfile update.
|
||||
98 verbose version @strudel/osc has no lockfile. Skipping lockfile update.
|
||||
99 verbose version superdough has no lockfile. Skipping lockfile update.
|
||||
100 silly lifecycle No script for "version" in "supradough", continuing
|
||||
101 silly lifecycle No script for "version" in "superdough", continuing
|
||||
102 silly lifecycle No script for "preversion" in "@strudel/codemirror", continuing
|
||||
103 silly lifecycle No script for "preversion" in "@strudel/webaudio", continuing
|
||||
104 silly lifecycle No script for "version" in "@strudel/osc", continuing
|
||||
105 verbose version @strudel/webaudio has no lockfile. Skipping lockfile update.
|
||||
106 verbose version @strudel/codemirror has no lockfile. Skipping lockfile update.
|
||||
107 silly lifecycle No script for "version" in "@strudel/codemirror", continuing
|
||||
108 silly lifecycle No script for "version" in "@strudel/webaudio", continuing
|
||||
109 silly lifecycle No script for "preversion" in "@strudel/csound", continuing
|
||||
110 silly lifecycle No script for "preversion" in "@strudel/midi", continuing
|
||||
111 silly lifecycle No script for "preversion" in "@strudel/soundfonts", continuing
|
||||
112 silly lifecycle No script for "preversion" in "@strudel/web", continuing
|
||||
113 verbose version @strudel/csound has no lockfile. Skipping lockfile update.
|
||||
114 verbose version @strudel/midi has no lockfile. Skipping lockfile update.
|
||||
115 verbose version @strudel/soundfonts has no lockfile. Skipping lockfile update.
|
||||
116 verbose version @strudel/web has no lockfile. Skipping lockfile update.
|
||||
117 silly lifecycle No script for "version" in "@strudel/midi", continuing
|
||||
118 silly lifecycle No script for "version" in "@strudel/csound", continuing
|
||||
119 silly lifecycle No script for "version" in "@strudel/soundfonts", continuing
|
||||
120 silly lifecycle No script for "preversion" in "@strudel/repl", continuing
|
||||
121 verbose version @strudel/repl has no lockfile. Skipping lockfile update.
|
||||
122 silly lifecycle No script for "version" in "@strudel/web", continuing
|
||||
123 silly lifecycle No script for "version" in "@strudel/repl", continuing
|
||||
124 silly lifecycle No script for "version" in "@strudel/monorepo", continuing
|
||||
125 verbose version Updating root pnpm-lock.yaml
|
||||
126 silly version Skipped applying prettier to ignored file: packages/supradough/package.json
|
||||
127 silly version Skipped applying prettier to ignored file: packages/superdough/package.json
|
||||
128 silly version Skipped applying prettier to ignored file: packages/osc/package.json
|
||||
129 silly version Skipped applying prettier to ignored file: packages/codemirror/package.json
|
||||
130 silly version Skipped applying prettier to ignored file: packages/webaudio/package.json
|
||||
131 silly version Skipped applying prettier to ignored file: packages/midi/package.json
|
||||
132 silly version Skipped applying prettier to ignored file: packages/csound/package.json
|
||||
133 silly version Skipped applying prettier to ignored file: packages/soundfonts/package.json
|
||||
134 silly version Skipped applying prettier to ignored file: packages/web/package.json
|
||||
135 silly version Skipped applying prettier to ignored file: packages/repl/package.json
|
||||
136 silly version Skipped applying prettier to ignored file: pnpm-lock.yaml
|
||||
137 silly gitAdd [
|
||||
137 silly gitAdd 'packages/supradough/package.json',
|
||||
137 silly gitAdd 'packages/superdough/package.json',
|
||||
137 silly gitAdd 'packages/osc/package.json',
|
||||
137 silly gitAdd 'packages/codemirror/package.json',
|
||||
137 silly gitAdd 'packages/webaudio/package.json',
|
||||
137 silly gitAdd 'packages/midi/package.json',
|
||||
137 silly gitAdd 'packages/csound/package.json',
|
||||
137 silly gitAdd 'packages/soundfonts/package.json',
|
||||
137 silly gitAdd 'packages/web/package.json',
|
||||
137 silly gitAdd 'packages/repl/package.json',
|
||||
137 silly gitAdd 'pnpm-lock.yaml'
|
||||
137 silly gitAdd ]
|
||||
138 silly gitCommit Publish
|
||||
138 silly gitCommit
|
||||
138 silly gitCommit - @strudel/codemirror@1.2.8
|
||||
138 silly gitCommit - @strudel/csound@1.2.8
|
||||
138 silly gitCommit - @strudel/midi@1.2.8
|
||||
138 silly gitCommit - @strudel/osc@1.3.2
|
||||
138 silly gitCommit - @strudel/repl@1.2.9
|
||||
138 silly gitCommit - @strudel/soundfonts@1.2.8
|
||||
138 silly gitCommit - superdough@1.2.5
|
||||
138 silly gitCommit - supradough@1.2.4
|
||||
138 silly gitCommit - @strudel/web@1.2.8
|
||||
138 silly gitCommit - @strudel/webaudio@1.2.9
|
||||
139 verbose git [
|
||||
139 verbose git 'commit',
|
||||
139 verbose git '-F',
|
||||
139 verbose git '/private/var/folders/hc/yf_zr55547sbcpz5rj7q85sc0000gn/T/3c85b5d4-56ae-40e9-8df7-db2bc1048402/lerna-commit.txt'
|
||||
139 verbose git ]
|
||||
140 silly gitTag @strudel/codemirror@1.2.8 git tag %s -m %s
|
||||
141 verbose git [
|
||||
141 verbose git 'tag',
|
||||
141 verbose git '@strudel/codemirror@1.2.8',
|
||||
141 verbose git '-m',
|
||||
141 verbose git '@strudel/codemirror@1.2.8'
|
||||
141 verbose git ]
|
||||
142 silly gitTag @strudel/csound@1.2.8 git tag %s -m %s
|
||||
143 verbose git [ 'tag', '@strudel/csound@1.2.8', '-m', '@strudel/csound@1.2.8' ]
|
||||
144 silly gitTag @strudel/midi@1.2.8 git tag %s -m %s
|
||||
145 verbose git [ 'tag', '@strudel/midi@1.2.8', '-m', '@strudel/midi@1.2.8' ]
|
||||
146 silly gitTag @strudel/osc@1.3.2 git tag %s -m %s
|
||||
147 verbose git [ 'tag', '@strudel/osc@1.3.2', '-m', '@strudel/osc@1.3.2' ]
|
||||
148 silly gitTag @strudel/repl@1.2.9 git tag %s -m %s
|
||||
149 verbose git [ 'tag', '@strudel/repl@1.2.9', '-m', '@strudel/repl@1.2.9' ]
|
||||
150 silly gitTag @strudel/soundfonts@1.2.8 git tag %s -m %s
|
||||
151 verbose git [
|
||||
151 verbose git 'tag',
|
||||
151 verbose git '@strudel/soundfonts@1.2.8',
|
||||
151 verbose git '-m',
|
||||
151 verbose git '@strudel/soundfonts@1.2.8'
|
||||
151 verbose git ]
|
||||
152 silly gitTag superdough@1.2.5 git tag %s -m %s
|
||||
153 verbose git [ 'tag', 'superdough@1.2.5', '-m', 'superdough@1.2.5' ]
|
||||
154 silly gitTag supradough@1.2.4 git tag %s -m %s
|
||||
155 verbose git [ 'tag', 'supradough@1.2.4', '-m', 'supradough@1.2.4' ]
|
||||
156 silly gitTag @strudel/web@1.2.8 git tag %s -m %s
|
||||
157 verbose git [ 'tag', '@strudel/web@1.2.8', '-m', '@strudel/web@1.2.8' ]
|
||||
158 silly gitTag @strudel/webaudio@1.2.9 git tag %s -m %s
|
||||
159 verbose git [ 'tag', '@strudel/webaudio@1.2.9', '-m', '@strudel/webaudio@1.2.9' ]
|
||||
160 error Error: Command failed with exit code 128: git tag superdough@1.2.5 -m superdough@1.2.5
|
||||
160 error fatal: tag 'superdough@1.2.5' already exists
|
||||
160 error at makeError (/Users/jaderose/Documents/Github/cstrudel/strudel/node_modules/.pnpm/execa@5.0.0/node_modules/execa/lib/error.js:59:11)
|
||||
160 error at handlePromise (/Users/jaderose/Documents/Github/cstrudel/strudel/node_modules/.pnpm/execa@5.0.0/node_modules/execa/index.js:114:26)
|
||||
160 error at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
|
||||
160 error at async Promise.all (index 6)
|
||||
160 error at async VersionCommand.gitCommitAndTagVersionForUpdates (/Users/jaderose/Documents/Github/cstrudel/strudel/node_modules/.pnpm/lerna@8.1.9_encoding@0.1.13/node_modules/lerna/dist/index.js:9957:11)
|
||||
160 error at async VersionCommand.commitAndTagUpdates (/Users/jaderose/Documents/Github/cstrudel/strudel/node_modules/.pnpm/lerna@8.1.9_encoding@0.1.13/node_modules/lerna/dist/index.js:9933:18)
|
||||
160 error at async Promise.all (index 0)
|
||||
+4
-2
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"packages": ["packages/*"],
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "independent",
|
||||
"npmClient": "pnpm",
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||
"useWorkspaces": true
|
||||
}
|
||||
|
||||
+14
-10
@@ -5,24 +5,22 @@ made into a pattern swatch.
|
||||
|
||||
Example: <https://felixroos.github.io/strudel/swatch/>
|
||||
|
||||
Please note: These instructions have not been fully tested/adapted since strudel moved to codeberg from github. PRs welcome!
|
||||
|
||||
## deploy
|
||||
|
||||
### 1. fork the [strudel repo on codeberg](https://codeberg.org/uzu/strudel.git)
|
||||
### 1. fork the [strudel repo on github](https://github.com/tidalcycles/strudel.git)
|
||||
|
||||
### 2. clone your fork to your machine `git clone https://codeberg.org/<your-username>/strudel.git strudel && cd strudel`
|
||||
### 2. clone your fork to your machine `git clone https://github.com/<your-username>/strudel.git strudel && cd strudel`
|
||||
|
||||
### 3. create a separate branch like `git branch patternuary && git checkout patternuary`
|
||||
|
||||
### 4. save one or more .txt files in the my-patterns folder
|
||||
|
||||
### 5. edit `website/public/CNAME` to contain `<your-username>.codeberg.page/strudel`
|
||||
### 5. edit `website/public/CNAME` to contain `<your-username>.github.io/strudel`
|
||||
|
||||
### 6. edit `website/astro.config.mjs` to use site: `https://<your-username>.codeberg.page` and base `/strudel`, like this
|
||||
### 6. edit `website/astro.config.mjs` to use site: `https://<your-username>.github.io` and base `/strudel`, like this
|
||||
|
||||
```js
|
||||
const site = 'https://<your-username>.codeberg.page';
|
||||
const site = 'https://<your-username>.github.io';
|
||||
const base = '/strudel';
|
||||
```
|
||||
|
||||
@@ -32,9 +30,15 @@ const base = '/strudel';
|
||||
git add . && git commit -m "site config" && git push --set-upstream origin
|
||||
```
|
||||
|
||||
### 8. deploy to codeberg pages
|
||||
### 8. deploy to github pages
|
||||
|
||||
### 9. view your patterns at `<your-username>.codeberg.page/strudel/swatch/`
|
||||
- go to settings -> pages and select "Github Actions" as source
|
||||
- go to settings -> environments -> github-pages and press the edit button next to `main` and type in `patternuary` (under "Deployment branches")
|
||||
- go to Actions -> `Build and Deploy` and click `Run workflow` with branch `patternuary`
|
||||
|
||||
### 9. view your patterns at `<your-username>.github.io/strudel/swatch/`
|
||||
|
||||
Alternatively, github pages allows you to use a custom domain, like https://mycooldomain.org/swatch/. [See their documentation for details](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site).
|
||||
|
||||
### 10. optional: automatic deployment
|
||||
|
||||
@@ -43,7 +47,7 @@ If you want to automatically deploy your site on push, go to `deploy.yml` and ch
|
||||
## running locally
|
||||
|
||||
- install dependencies with `npm run setup`
|
||||
- run dev server with `npm run repl` and open `http://localhost:4321/strudel/swatch/`
|
||||
- run dev server with `npm run repl` and open `http://localhost:3000/strudel/swatch/`
|
||||
|
||||
## tests fail?
|
||||
|
||||
|
||||
+27
-35
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@strudel/monorepo",
|
||||
"name": "@strudel.cycles/monorepo",
|
||||
"version": "0.5.0",
|
||||
"private": true,
|
||||
"description": "Port of tidalcycles to javascript",
|
||||
@@ -11,7 +11,6 @@
|
||||
"test": "npm run pretest && vitest run --version",
|
||||
"test-ui": "npm run pretest && vitest --ui",
|
||||
"test-coverage": "npm run pretest && vitest --coverage",
|
||||
"bench": "npm run pretest && vitest bench",
|
||||
"snapshot": "npm run pretest && vitest run -u --silent",
|
||||
"repl": "npm run prestart && cd website && npm run dev",
|
||||
"start": "npm run prestart && cd website && npm run dev",
|
||||
@@ -19,19 +18,18 @@
|
||||
"build": "npm run prebuild && cd website && npm run build",
|
||||
"preview": "cd website && npm run preview",
|
||||
"osc": "cd packages/osc && npm run server",
|
||||
"jsdoc": "jsdoc packages/ -c jsdoc/jsdoc.config.json",
|
||||
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc/jsdoc.config.json",
|
||||
"lint": "eslint . --quiet",
|
||||
"jsdoc": "jsdoc packages/ -c jsdoc.config.json",
|
||||
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json",
|
||||
"lint": "eslint . --ext mjs,js --quiet",
|
||||
"codeformat": "prettier --write .",
|
||||
"format-check": "prettier --check .",
|
||||
"report-undocumented": "npm run jsdoc-json && node 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",
|
||||
"sampler": "cd samples && node ../packages/sampler/sample-server.mjs",
|
||||
"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"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://codeberg.org/uzu/strudel.git"
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tidalcycles",
|
||||
@@ -43,38 +41,32 @@
|
||||
"author": "Alex McLean <alex@slab.org> (https://slab.org)",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://codeberg.org/uzu/strudel/issues"
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://strudel.cc",
|
||||
"homepage": "https://strudel.tidalcycles.org",
|
||||
"dependencies": {
|
||||
"@strudel/core": "workspace:*",
|
||||
"@strudel/mini": "workspace:*",
|
||||
"@strudel/tonal": "workspace:*",
|
||||
"@strudel/transpiler": "workspace:*",
|
||||
"@strudel/webaudio": "workspace:*",
|
||||
"@strudel/xen": "workspace:*"
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@strudel.cycles/mini": "workspace:*",
|
||||
"@strudel.cycles/tonal": "workspace:*",
|
||||
"@strudel.cycles/transpiler": "workspace:*",
|
||||
"@strudel.cycles/webaudio": "workspace:*",
|
||||
"@strudel.cycles/xen": "workspace:*",
|
||||
"acorn": "^8.8.1",
|
||||
"dependency-tree": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.2.5",
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.19.0",
|
||||
"@tauri-apps/cli": "^2.2.7",
|
||||
"@vitest/coverage-v8": "3.0.4",
|
||||
"@vitest/ui": "^3.0.4",
|
||||
"acorn": "^8.14.0",
|
||||
"dependency-tree": "^11.0.1",
|
||||
"eslint": "^9.19.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"@tauri-apps/cli": "^1.4.0",
|
||||
"@vitest/ui": "^0.28.0",
|
||||
"canvas": "^2.11.2",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"events": "^3.3.0",
|
||||
"globals": "^15.14.0",
|
||||
"jsdoc": "^4.0.4",
|
||||
"jsdoc": "^4.0.2",
|
||||
"jsdoc-json": "^2.0.2",
|
||||
"lerna": "^8.1.9",
|
||||
"prettier": "^3.4.2",
|
||||
"vitest": "^3.0.4",
|
||||
"vite-plugin-bundle-audioworklet": "workspace:*"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
"jsdoc-to-markdown": "^8.0.0",
|
||||
"lerna": "^6.6.1",
|
||||
"prettier": "^2.8.8",
|
||||
"rollup-plugin-visualizer": "^5.8.1",
|
||||
"vitest": "^0.33.0"
|
||||
}
|
||||
}
|
||||
|
||||
+12
-2
@@ -1,5 +1,15 @@
|
||||
# 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://codeberg.org/uzu/strudel/wiki/Technical-Manual) or the individual READMEs.
|
||||
To understand how those pieces connect, refer to the [Technical Manual](https://github.com/tidalcycles/strudel/wiki/Technical-Manual) or the individual READMEs.
|
||||
|
||||
This is a graphical view of all the packages: [full screen](https://raw.githubusercontent.com/tidalcycles/strudel/main/dependencies.svg)
|
||||
|
||||

|
||||
|
||||
Generated with
|
||||
|
||||
```sh
|
||||
npx depcruise --include-only "^packages" -X "node_modules" --output-type dot packages | dot -T svg > dependencygraph.svg
|
||||
```
|
||||
|
||||
@@ -1,464 +0,0 @@
|
||||
import jsdoc from '../../doc.json';
|
||||
import { autocompletion } from '@codemirror/autocomplete';
|
||||
import { h } from './html';
|
||||
//TODO: fix tonal scale import
|
||||
// import { Scale } from '@tonaljs/tonal';
|
||||
// import { soundMap } from '@strudel/webaudio';
|
||||
let soundMap = undefined;
|
||||
import { complex } from '@strudel/tonal';
|
||||
|
||||
const escapeHtml = (str) => {
|
||||
const div = document.createElement('div');
|
||||
div.innerText = str;
|
||||
return div.innerHTML;
|
||||
};
|
||||
|
||||
const stripHtml = (html) => {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = html;
|
||||
return div.textContent || div.innerText || '';
|
||||
};
|
||||
|
||||
const getDocLabel = (doc) => doc.name || doc.longname;
|
||||
|
||||
const buildParamsList = (params) =>
|
||||
params?.length
|
||||
? `
|
||||
<div class="autocomplete-info-params-section">
|
||||
<h4 class="autocomplete-info-section-title">Parameters</h4>
|
||||
<ul class="autocomplete-info-params-list">
|
||||
${params
|
||||
.map(
|
||||
({ name, type, description }) => `
|
||||
<li class="autocomplete-info-param-item">
|
||||
<span class="autocomplete-info-param-name">${name}</span>
|
||||
<span class="autocomplete-info-param-type">${type.names?.join(' | ')}</span>
|
||||
${description ? `<div class="autocomplete-info-param-desc">${stripHtml(description)}</div>` : ''}
|
||||
</li>
|
||||
`,
|
||||
)
|
||||
.join('')}
|
||||
</ul>
|
||||
</div>
|
||||
`
|
||||
: '';
|
||||
|
||||
const buildExamples = (examples) =>
|
||||
examples?.length
|
||||
? `
|
||||
<div class="autocomplete-info-examples-section">
|
||||
<h4 class="autocomplete-info-section-title">Examples</h4>
|
||||
${examples
|
||||
.map(
|
||||
(example) => `
|
||||
<pre class="autocomplete-info-example-code">${escapeHtml(example)}</pre>
|
||||
`,
|
||||
)
|
||||
.join('')}
|
||||
</div>
|
||||
`
|
||||
: '';
|
||||
|
||||
export const Autocomplete = (doc) =>
|
||||
h`
|
||||
<div class="autocomplete-info-container">
|
||||
<div class="autocomplete-info-tooltip">
|
||||
<h3 class="autocomplete-info-function-name">${getDocLabel(doc)}</h3>
|
||||
${doc.synonyms_text ? `<div class="autocomplete-info-function-synonyms">Synonyms: ${doc.synonyms_text}</div>` : ''}
|
||||
${doc.description ? `<div class="autocomplete-info-function-description">${doc.description}</div>` : ''}
|
||||
${buildParamsList(doc.params)}
|
||||
${buildExamples(doc.examples)}
|
||||
</div>
|
||||
</div>
|
||||
`[0];
|
||||
|
||||
const isValidDoc = (doc) => {
|
||||
const label = getDocLabel(doc);
|
||||
return label && !label.startsWith('_') && !['package'].includes(doc.kind);
|
||||
};
|
||||
|
||||
const hasExcludedTags = (doc) =>
|
||||
['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag));
|
||||
|
||||
export function bankCompletions() {
|
||||
// TODO: FIX IMPORT
|
||||
const soundDict = soundMap?.get() ?? {};
|
||||
|
||||
const banks = new Set();
|
||||
for (const key of Object.keys(soundDict)) {
|
||||
const [bank, suffix] = key.split('_');
|
||||
if (suffix && bank) banks.add(bank);
|
||||
}
|
||||
return Array.from(banks)
|
||||
.sort()
|
||||
.map((name) => ({ label: name, type: 'bank' }));
|
||||
}
|
||||
|
||||
// Attempt to get all scale names from Tonal TODO: FIX IMPORT
|
||||
let scaleCompletions = [];
|
||||
// try {
|
||||
// scaleCompletions = (Scale.names ? Scale.names() : []).map((name) => ({ label: name, type: 'scale' }));
|
||||
// } catch (e) {
|
||||
// console.warn('[autocomplete] Could not load scale names from Tonal:', e);
|
||||
// }
|
||||
|
||||
// Valid mode values for voicing
|
||||
const modeCompletions = [
|
||||
{ label: 'below', type: 'mode' },
|
||||
{ label: 'above', type: 'mode' },
|
||||
{ label: 'duck', type: 'mode' },
|
||||
{ label: 'root', type: 'mode' },
|
||||
];
|
||||
|
||||
// Valid chord symbols from ireal dictionary plus empty string for major triads
|
||||
const chordSymbols = ['', ...Object.keys(complex)].sort();
|
||||
const chordSymbolCompletions = chordSymbols.map((symbol) => {
|
||||
if (symbol === '') {
|
||||
return {
|
||||
label: 'major',
|
||||
apply: '',
|
||||
type: 'chord-symbol',
|
||||
};
|
||||
}
|
||||
return {
|
||||
label: symbol,
|
||||
apply: symbol,
|
||||
type: 'chord-symbol',
|
||||
};
|
||||
});
|
||||
|
||||
export const getSynonymDoc = (doc, synonym) => {
|
||||
const synonyms = doc.synonyms || [];
|
||||
const docLabel = getDocLabel(doc);
|
||||
// Swap `doc.name` in for `s` in the list of synonyms
|
||||
const synonymsWithDoc = [docLabel, ...synonyms].filter((x) => x && x !== synonym);
|
||||
return {
|
||||
...doc,
|
||||
name: synonym,
|
||||
longname: synonym,
|
||||
synonyms: synonymsWithDoc,
|
||||
synonyms_text: synonymsWithDoc.join(', '),
|
||||
};
|
||||
};
|
||||
|
||||
const jsdocCompletions = (() => {
|
||||
const seen = new Set(); // avoid repetition
|
||||
const completions = [];
|
||||
for (const doc of jsdoc.docs) {
|
||||
if (!isValidDoc(doc) || hasExcludedTags(doc)) continue;
|
||||
const docLabel = getDocLabel(doc);
|
||||
// Remove duplicates
|
||||
const synonyms = doc.synonyms || [];
|
||||
let labels = [docLabel, ...synonyms];
|
||||
for (const label of labels) {
|
||||
// https://codemirror.net/docs/ref/#autocomplete.Completion
|
||||
if (label && !seen.has(label)) {
|
||||
seen.add(label);
|
||||
completions.push({
|
||||
label,
|
||||
info: () => Autocomplete(getSynonymDoc(doc, label)),
|
||||
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return completions;
|
||||
})();
|
||||
|
||||
// --- Handler functions for each context ---
|
||||
const pitchNames = [
|
||||
'C',
|
||||
'C#',
|
||||
'Db',
|
||||
'D',
|
||||
'D#',
|
||||
'Eb',
|
||||
'E',
|
||||
'E#',
|
||||
'Fb',
|
||||
'F',
|
||||
'F#',
|
||||
'Gb',
|
||||
'G',
|
||||
'G#',
|
||||
'Ab',
|
||||
'A',
|
||||
'A#',
|
||||
'Bb',
|
||||
'B',
|
||||
'B#',
|
||||
'Cb',
|
||||
];
|
||||
|
||||
// Cached regex patterns for scaleHandler
|
||||
const SCALE_NO_QUOTES_REGEX = /scale\(\s*$/;
|
||||
const SCALE_AFTER_COLON_REGEX = /scale\(\s*['"][^'"]*:[^'"]*$/;
|
||||
const SCALE_PRE_COLON_REGEX = /scale\(\s*['"][^'"]*$/;
|
||||
const SCALE_PITCH_MATCH_REGEX = /([A-Ga-g][#b]*)?$/;
|
||||
const SCALE_SPACES_TO_COLON_REGEX = /\s+/g;
|
||||
|
||||
function scaleHandler(context) {
|
||||
// First check for scale context without quotes - block with empty completions
|
||||
let scaleNoQuotesContext = context.matchBefore(SCALE_NO_QUOTES_REGEX);
|
||||
if (scaleNoQuotesContext) {
|
||||
return {
|
||||
from: scaleNoQuotesContext.to,
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Check for after-colon context first (more specific)
|
||||
let scaleAfterColonContext = context.matchBefore(SCALE_AFTER_COLON_REGEX);
|
||||
if (scaleAfterColonContext) {
|
||||
const text = scaleAfterColonContext.text;
|
||||
const colonIdx = text.lastIndexOf(':');
|
||||
if (colonIdx !== -1) {
|
||||
const fragment = text.slice(colonIdx + 1);
|
||||
const filteredScales = scaleCompletions.filter((s) => s.label.startsWith(fragment));
|
||||
const options = filteredScales.map((s) => ({
|
||||
...s,
|
||||
apply: s.label.replace(SCALE_SPACES_TO_COLON_REGEX, ':'),
|
||||
}));
|
||||
const from = scaleAfterColonContext.from + colonIdx + 1;
|
||||
return {
|
||||
from,
|
||||
options,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Then check for pre-colon context
|
||||
let scalePreColonContext = context.matchBefore(SCALE_PRE_COLON_REGEX);
|
||||
if (scalePreColonContext) {
|
||||
if (!scalePreColonContext.text.includes(':')) {
|
||||
if (context.explicit) {
|
||||
const text = scalePreColonContext.text;
|
||||
const match = text.match(SCALE_PITCH_MATCH_REGEX);
|
||||
const fragment = match ? match[0] : '';
|
||||
const filtered = pitchNames.filter((p) => p.toLowerCase().startsWith(fragment.toLowerCase()));
|
||||
const from = scalePreColonContext.to - fragment.length;
|
||||
const options = filtered.map((p) => ({ label: p, type: 'pitch' }));
|
||||
return { from, options };
|
||||
} else {
|
||||
return { from: scalePreColonContext.to, options: [] };
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Cached regex patterns for soundHandler
|
||||
const SOUND_NO_QUOTES_REGEX = /(s|sound)\(\s*$/;
|
||||
const SOUND_WITH_QUOTES_REGEX = /(s|sound)\(\s*['"][^'"]*$/;
|
||||
const SOUND_FRAGMENT_MATCH_REGEX = /(?:[\s[{(<])([\w]*)$/;
|
||||
|
||||
function soundHandler(context) {
|
||||
// First check for sound context without quotes - block with empty completions
|
||||
let soundNoQuotesContext = context.matchBefore(SOUND_NO_QUOTES_REGEX);
|
||||
if (soundNoQuotesContext) {
|
||||
return {
|
||||
from: soundNoQuotesContext.to,
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Then check for sound context with quotes - provide completions
|
||||
let soundContext = context.matchBefore(SOUND_WITH_QUOTES_REGEX);
|
||||
if (!soundContext) return null;
|
||||
|
||||
const text = soundContext.text;
|
||||
const quoteIdx = Math.max(text.lastIndexOf('"'), text.lastIndexOf("'"));
|
||||
if (quoteIdx === -1) return null;
|
||||
const inside = text.slice(quoteIdx + 1);
|
||||
const fragMatch = inside.match(SOUND_FRAGMENT_MATCH_REGEX);
|
||||
const fragment = fragMatch ? fragMatch[1] : inside;
|
||||
const soundNames = Object.keys(soundMap?.get() ?? {}).sort();
|
||||
const filteredSounds = soundNames.filter((name) => name.includes(fragment));
|
||||
let options = filteredSounds.map((name) => ({ label: name, type: 'sound' }));
|
||||
const from = soundContext.to - fragment.length;
|
||||
return {
|
||||
from,
|
||||
options,
|
||||
};
|
||||
}
|
||||
|
||||
// Cached regex patterns for bankHandler
|
||||
const BANK_NO_QUOTES_REGEX = /bank\(\s*$/;
|
||||
const BANK_WITH_QUOTES_REGEX = /bank\(\s*['"][^'"]*$/;
|
||||
|
||||
function bankHandler(context) {
|
||||
// First check for bank context without quotes - block with empty completions
|
||||
let bankNoQuotesContext = context.matchBefore(BANK_NO_QUOTES_REGEX);
|
||||
if (bankNoQuotesContext) {
|
||||
return {
|
||||
from: bankNoQuotesContext.to,
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Then check for bank context with quotes - provide completions
|
||||
let bankMatch = context.matchBefore(BANK_WITH_QUOTES_REGEX);
|
||||
if (!bankMatch) return null;
|
||||
|
||||
const text = bankMatch.text;
|
||||
const quoteIdx = Math.max(text.lastIndexOf('"'), text.lastIndexOf("'"));
|
||||
if (quoteIdx === -1) return null;
|
||||
const inside = text.slice(quoteIdx + 1);
|
||||
const fragment = inside;
|
||||
let banks = bankCompletions();
|
||||
const filteredBanks = banks.filter((b) => b.label.startsWith(fragment));
|
||||
const from = bankMatch.to - fragment.length;
|
||||
return {
|
||||
from,
|
||||
options: filteredBanks,
|
||||
};
|
||||
}
|
||||
|
||||
// Cached regex patterns for modeHandler
|
||||
const MODE_NO_QUOTES_REGEX = /mode\(\s*$/;
|
||||
const MODE_AFTER_COLON_REGEX = /mode\(\s*['"][^'"]*:[^'"]*$/;
|
||||
const MODE_PRE_COLON_REGEX = /mode\(\s*['"][^'"]*$/;
|
||||
const MODE_FRAGMENT_MATCH_REGEX = /(?:[\s[{(<])([\w:]*)$/;
|
||||
|
||||
function modeHandler(context) {
|
||||
// First check for mode context without quotes - block with empty completions
|
||||
let modeNoQuotesContext = context.matchBefore(MODE_NO_QUOTES_REGEX);
|
||||
if (modeNoQuotesContext) {
|
||||
return {
|
||||
from: modeNoQuotesContext.to,
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Check for after-colon context first (more specific)
|
||||
let modeAfterColonContext = context.matchBefore(MODE_AFTER_COLON_REGEX);
|
||||
if (modeAfterColonContext) {
|
||||
const text = modeAfterColonContext.text;
|
||||
const colonIdx = text.lastIndexOf(':');
|
||||
if (colonIdx !== -1) {
|
||||
const fragment = text.slice(colonIdx + 1);
|
||||
// For anchor after colon, we can suggest pitch names
|
||||
const filtered = pitchNames.filter((p) => p.toLowerCase().startsWith(fragment.toLowerCase()));
|
||||
const options = filtered.map((p) => ({ label: p, type: 'pitch' }));
|
||||
const from = modeAfterColonContext.from + colonIdx + 1;
|
||||
return {
|
||||
from,
|
||||
options,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Then check for pre-colon context
|
||||
let modeContext = context.matchBefore(MODE_PRE_COLON_REGEX);
|
||||
if (!modeContext) return null;
|
||||
|
||||
const text = modeContext.text;
|
||||
const quoteIdx = Math.max(text.lastIndexOf('"'), text.lastIndexOf("'"));
|
||||
if (quoteIdx === -1) return null;
|
||||
const inside = text.slice(quoteIdx + 1);
|
||||
const fragMatch = inside.match(MODE_FRAGMENT_MATCH_REGEX);
|
||||
const fragment = fragMatch ? fragMatch[1] : inside;
|
||||
const filteredModes = modeCompletions.filter((m) => m.label.startsWith(fragment));
|
||||
const from = modeContext.to - fragment.length;
|
||||
return {
|
||||
from,
|
||||
options: filteredModes,
|
||||
};
|
||||
}
|
||||
|
||||
// Cached regex patterns for chordHandler
|
||||
const CHORD_NO_QUOTES_REGEX = /chord\(\s*$/;
|
||||
const CHORD_WITH_QUOTES_REGEX = /chord\(\s*['"][^'"]*$/;
|
||||
const CHORD_FRAGMENT_MATCH_REGEX = /(?:[\s[{(<])([\w#b+^:-]*)$/;
|
||||
|
||||
function chordHandler(context) {
|
||||
// First check for chord context without quotes - block with empty completions
|
||||
let chordNoQuotesContext = context.matchBefore(CHORD_NO_QUOTES_REGEX);
|
||||
if (chordNoQuotesContext) {
|
||||
return {
|
||||
from: chordNoQuotesContext.to,
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Then check for chord context with quotes - provide completions
|
||||
let chordContext = context.matchBefore(CHORD_WITH_QUOTES_REGEX);
|
||||
if (!chordContext) return null;
|
||||
|
||||
const text = chordContext.text;
|
||||
const quoteIdx = Math.max(text.lastIndexOf('"'), text.lastIndexOf("'"));
|
||||
if (quoteIdx === -1) return null;
|
||||
const inside = text.slice(quoteIdx + 1);
|
||||
|
||||
// Use same fragment matching as sound/mode for expressions like "<G Am>"
|
||||
const fragMatch = inside.match(CHORD_FRAGMENT_MATCH_REGEX);
|
||||
const fragment = fragMatch ? fragMatch[1] : inside;
|
||||
|
||||
// Check if fragment contains any pitch name at start (for root + symbol)
|
||||
let rootMatch = null;
|
||||
let symbolFragment = fragment;
|
||||
for (const pitch of pitchNames) {
|
||||
if (fragment.toLowerCase().startsWith(pitch.toLowerCase())) {
|
||||
rootMatch = pitch;
|
||||
symbolFragment = fragment.slice(pitch.length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rootMatch) {
|
||||
// We have a root, now complete chord symbols
|
||||
const filteredSymbols = chordSymbolCompletions.filter((s) =>
|
||||
s.label.toLowerCase().startsWith(symbolFragment.toLowerCase()),
|
||||
);
|
||||
|
||||
// Create completions that replace the entire chord, not just the symbol part
|
||||
const options = filteredSymbols;
|
||||
|
||||
const from = chordContext.to - symbolFragment.length;
|
||||
return { from, options };
|
||||
} else {
|
||||
// No root yet, complete with pitch names
|
||||
const filteredPitches = pitchNames.filter((p) => p.toLowerCase().startsWith(fragment.toLowerCase()));
|
||||
const options = filteredPitches.map((p) => ({ label: p, type: 'pitch' }));
|
||||
const from = chordContext.to - fragment.length;
|
||||
return { from, options };
|
||||
}
|
||||
}
|
||||
|
||||
// Cached regex patterns for fallbackHandler
|
||||
const FALLBACK_WORD_REGEX = /\w*/;
|
||||
|
||||
function fallbackHandler(context) {
|
||||
const word = context.matchBefore(FALLBACK_WORD_REGEX);
|
||||
if (word && word.from === word.to && !context.explicit) return null;
|
||||
if (word) {
|
||||
return {
|
||||
from: word.from,
|
||||
options: jsdocCompletions,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const handlers = [
|
||||
soundHandler,
|
||||
bankHandler,
|
||||
chordHandler,
|
||||
scaleHandler,
|
||||
modeHandler,
|
||||
// this handler *must* be last
|
||||
fallbackHandler,
|
||||
];
|
||||
|
||||
export const strudelAutocomplete = (context) => {
|
||||
for (const handler of handlers) {
|
||||
const result = handler(context);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const isAutoCompletionEnabled = (enabled) =>
|
||||
enabled ? [autocompletion({ override: [strudelAutocomplete], closeOnBlur: false })] : [];
|
||||
@@ -1,63 +0,0 @@
|
||||
import {
|
||||
keymap,
|
||||
highlightSpecialChars,
|
||||
drawSelection,
|
||||
highlightActiveLine,
|
||||
dropCursor,
|
||||
rectangularSelection,
|
||||
crosshairCursor,
|
||||
lineNumbers,
|
||||
highlightActiveLineGutter,
|
||||
} from '@codemirror/view';
|
||||
import {
|
||||
defaultHighlightStyle,
|
||||
syntaxHighlighting,
|
||||
bracketMatching,
|
||||
foldGutter,
|
||||
foldKeymap,
|
||||
} from '@codemirror/language';
|
||||
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
||||
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
|
||||
import { completionKeymap, closeBracketsKeymap } from '@codemirror/autocomplete';
|
||||
|
||||
// Taken + slightly modified from https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts
|
||||
|
||||
export const basicSetup = (() => [
|
||||
// lineNumbers(),
|
||||
// highlightActiveLineGutter(),
|
||||
highlightSpecialChars(),
|
||||
history(),
|
||||
// foldGutter(),
|
||||
// drawSelection(),
|
||||
dropCursor(),
|
||||
// EditorState.allowMultipleSelections.of(true),
|
||||
// indentOnInput(),
|
||||
// syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
||||
// autocompletion(),
|
||||
rectangularSelection(),
|
||||
crosshairCursor(),
|
||||
// highlightActiveLine(),
|
||||
// highlightSelectionMatches(),
|
||||
keymap.of([
|
||||
...closeBracketsKeymap,
|
||||
...defaultKeymap,
|
||||
// ...searchKeymap,
|
||||
...historyKeymap,
|
||||
// ...foldKeymap,
|
||||
// ...completionKeymap,
|
||||
]),
|
||||
])();
|
||||
|
||||
/// A minimal set of extensions to create a functional editor. Only
|
||||
/// includes [the default keymap](#commands.defaultKeymap), [undo
|
||||
/// history](#commands.history), [special character
|
||||
/// highlighting](#view.highlightSpecialChars), [custom selection
|
||||
/// drawing](#view.drawSelection), and [default highlight
|
||||
/// style](#language.defaultHighlightStyle).
|
||||
export const minimalSetup = (() => [
|
||||
highlightSpecialChars(),
|
||||
history(),
|
||||
drawSelection(),
|
||||
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
||||
keymap.of([...defaultKeymap, ...historyKeymap]),
|
||||
])();
|
||||
@@ -1,143 +1,37 @@
|
||||
import { closeBrackets } from '@codemirror/autocomplete';
|
||||
import { indentWithTab, toggleLineComment } from '@codemirror/commands';
|
||||
import { javascript, javascriptLanguage } from '@codemirror/lang-javascript';
|
||||
import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||
import { Compartment, EditorState, Prec } from '@codemirror/state';
|
||||
import {
|
||||
drawSelection,
|
||||
EditorView,
|
||||
highlightActiveLine,
|
||||
highlightActiveLineGutter,
|
||||
keymap,
|
||||
lineNumbers,
|
||||
} from '@codemirror/view';
|
||||
import { persistentAtom } from '@nanostores/persistent';
|
||||
import { logger, registerControl, repl } from '@strudel/core';
|
||||
import { cleanupDraw, Drawer } from '@strudel/draw';
|
||||
|
||||
import { isAutoCompletionEnabled } from './autocomplete.mjs';
|
||||
import { basicSetup } from './basicSetup.mjs';
|
||||
import { flash, isFlashEnabled } from './flash.mjs';
|
||||
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
|
||||
import { keybindings } from './keybindings.mjs';
|
||||
import { sliderPlugin, updateSliderWidgets } from './slider.mjs';
|
||||
import { activateTheme, initTheme, theme } from './themes.mjs';
|
||||
import { isTooltipEnabled } from './tooltip.mjs';
|
||||
import { updateWidgets, widgetPlugin } from './widget.mjs';
|
||||
import { jumpToCharacter } from './labelJump.mjs';
|
||||
|
||||
export { toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment } from '@codemirror/commands';
|
||||
|
||||
export const extensions = {
|
||||
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
|
||||
isBracketMatchingEnabled: (on) => (on ? bracketMatching({ brackets: '()[]{}<>' }) : []),
|
||||
isBracketClosingEnabled: (on) => (on ? closeBrackets() : []),
|
||||
isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []),
|
||||
theme,
|
||||
isAutoCompletionEnabled,
|
||||
isTooltipEnabled,
|
||||
isPatternHighlightingEnabled,
|
||||
isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []),
|
||||
isFlashEnabled,
|
||||
keybindings,
|
||||
isTabIndentationEnabled: (on) => (on ? keymap.of([indentWithTab]) : []),
|
||||
isMultiCursorEnabled: (on) =>
|
||||
on
|
||||
? [
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
EditorView.clickAddsSelectionRange.of((ev) => ev.metaKey || ev.ctrlKey),
|
||||
]
|
||||
: [],
|
||||
};
|
||||
export const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
|
||||
|
||||
export const defaultSettings = {
|
||||
keybindings: 'codemirror',
|
||||
isBracketMatchingEnabled: false,
|
||||
isBracketClosingEnabled: true,
|
||||
isLineNumbersDisplayed: true,
|
||||
isActiveLineHighlighted: false,
|
||||
isAutoCompletionEnabled: false,
|
||||
isPatternHighlightingEnabled: true,
|
||||
isFlashEnabled: true,
|
||||
isTooltipEnabled: false,
|
||||
isLineWrappingEnabled: false,
|
||||
isTabIndentationEnabled: false,
|
||||
isMultiCursorEnabled: false,
|
||||
theme: 'strudelTheme',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 18,
|
||||
};
|
||||
|
||||
export const codemirrorSettings = persistentAtom('codemirror-settings', defaultSettings, {
|
||||
encode: JSON.stringify,
|
||||
decode: JSON.parse,
|
||||
});
|
||||
import { defaultKeymap } from '@codemirror/commands';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import { EditorView, highlightActiveLineGutter, keymap, lineNumbers } from '@codemirror/view';
|
||||
import { Drawer, repl } from '@strudel.cycles/core';
|
||||
import { flashField, flash } from './flash.mjs';
|
||||
import { highlightExtension, highlightMiniLocations } from './highlight.mjs';
|
||||
import { oneDark } from './themes/one-dark';
|
||||
|
||||
// https://codemirror.net/docs/guide/
|
||||
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, root, mondo }) {
|
||||
const settings = codemirrorSettings.get();
|
||||
const initialSettings = Object.keys(compartments).map((key) =>
|
||||
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
|
||||
);
|
||||
|
||||
initTheme(settings.theme);
|
||||
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, theme = oneDark, root }) {
|
||||
let state = EditorState.create({
|
||||
doc: initialCode,
|
||||
extensions: [
|
||||
/* search(),
|
||||
highlightSelectionMatches(), */
|
||||
...initialSettings,
|
||||
basicSetup,
|
||||
mondo ? [] : javascript(),
|
||||
javascriptLanguage.data.of({
|
||||
closeBrackets: { brackets: ['(', '[', '{', "'", '"', '<'] },
|
||||
bracketMatching: { brackets: ['(', '[', '{', "'", '"', '<'] },
|
||||
}),
|
||||
sliderPlugin,
|
||||
widgetPlugin,
|
||||
// indentOnInput(), // works without. already brought with javascript
|
||||
// extension? bracketMatching(), // does not do anything
|
||||
theme,
|
||||
javascript(),
|
||||
lineNumbers(),
|
||||
highlightExtension,
|
||||
highlightActiveLineGutter(),
|
||||
syntaxHighlighting(defaultHighlightStyle),
|
||||
keymap.of(defaultKeymap),
|
||||
flashField,
|
||||
EditorView.updateListener.of((v) => onChange(v)),
|
||||
drawSelection({ cursorBlinkRate: 0 }),
|
||||
Prec.highest(
|
||||
keymap.of([
|
||||
{
|
||||
key: 'Ctrl-Enter',
|
||||
run: () => onEvaluate?.(),
|
||||
},
|
||||
{
|
||||
key: 'Alt-Enter',
|
||||
run: () => onEvaluate?.(),
|
||||
},
|
||||
{
|
||||
key: 'Ctrl-.',
|
||||
run: () => onStop?.(),
|
||||
},
|
||||
{
|
||||
key: 'Alt-.',
|
||||
preventDefault: true,
|
||||
run: () => onStop?.(),
|
||||
},
|
||||
{
|
||||
key: 'Alt-w',
|
||||
run: (view) => jumpToCharacter(view, '$', 1),
|
||||
},
|
||||
{
|
||||
key: 'Alt-q',
|
||||
run: (view) => jumpToCharacter(view, '$', -1),
|
||||
},
|
||||
/* {
|
||||
key: 'Ctrl-Shift-.',
|
||||
run: () => (onPanic ? onPanic() : onStop?.()),
|
||||
},
|
||||
{
|
||||
key: 'Ctrl-Shift-Enter',
|
||||
run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()),
|
||||
}, */
|
||||
]),
|
||||
),
|
||||
keymap.of([
|
||||
{
|
||||
key: 'Ctrl-Enter',
|
||||
run: () => onEvaluate(),
|
||||
},
|
||||
{
|
||||
key: 'Ctrl-.',
|
||||
run: () => onStop(),
|
||||
},
|
||||
]),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -149,293 +43,71 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
||||
|
||||
export class StrudelMirror {
|
||||
constructor(options) {
|
||||
const {
|
||||
root,
|
||||
id,
|
||||
initialCode = '',
|
||||
onDraw,
|
||||
drawContext,
|
||||
drawTime = [0, 0],
|
||||
autodraw,
|
||||
prebake,
|
||||
bgFill = true,
|
||||
solo = true,
|
||||
...replOptions
|
||||
} = options;
|
||||
const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, ...replOptions } = options;
|
||||
this.code = initialCode;
|
||||
this.root = root;
|
||||
this.miniLocations = [];
|
||||
this.widgets = [];
|
||||
this.drawTime = drawTime;
|
||||
this.drawContext = drawContext;
|
||||
this.onDraw = onDraw || this.draw;
|
||||
this.id = id || s4();
|
||||
this.solo = solo;
|
||||
|
||||
this.drawer = new Drawer((haps, time, _, painters) => {
|
||||
const currentFrame = haps.filter((hap) => hap.isActive(time));
|
||||
this.drawer = new Drawer((haps, time) => {
|
||||
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
|
||||
this.highlight(currentFrame, time);
|
||||
this.onDraw(haps, time, painters);
|
||||
onDraw?.(haps, time, currentFrame);
|
||||
}, drawTime);
|
||||
|
||||
this.prebaked = prebake();
|
||||
autodraw && this.drawFirstFrame();
|
||||
const prebaked = prebake();
|
||||
prebaked.then(async () => {
|
||||
if (!onDraw) {
|
||||
return;
|
||||
}
|
||||
const { scheduler, evaluate } = await this.repl;
|
||||
// draw first frame instantly
|
||||
prebaked.then(async () => {
|
||||
await evaluate(this.code, false);
|
||||
this.drawer.invalidate(scheduler);
|
||||
onDraw?.(this.drawer.visibleHaps, 0, []);
|
||||
});
|
||||
});
|
||||
|
||||
this.repl = repl({
|
||||
...replOptions,
|
||||
id,
|
||||
onToggle: (started) => {
|
||||
onToggle: async (started) => {
|
||||
replOptions?.onToggle?.(started);
|
||||
const { scheduler } = await this.repl;
|
||||
if (started) {
|
||||
this.drawer.start(this.repl.scheduler);
|
||||
if (this.solo) {
|
||||
// stop other repls when this one is started
|
||||
document.dispatchEvent(
|
||||
new CustomEvent('start-repl', {
|
||||
detail: this.id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
this.drawer.start(scheduler);
|
||||
} else {
|
||||
this.drawer.stop();
|
||||
updateMiniLocations(this.editor, []);
|
||||
cleanupDraw(true, id);
|
||||
}
|
||||
},
|
||||
beforeEval: async () => {
|
||||
cleanupDraw(true, id);
|
||||
await this.prebaked;
|
||||
await replOptions?.beforeEval?.();
|
||||
await prebaked;
|
||||
},
|
||||
afterEval: (options) => {
|
||||
// remember for when highlighting is toggled on
|
||||
this.miniLocations = options.meta?.miniLocations;
|
||||
this.widgets = options.meta?.widgets;
|
||||
const sliders = this.widgets.filter((w) => w.type === 'slider');
|
||||
updateSliderWidgets(this.editor, sliders);
|
||||
const widgets = this.widgets.filter((w) => w.type !== 'slider');
|
||||
updateWidgets(this.editor, widgets);
|
||||
updateMiniLocations(this.editor, this.miniLocations);
|
||||
replOptions?.afterEval?.(options);
|
||||
// if no painters are set (.onPaint was not called), then we only need
|
||||
// the present moment (for highlighting)
|
||||
const drawTime = options.pattern.getPainters().length ? this.drawTime : [0, 0];
|
||||
this.drawer.setDrawTime(drawTime);
|
||||
// invalidate drawer after we've set the appropriate drawTime
|
||||
this.drawer.invalidate(this.repl.scheduler);
|
||||
this.drawer.invalidate();
|
||||
},
|
||||
});
|
||||
this.editor = initEditor({
|
||||
root,
|
||||
initialCode,
|
||||
onChange: (v) => {
|
||||
if (v.docChanged) {
|
||||
this.code = v.state.doc.toString();
|
||||
this.repl.setCode?.(this.code);
|
||||
}
|
||||
this.code = v.state.doc.toString();
|
||||
},
|
||||
onEvaluate: () => this.evaluate(),
|
||||
onStop: () => this.stop(),
|
||||
mondo: replOptions.mondo,
|
||||
});
|
||||
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 (this.solo && e.detail !== this.id) {
|
||||
this.stop();
|
||||
}
|
||||
};
|
||||
document.addEventListener('start-repl', this.onStartRepl);
|
||||
|
||||
// Handle global evaluation requests (e.g., from Vim :w)
|
||||
this.onEvaluateRequest = (e) => {
|
||||
try {
|
||||
// Evaluate current editor on repl-evaluate
|
||||
logger('[repl] evaluate via event');
|
||||
this.evaluate();
|
||||
e?.cancelable && e.preventDefault?.();
|
||||
} catch (err) {
|
||||
console.error('Error handling repl-evaluate event', err);
|
||||
}
|
||||
};
|
||||
document.addEventListener('repl-evaluate', this.onEvaluateRequest);
|
||||
document.addEventListener('repl-stop', this.onStopRequest);
|
||||
|
||||
// Toggle comments requested from Vim (gc)
|
||||
this.onToggleComment = (e) => {
|
||||
try {
|
||||
// Honor selections; toggleLineComment handles both selections and
|
||||
// single line
|
||||
toggleLineComment(this.editor);
|
||||
e?.cancelable && e.preventDefault?.();
|
||||
} catch (err) {
|
||||
console.error('Error handling repl-toggle-comment event', err);
|
||||
}
|
||||
};
|
||||
document.addEventListener('repl-toggle-comment', this.onToggleComment);
|
||||
}
|
||||
draw(haps, time, painters) {
|
||||
painters?.forEach((painter) => painter(this.drawContext, time, haps, this.drawTime));
|
||||
}
|
||||
async drawFirstFrame() {
|
||||
if (!this.onDraw) {
|
||||
return;
|
||||
}
|
||||
// draw first frame instantly
|
||||
await this.prebaked;
|
||||
try {
|
||||
await this.repl.evaluate(this.code, false);
|
||||
this.drawer.invalidate(this.repl.scheduler, -0.001);
|
||||
// draw at -0.001 to avoid haps at 0 to be visualized as active
|
||||
this.onDraw?.(this.drawer.visibleHaps, -0.001, this.drawer.painters);
|
||||
} catch (err) {
|
||||
console.warn('first frame could not be painted');
|
||||
}
|
||||
}
|
||||
async evaluate(autostart = true) {
|
||||
async evaluate() {
|
||||
const { evaluate } = await this.repl;
|
||||
this.flash();
|
||||
await this.repl.evaluate(this.code, autostart);
|
||||
await evaluate(this.code);
|
||||
}
|
||||
async stop() {
|
||||
this.repl.scheduler.stop();
|
||||
}
|
||||
|
||||
// Listen for global stop requests (e.g., from Vim :q)
|
||||
onStopRequest = (e) => {
|
||||
try {
|
||||
this.stop();
|
||||
e?.cancelable && e.preventDefault?.();
|
||||
} catch (err) {
|
||||
console.error('Error handling repl-stop event', err);
|
||||
}
|
||||
};
|
||||
async toggle() {
|
||||
if (this.repl.scheduler.started) {
|
||||
this.repl.stop();
|
||||
} else {
|
||||
this.evaluate();
|
||||
}
|
||||
const { scheduler } = await this.repl;
|
||||
scheduler.stop();
|
||||
}
|
||||
flash(ms) {
|
||||
flash(this.editor, ms);
|
||||
}
|
||||
highlight(haps, time) {
|
||||
highlightMiniLocations(this.editor, time, haps);
|
||||
}
|
||||
setFontSize(size) {
|
||||
this.root.style.fontSize = size + 'px';
|
||||
}
|
||||
setFontFamily(family) {
|
||||
this.root.style.fontFamily = family;
|
||||
const scroller = this.root.querySelector('.cm-scroller');
|
||||
if (scroller) {
|
||||
scroller.style.fontFamily = family;
|
||||
}
|
||||
}
|
||||
reconfigureExtension(key, value) {
|
||||
if (!extensions[key]) {
|
||||
console.warn(`extension ${key} is not known`);
|
||||
return;
|
||||
}
|
||||
value = parseBooleans(value);
|
||||
const newValue = extensions[key](value, this);
|
||||
this.editor.dispatch({
|
||||
effects: compartments[key].reconfigure(newValue),
|
||||
});
|
||||
if (key === 'theme') {
|
||||
activateTheme(value);
|
||||
}
|
||||
}
|
||||
setLineWrappingEnabled(enabled) {
|
||||
this.reconfigureExtension('isLineWrappingEnabled', enabled);
|
||||
}
|
||||
setBracketMatchingEnabled(enabled) {
|
||||
this.reconfigureExtension('isBracketMatchingEnabled', enabled);
|
||||
}
|
||||
setLineNumbersDisplayed(enabled) {
|
||||
this.reconfigureExtension('isLineNumbersDisplayed', enabled);
|
||||
}
|
||||
setBracketClosingEnabled(enabled) {
|
||||
this.reconfigureExtension('isBracketClosingEnabled', enabled);
|
||||
}
|
||||
setTheme(theme) {
|
||||
this.reconfigureExtension('theme', theme);
|
||||
}
|
||||
setAutocompletionEnabled(enabled) {
|
||||
this.reconfigureExtension('isAutoCompletionEnabled', enabled);
|
||||
}
|
||||
updateSettings(settings) {
|
||||
this.setFontSize(settings.fontSize);
|
||||
this.setFontFamily(settings.fontFamily);
|
||||
for (let key in extensions) {
|
||||
this.reconfigureExtension(key, settings[key]);
|
||||
}
|
||||
const updated = { ...codemirrorSettings.get(), ...settings };
|
||||
codemirrorSettings.set(updated);
|
||||
}
|
||||
changeSetting(key, value) {
|
||||
if (extensions[key]) {
|
||||
this.reconfigureExtension(key, value);
|
||||
return;
|
||||
} else if (key === 'fontFamily') {
|
||||
this.setFontFamily(value);
|
||||
} else if (key === 'fontSize') {
|
||||
this.setFontSize(value);
|
||||
}
|
||||
}
|
||||
setCode(code) {
|
||||
const changes = {
|
||||
from: 0,
|
||||
to: this.editor.state.doc.length,
|
||||
insert: code,
|
||||
};
|
||||
this.editor.dispatch({ changes });
|
||||
}
|
||||
clear() {
|
||||
this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl);
|
||||
this.onEvaluateRequest && document.removeEventListener('repl-evaluate', this.onEvaluateRequest);
|
||||
this.onStopRequest && document.removeEventListener('repl-stop', this.onStopRequest);
|
||||
this.onToggleComment && document.removeEventListener('repl-toggle-comment', this.onToggleComment);
|
||||
}
|
||||
getCursorLocation() {
|
||||
return this.editor.state.selection.main.head;
|
||||
}
|
||||
setCursorLocation(col) {
|
||||
return this.editor.dispatch({ selection: { anchor: col } });
|
||||
}
|
||||
appendCode(code) {
|
||||
const cursor = this.getCursorLocation();
|
||||
this.setCode(this.code + code);
|
||||
this.setCursorLocation(cursor);
|
||||
highlightMiniLocations(this.editor.view, time, haps);
|
||||
}
|
||||
}
|
||||
|
||||
export function parseBooleans(value) {
|
||||
return { true: true, false: false }[value] ?? value;
|
||||
}
|
||||
|
||||
// helper function to generate repl ids
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the css of highlighted events. Make sure to use single quotes!
|
||||
* @name markcss
|
||||
* @example
|
||||
* note("c a f e")
|
||||
* .markcss('text-decoration:underline')
|
||||
*/
|
||||
export const markcss = registerControl('markcss');
|
||||
|
||||
@@ -11,9 +11,7 @@ export const flashField = StateField.define({
|
||||
for (let e of tr.effects) {
|
||||
if (e.is(setFlash)) {
|
||||
if (e.value && tr.newDoc.length > 0) {
|
||||
const mark = Decoration.mark({
|
||||
attributes: { style: `background-color: rgba(255,255,255, .4); filter: invert(10%)` },
|
||||
});
|
||||
const mark = Decoration.mark({ attributes: { style: `background-color: #FFCA2880` } });
|
||||
flash = Decoration.set([mark.range(0, tr.newDoc.length)]);
|
||||
} else {
|
||||
flash = Decoration.set([]);
|
||||
@@ -35,5 +33,3 @@ export const flash = (view, ms = 200) => {
|
||||
view.dispatch({ effects: setFlash.of(false) });
|
||||
}, ms);
|
||||
};
|
||||
|
||||
export const isFlashEnabled = (on) => (on ? flashField : []);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RangeSetBuilder, StateEffect, StateField, Prec } from '@codemirror/state';
|
||||
import { RangeSetBuilder, StateEffect, StateField } from '@codemirror/state';
|
||||
import { Decoration, EditorView } from '@codemirror/view';
|
||||
|
||||
export const setMiniLocations = StateEffect.define();
|
||||
@@ -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
|
||||
const haps = new Map();
|
||||
for (let hap of e.value.haps) {
|
||||
if (!hap.context?.locations || !hap.whole) {
|
||||
continue;
|
||||
}
|
||||
for (let { start, end } of hap.context.locations) {
|
||||
let id = `${start}:${end}`;
|
||||
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 };
|
||||
}
|
||||
}
|
||||
@@ -92,8 +90,7 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
|
||||
|
||||
if (haps.has(id)) {
|
||||
const hap = haps.get(id);
|
||||
const color = hap.value?.color ?? 'var(--foreground)';
|
||||
const style = hap.value?.markcss || `outline: solid 2px ${color}`;
|
||||
const color = hap.context.color ?? 'var(--foreground)';
|
||||
// Get explicit channels for color values
|
||||
/*
|
||||
const swatch = document.createElement('div');
|
||||
@@ -115,7 +112,7 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
|
||||
to,
|
||||
Decoration.mark({
|
||||
// attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` },
|
||||
attributes: { style },
|
||||
attributes: { style: `outline: solid 2px ${color}` },
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -127,12 +124,3 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
|
||||
});
|
||||
|
||||
export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights];
|
||||
|
||||
export const isPatternHighlightingEnabled = (on, config) => {
|
||||
on &&
|
||||
config &&
|
||||
setTimeout(() => {
|
||||
updateMiniLocations(config.editor, config.miniLocations);
|
||||
}, 100);
|
||||
return on ? Prec.highest(highlightExtension) : [];
|
||||
};
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
export let html = (string) => {
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = string.trim();
|
||||
return template.content.childNodes;
|
||||
};
|
||||
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,7 +1,3 @@
|
||||
export * from './codemirror.mjs';
|
||||
export * from './highlight.mjs';
|
||||
export * from './flash.mjs';
|
||||
export * from './slider.mjs';
|
||||
export * from './themes.mjs';
|
||||
export * from './widget.mjs';
|
||||
export { Vim } from './keybindings.mjs';
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
import { defaultKeymap } from '@codemirror/commands';
|
||||
import { Prec, EditorState } from '@codemirror/state';
|
||||
import { keymap, ViewPlugin } from '@codemirror/view';
|
||||
// import { searchKeymap } from '@codemirror/search';
|
||||
import { emacs } from '@replit/codemirror-emacs';
|
||||
import { vim, Vim } from '@replit/codemirror-vim';
|
||||
// import { vim } from './vim_test.mjs';
|
||||
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
||||
import { logger } from '@strudel/core';
|
||||
|
||||
const vscodePlugin = ViewPlugin.fromClass(
|
||||
class {
|
||||
constructor() {}
|
||||
},
|
||||
{
|
||||
provide: () => {
|
||||
return Prec.highest(keymap.of([...vscodeKeymap]));
|
||||
},
|
||||
},
|
||||
);
|
||||
const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
|
||||
|
||||
// Map Vim :w to trigger the same action as evaluation. We dispatch a custom
|
||||
// event 'repl-evaluate' that the editor listens for, and also simulate
|
||||
// Ctrl+Enter/Alt+Enter as a fallback. We log to the Strudel logger so it
|
||||
// appears in the Console panel.
|
||||
try {
|
||||
if (Vim && typeof Vim.defineEx === 'function') {
|
||||
// Map gc to toggle line comments by dispatching a custom event that our
|
||||
// CodeMirror integration listens to. This avoids depending on Vim's
|
||||
// internal actions and works with current selections/visual mode.
|
||||
try {
|
||||
Vim.defineAction('strudelToggleComment', (cm) => {
|
||||
const view = cm?.view || cm;
|
||||
try {
|
||||
const ev = new CustomEvent('repl-toggle-comment', { detail: { source: 'vim', view }, cancelable: true });
|
||||
document.dispatchEvent(ev);
|
||||
} catch (e) {
|
||||
console.error('strudelToggleComment dispatch failed', e);
|
||||
}
|
||||
});
|
||||
Vim.mapCommand('gc', 'action', 'strudelToggleComment', {}, { context: 'normal' });
|
||||
Vim.mapCommand('gc', 'action', 'strudelToggleComment', {}, { context: 'visual' });
|
||||
} catch (e) {
|
||||
console.error('Vim gc mapping failed', e);
|
||||
}
|
||||
|
||||
// :q to pause/stop
|
||||
Vim.defineEx('quit', 'q', (cm) => {
|
||||
try {
|
||||
const view = cm?.view || cm;
|
||||
// First try dispatching our custom stop event, then fallback to Alt+.
|
||||
let handled = false;
|
||||
try {
|
||||
const ev = new CustomEvent('repl-stop', { detail: { source: 'vim', view }, cancelable: true });
|
||||
handled = document.dispatchEvent(ev) === false;
|
||||
} catch (e) {
|
||||
console.error('Error dispatching repl-stop event', e);
|
||||
}
|
||||
if (!handled) {
|
||||
const altDot = new KeyboardEvent('keydown', {
|
||||
key: '.',
|
||||
code: 'Period',
|
||||
altKey: true,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
view?.dom?.dispatchEvent?.(altDot);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error dispatching :q stop event', e);
|
||||
}
|
||||
});
|
||||
|
||||
// :w to evaluate
|
||||
Vim.defineEx('write', 'w', (cm) => {
|
||||
const view = cm?.view || cm; // CM6 Vim passes either an object with view or the view itself
|
||||
try {
|
||||
view?.focus?.();
|
||||
// Let the app know this came from Vim :w
|
||||
try {
|
||||
logger('[vim] :w — evaluating code');
|
||||
} catch (e) {
|
||||
console.error('Error logging Vim :w evaluation', e);
|
||||
}
|
||||
// Dispatch a dedicated evaluate event first
|
||||
let handled = false;
|
||||
try {
|
||||
const ev = new CustomEvent('repl-evaluate', { detail: { source: 'vim', view }, cancelable: true });
|
||||
handled = document.dispatchEvent(ev) === false; // false means preventDefault was called
|
||||
} catch (e) {
|
||||
console.error('Error dispatching repl-evaluate event', e);
|
||||
}
|
||||
if (handled) {
|
||||
return;
|
||||
}
|
||||
// Try Ctrl+Enter first if not handled by custom event
|
||||
const ctrlEnter = new KeyboardEvent('keydown', {
|
||||
key: 'Enter',
|
||||
code: 'Enter',
|
||||
ctrlKey: true,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
view?.dom?.dispatchEvent?.(ctrlEnter);
|
||||
// If not handled (no handler called preventDefault), try Alt+Enter as
|
||||
// fallback
|
||||
if (!ctrlEnter.defaultPrevented) {
|
||||
const altEnter = new KeyboardEvent('keydown', {
|
||||
key: 'Enter',
|
||||
code: 'Enter',
|
||||
altKey: true,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
view?.dom?.dispatchEvent?.(altEnter);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error dispatching :w evaluation event', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Vim ex command setup failed (defineEx missing or Vim unavailable)', e);
|
||||
}
|
||||
|
||||
const keymaps = {
|
||||
vim,
|
||||
emacs,
|
||||
codemirror: () => keymap.of(defaultKeymap),
|
||||
vscode: vscodeExtension,
|
||||
};
|
||||
|
||||
export { Vim } from '@replit/codemirror-vim';
|
||||
|
||||
export function keybindings(name) {
|
||||
const active = keymaps[name];
|
||||
const extensions = active ? [Prec.high(active())] : [];
|
||||
if (name === 'vim') {
|
||||
extensions.push(EditorState.allowMultipleSelections.of(true));
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { EditorSelection } from '@codemirror/state';
|
||||
import { SearchCursor } from '@codemirror/search';
|
||||
|
||||
export function jumpToCharacter(view, character, direction = 1) {
|
||||
const { state, dispatch } = view;
|
||||
const pos = state.selection.main.head;
|
||||
const cursor = new SearchCursor(state.doc, character);
|
||||
|
||||
let characterPositions = [];
|
||||
let jumpPos;
|
||||
while (!cursor.next().done) {
|
||||
characterPositions.push(cursor.value.to);
|
||||
}
|
||||
if (!characterPositions.length) {
|
||||
return false;
|
||||
}
|
||||
if (direction > 0) {
|
||||
jumpPos = characterPositions.find((x) => x > pos + 1) ?? characterPositions.at(0); // Loop back around for convenience
|
||||
} else {
|
||||
jumpPos = characterPositions.reverse().find((x) => x < pos + 1) ?? characterPositions.at(0);
|
||||
}
|
||||
|
||||
if (jumpPos == null) {
|
||||
return false;
|
||||
}
|
||||
dispatch({
|
||||
selection: EditorSelection.cursor(jumpPos - 1),
|
||||
scrollIntoView: true,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "@strudel/codemirror",
|
||||
"version": "1.3.0",
|
||||
"version": "0.8.4",
|
||||
"description": "Codemirror Extensions for Strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.mjs"
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
@@ -13,7 +14,7 @@
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://codeberg.org/uzu/strudel.git"
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tidalcycles",
|
||||
@@ -28,34 +29,19 @@
|
||||
],
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://codeberg.org/uzu/strudel/issues"
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://codeberg.org/uzu/strudel#readme",
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.18.4",
|
||||
"@codemirror/commands": "^6.8.0",
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"@codemirror/language": "^6.10.8",
|
||||
"@codemirror/search": "^6.5.8",
|
||||
"@codemirror/state": "^6.5.1",
|
||||
"@codemirror/view": "^6.36.2",
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@nanostores/persistent": "^0.10.2",
|
||||
"@replit/codemirror-emacs": "^6.1.0",
|
||||
"@replit/codemirror-vim": "^6.3.0",
|
||||
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
||||
"@strudel/core": "workspace:*",
|
||||
"@strudel/draw": "workspace:*",
|
||||
"@strudel/tonal": "workspace:*",
|
||||
"@strudel/transpiler": "workspace:*",
|
||||
"@tonaljs/tonal": "^4.10.0",
|
||||
"nanostores": "^0.11.3",
|
||||
"superdough": "workspace:*"
|
||||
"@codemirror/commands": "^6.2.4",
|
||||
"@codemirror/lang-javascript": "^6.1.7",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/state": "^6.2.0",
|
||||
"@codemirror/view": "^6.10.0",
|
||||
"@lezer/highlight": "^1.1.4",
|
||||
"@strudel.cycles/core": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^6.0.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
"vite": "^4.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
import { ref, pure } from '@strudel/core';
|
||||
import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view';
|
||||
import { StateEffect } from '@codemirror/state';
|
||||
|
||||
export let sliderValues = {};
|
||||
const getSliderID = (from) => `slider_${from}`;
|
||||
|
||||
export class SliderWidget extends WidgetType {
|
||||
constructor(value, min, max, from, to, step, view) {
|
||||
super();
|
||||
this.value = value;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.from = from;
|
||||
this.originalFrom = from;
|
||||
this.to = to;
|
||||
this.step = step;
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
eq() {
|
||||
return false;
|
||||
}
|
||||
|
||||
toDOM() {
|
||||
let wrap = document.createElement('span');
|
||||
wrap.setAttribute('aria-hidden', 'true');
|
||||
wrap.className = 'cm-slider'; // inline-flex items-center
|
||||
let slider = wrap.appendChild(document.createElement('input'));
|
||||
slider.type = 'range';
|
||||
slider.min = this.min;
|
||||
slider.max = this.max;
|
||||
slider.step = this.step ?? (this.max - this.min) / 1000;
|
||||
slider.originalValue = this.value;
|
||||
// to make sure the code stays in sync, let's save the original value
|
||||
// becuase .value automatically clamps values so it'll desync with the code
|
||||
slider.value = slider.originalValue;
|
||||
slider.from = this.from;
|
||||
slider.originalFrom = this.originalFrom;
|
||||
slider.to = this.to;
|
||||
slider.style = 'width:64px;margin-right:4px;transform:translateY(4px)';
|
||||
this.slider = slider;
|
||||
slider.addEventListener('input', (e) => {
|
||||
const next = e.target.value;
|
||||
let insert = next;
|
||||
//let insert = next.toFixed(2);
|
||||
const to = slider.from + slider.originalValue.length;
|
||||
let change = { from: slider.from, to, insert };
|
||||
slider.originalValue = insert;
|
||||
slider.value = insert;
|
||||
this.view.dispatch({ changes: change });
|
||||
const id = getSliderID(slider.originalFrom); // matches id generated in transpiler
|
||||
window.postMessage({ type: 'cm-slider', value: Number(next), id });
|
||||
});
|
||||
return wrap;
|
||||
}
|
||||
|
||||
ignoreEvent(e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export const setSliderWidgets = StateEffect.define();
|
||||
|
||||
export const updateSliderWidgets = (view, widgets) => {
|
||||
view.dispatch({ effects: setSliderWidgets.of(widgets) });
|
||||
};
|
||||
|
||||
function getSliders(widgetConfigs, view) {
|
||||
return widgetConfigs
|
||||
.filter((w) => w.type === 'slider')
|
||||
.map(({ from, to, value, min, max, step }) => {
|
||||
return Decoration.widget({
|
||||
widget: new SliderWidget(value, min, max, from, to, step, view),
|
||||
side: 0,
|
||||
}).range(from /* , to */);
|
||||
});
|
||||
}
|
||||
|
||||
export const sliderPlugin = ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations; //: DecorationSet
|
||||
|
||||
constructor(view /* : EditorView */) {
|
||||
this.decorations = Decoration.set([]);
|
||||
}
|
||||
|
||||
update(update /* : ViewUpdate */) {
|
||||
update.transactions.forEach((tr) => {
|
||||
if (tr.docChanged) {
|
||||
this.decorations = this.decorations.map(tr.changes);
|
||||
const iterator = this.decorations.iter();
|
||||
while (iterator.value) {
|
||||
// when the widgets are moved, we need to tell the dom node the current position
|
||||
// this is important because the updateSliderValue function has to work with the dom node
|
||||
if (iterator.value?.widget?.slider) {
|
||||
iterator.value.widget.slider.from = iterator.from;
|
||||
iterator.value.widget.slider.to = iterator.to;
|
||||
}
|
||||
iterator.next();
|
||||
}
|
||||
}
|
||||
for (let e of tr.effects) {
|
||||
if (e.is(setSliderWidgets)) {
|
||||
this.decorations = Decoration.set(getSliders(e.value, update.view));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
decorations: (v) => v.decorations,
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 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) => {
|
||||
console.warn('slider will only work when the transpiler is used... passing value as is');
|
||||
return pure(value);
|
||||
};
|
||||
// function transpiled from slider = (value, min, max)
|
||||
export let sliderWithID = (id, value, min, max) => {
|
||||
sliderValues[id] = value; // sync state at eval time (code -> state)
|
||||
return ref(() => sliderValues[id]); // use state at query time
|
||||
};
|
||||
// update state when sliders are moved
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('message', (e) => {
|
||||
if (e.data.type === 'cm-slider') {
|
||||
if (sliderValues[e.data.id] !== undefined) {
|
||||
// update state when slider is moved
|
||||
sliderValues[e.data.id] = e.data.value;
|
||||
} else {
|
||||
console.warn(`slider with id "${e.data.id}" is not registered. Only ${Object.keys(sliderValues)}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Vendored
-218
@@ -1,218 +0,0 @@
|
||||
import strudelTheme, { settings as strudelThemeSettings } from './themes/strudel-theme.mjs';
|
||||
import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen.mjs';
|
||||
import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen.mjs';
|
||||
import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen.mjs';
|
||||
import teletext, { settings as teletextSettings } from './themes/teletext.mjs';
|
||||
import algoboy, { settings as algoboySettings } from './themes/algoboy.mjs';
|
||||
import CutiePi, { settings as CutiePiSettings } from './themes/CutiePi.mjs';
|
||||
import sonicPink, { settings as sonicPinkSettings } from './themes/sonic-pink.mjs';
|
||||
import redText, { settings as redTextSettings } from './themes/red-text.mjs';
|
||||
import greenText, { settings as greenTextSettings } from './themes/green-text.mjs';
|
||||
import archBtw, { settings as archBtwSettings } from './themes/archBtw.mjs';
|
||||
import fruitDaw, { settings as fruitDawSettings } from './themes/fruitDaw.mjs';
|
||||
|
||||
import bluescreenlight, { settings as bluescreenlightsettings } from './themes/bluescreenlight.mjs';
|
||||
|
||||
import androidstudio, { settings as androidstudioSettings } from './themes/androidstudio.mjs';
|
||||
import atomone, { settings as atomOneSettings } from './themes/atomone.mjs';
|
||||
import aura, { settings as auraSettings } from './themes/aura.mjs';
|
||||
import darcula, { settings as darculaSettings } from './themes/darcula.mjs';
|
||||
import dracula, { settings as draculaSettings } from './themes/dracula.mjs';
|
||||
import duotoneDark, { settings as duotoneDarkSettings } from './themes/duotoneDark.mjs';
|
||||
import eclipse, { settings as eclipseSettings } from './themes/eclipse.mjs';
|
||||
import githubDark, { settings as githubDarkSettings } from './themes/githubDark.mjs';
|
||||
import githubLight, { settings as githubLightSettings } from './themes/githubLight.mjs';
|
||||
import gruvboxDark, { settings as gruvboxDarkSettings } from './themes/gruvboxDark.mjs';
|
||||
import gruvboxLight, { settings as gruvboxLightSettings } from './themes/gruvboxLight.mjs';
|
||||
import materialDark, { settings as materialDarkSettings } from './themes/materialDark.mjs';
|
||||
import materialLight, { settings as materialLightSettings } from './themes/materialLight.mjs';
|
||||
import nord, { settings as nordSettings } from './themes/nord.mjs';
|
||||
import monokai, { settings as monokaiSettings } from './themes/monokai.mjs';
|
||||
import solarizedDark, { settings as solarizedDarkSettings } from './themes/solarizedDark.mjs';
|
||||
import solarizedLight, { settings as solarizedLightSettings } from './themes/solarizedLight.mjs';
|
||||
import sublime, { settings as sublimeSettings } from './themes/sublime.mjs';
|
||||
import tokyoNight, { settings as tokyoNightSettings } from './themes/tokyoNight.mjs';
|
||||
import tokyoNightStorm, { settings as tokyoNightStormSettings } from './themes/tokioNightStorm.mjs';
|
||||
import tokyoNightDay, { settings as tokyoNightDaySettings } from './themes/tokyoNightDay.mjs';
|
||||
import vscodeDark, { settings as vscodeDarkSettings } from './themes/vscodeDark.mjs';
|
||||
import vscodeLight, { settings as vscodeLightSettings } from './themes/vscodeLight.mjs';
|
||||
import xcodeLight, { settings as xcodeLightSettings } from './themes/xcodeLight.mjs';
|
||||
import bbedit, { settings as bbeditSettings } from './themes/bbedit.mjs';
|
||||
import noctisLilac, { settings as noctisLilacSettings } from './themes/noctisLilac.mjs';
|
||||
|
||||
import { setTheme } from '@strudel/draw';
|
||||
export const themes = {
|
||||
strudelTheme,
|
||||
algoboy,
|
||||
archBtw,
|
||||
androidstudio,
|
||||
atomone,
|
||||
aura,
|
||||
bbedit,
|
||||
blackscreen,
|
||||
bluescreen,
|
||||
bluescreenlight,
|
||||
CutiePi,
|
||||
darcula,
|
||||
dracula,
|
||||
duotoneDark,
|
||||
eclipse,
|
||||
fruitDaw,
|
||||
githubDark,
|
||||
githubLight,
|
||||
greenText,
|
||||
gruvboxDark,
|
||||
gruvboxLight,
|
||||
sonicPink,
|
||||
materialDark,
|
||||
materialLight,
|
||||
monokai,
|
||||
noctisLilac,
|
||||
nord,
|
||||
redText,
|
||||
solarizedDark,
|
||||
solarizedLight,
|
||||
sublime,
|
||||
teletext,
|
||||
tokyoNight,
|
||||
tokyoNightDay,
|
||||
tokyoNightStorm,
|
||||
vscodeDark,
|
||||
vscodeLight,
|
||||
whitescreen,
|
||||
xcodeLight,
|
||||
};
|
||||
|
||||
export const settings = {
|
||||
strudelTheme: strudelThemeSettings,
|
||||
bluescreen: bluescreenSettings,
|
||||
bluescreenlight: bluescreenlightsettings,
|
||||
blackscreen: blackscreenSettings,
|
||||
whitescreen: whitescreenSettings,
|
||||
teletext: teletextSettings,
|
||||
algoboy: algoboySettings,
|
||||
archBtw: archBtwSettings,
|
||||
androidstudio: androidstudioSettings,
|
||||
atomone: atomOneSettings,
|
||||
aura: auraSettings,
|
||||
bbedit: bbeditSettings,
|
||||
darcula: darculaSettings,
|
||||
dracula: draculaSettings,
|
||||
duotoneDark: duotoneDarkSettings,
|
||||
eclipse: eclipseSettings,
|
||||
CutiePi: CutiePiSettings,
|
||||
sonicPink: sonicPinkSettings,
|
||||
fruitDaw: fruitDawSettings,
|
||||
githubLight: githubLightSettings,
|
||||
githubDark: githubDarkSettings,
|
||||
greenText: greenTextSettings,
|
||||
|
||||
gruvboxDark: gruvboxDarkSettings,
|
||||
gruvboxLight: gruvboxLightSettings,
|
||||
materialDark: materialDarkSettings,
|
||||
materialLight: materialLightSettings,
|
||||
noctisLilac: noctisLilacSettings,
|
||||
nord: nordSettings,
|
||||
monokai: monokaiSettings,
|
||||
redText: redTextSettings,
|
||||
solarizedLight: solarizedLightSettings,
|
||||
solarizedDark: solarizedDarkSettings,
|
||||
sublime: sublimeSettings,
|
||||
tokyoNight: tokyoNightSettings,
|
||||
tokyoNightStorm: tokyoNightStormSettings,
|
||||
vscodeDark: vscodeDarkSettings,
|
||||
vscodeLight: vscodeLightSettings,
|
||||
xcodeLight: xcodeLightSettings,
|
||||
tokyoNightDay: tokyoNightDaySettings,
|
||||
};
|
||||
|
||||
function getColors(str) {
|
||||
const colorRegex = /#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})/g;
|
||||
const colors = [];
|
||||
|
||||
let match;
|
||||
while ((match = colorRegex.exec(str)) !== null) {
|
||||
const color = match[0];
|
||||
if (!colors.includes(color)) {
|
||||
colors.push(color);
|
||||
}
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
// TODO: remove
|
||||
export function themeColors(theme) {
|
||||
return getColors(stringifySafe(theme));
|
||||
}
|
||||
|
||||
function getCircularReplacer() {
|
||||
const seen = new WeakSet();
|
||||
return (key, value) => {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (seen.has(value)) {
|
||||
return;
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
function stringifySafe(json) {
|
||||
return JSON.stringify(json, getCircularReplacer());
|
||||
}
|
||||
|
||||
export const theme = (theme) => themes[theme] || themes.strudelTheme;
|
||||
|
||||
// css style injection helpers
|
||||
export function injectStyle(rule) {
|
||||
const newStyle = document.createElement('style');
|
||||
document.head.appendChild(newStyle);
|
||||
const styleSheet = newStyle.sheet;
|
||||
const ruleIndex = styleSheet.insertRule(rule, 0);
|
||||
return () => styleSheet.deleteRule(ruleIndex);
|
||||
}
|
||||
|
||||
let currentTheme,
|
||||
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')}
|
||||
}`;
|
||||
setTheme(themeSettings);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Cutie Pi
|
||||
* by Switch Angel
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
const deepPurple = '#5c019a';
|
||||
const yellowPink = '#fbeffc';
|
||||
const grey = '#272C35';
|
||||
const pinkAccent = '#fee1ff';
|
||||
const lightGrey = '#465063';
|
||||
const bratGreen = '#9acd3f';
|
||||
const lighterGrey = '#97a1b7';
|
||||
const pink = '#f6a6fd';
|
||||
|
||||
export const settings = {
|
||||
background: 'white',
|
||||
lineBackground: 'transparent',
|
||||
foreground: deepPurple,
|
||||
caret: '#797977',
|
||||
selection: yellowPink,
|
||||
selectionMatch: '#2B323D',
|
||||
gutterBackground: grey,
|
||||
gutterForeground: lightGrey,
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: pinkAccent,
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings,
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: deepPurple,
|
||||
},
|
||||
{ tag: [t.tagName, t.heading], color: settings.foreground },
|
||||
{ tag: t.comment, color: lighterGrey },
|
||||
{ tag: [t.variableName, t.propertyName, t.labelName], color: pink },
|
||||
{ tag: [t.attributeName, t.number], color: '#d19a66' },
|
||||
{ tag: t.className, color: grey },
|
||||
{ tag: t.keyword, color: deepPurple },
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: bratGreen },
|
||||
],
|
||||
});
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
const palettes = {
|
||||
// https://www.deviantart.com/advancedfan2020/art/Game-Boy-Palette-Set-Color-HEX-Part-09-920495662
|
||||
'Central Florida A': ['#FFF630', '#B3AC22', '#666213', '#191905'],
|
||||
'Central Florida B': ['#38CEBA', '#279082', '#16524A', '#061513'],
|
||||
'Central Florida C': ['#FF8836', '#B35F26', '#663616', '#190E05'],
|
||||
'Central Florida D': ['#E07070', '#9D4E4E', '#5A2D2D', '#160B0B'],
|
||||
'Central Florida E': ['#7AA4CB', '#55738E', '#314251', '#0C1014'],
|
||||
'Feminine Energy A': ['#DC5686', '#9A415E', '#582536', '#16090D'],
|
||||
'Feminine Energy B': ['#D0463C', '#92312A', '#531c18', '#150706'],
|
||||
'Feminine Energy C': ['#D86918', '#974A11', '#562A0A', '#160A02'],
|
||||
'Feminine Energy D': ['#EFC54F', '#A78A36', '#604F20', '#181408'],
|
||||
'Feminine Energy E': ['#866399', '#5e456b', '#36283d', '#0d0a0f'],
|
||||
'Sour Watermelon A': ['#993366', '#6B2447', '#3D1429', '#0F050A'],
|
||||
'Sour Watermelon B': ['#996666', '#6B4747', '#3D2929', '#0F0A0A'],
|
||||
'Sour Watermelon C': ['#999966', '#686B47', '#3d3d29', '#0f0f0A'],
|
||||
'Sour Watermelon D': ['#99cc66', '#6b8f47', '#3d5229', '#0f140a'],
|
||||
'Sour Watermelon E': ['#99ff66', '#6bb347', '#3d6629', '#0f190a'],
|
||||
//https://www.deviantart.com/advancedfan2020/art/Game-Boy-Palette-Set-Color-HEX-Part-02-920073260
|
||||
'Peri Peaceful A': ['#909BE9', '#656DA3', '#3A3E5D', '#0e0f17'],
|
||||
'Peri Peaceful B': ['#68628d', '#494563', '#2a2738', '#0a0a0e'], // pretty dim
|
||||
'Peri Peaceful E': ['#b5a0a9', '#7f7076', '#484044', '#121011'],
|
||||
'Hichem Palette B': ['#4fa3a5', '#377273', '#204142', '#081010'],
|
||||
'Hichem Palette C': ['#Fe6f9b', '#b24e6d', '#662c3e', '#190b0f'],
|
||||
'Hichem Palette D': ['#ffbb5a', '#b3833f', '#664b24', '#191309'],
|
||||
'JSR2 A': ['#E0EFC0', '#9da786', '#5a604d', '#161813'],
|
||||
};
|
||||
const palette = palettes['Sour Watermelon B'];
|
||||
export const settings = {
|
||||
background: palette[3],
|
||||
foreground: palette[1],
|
||||
caret: palette[0],
|
||||
selection: palette[0],
|
||||
selectionMatch: palette[1],
|
||||
lineHighlight: palette[3],
|
||||
lineBackground: palette[3] + '90',
|
||||
//lineBackground: 'transparent',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: palette[0],
|
||||
light: false,
|
||||
// customStyle: '.cm-line { line-height: 1 }',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.comment, color: palette[2] },
|
||||
{ tag: t.string, color: palette[1] },
|
||||
{ tag: [t.atom, t.number], color: palette[1] },
|
||||
{ tag: [t.meta, t.labelName, t.variableName], color: palette[0] },
|
||||
{
|
||||
tag: [t.keyword, t.tagName, t.arithmeticOperator],
|
||||
color: palette[1],
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: palette[0] },
|
||||
{ tag: [t.function(t.variableName), t.propertyName], color: palette[0] },
|
||||
{ tag: t.atom, color: palette[1] },
|
||||
],
|
||||
});
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* androidstudio
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#282b2e',
|
||||
lineBackground: '#282b2e99',
|
||||
foreground: '#a9b7c6',
|
||||
caret: '#00FF00',
|
||||
selection: '#343739',
|
||||
selectionMatch: '#343739',
|
||||
lineHighlight: '#343739',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#282b2e',
|
||||
foreground: '#a9b7c6',
|
||||
caret: '#00FF00',
|
||||
selection: '#4e5254',
|
||||
selectionMatch: '#4e5254',
|
||||
gutterForeground: '#cccccc50',
|
||||
lineHighlight: '#7f85891f',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.labelName, color: 'inherit' },
|
||||
{ tag: [t.keyword, t.deleted, t.className], color: '#a9b7c6' },
|
||||
{ tag: [t.number, t.literal], color: '#6897bb' },
|
||||
//{ tag: [t.link, t.variableName], color: '#629755' },
|
||||
{ tag: [t.link, t.variableName], color: '#a9b7c6' },
|
||||
{ tag: [t.comment, t.quote], color: 'grey' },
|
||||
{ tag: [t.meta, t.documentMeta], color: '#bbb529' },
|
||||
//{ tag: [t.string, t.propertyName, t.attributeValue], color: '#6a8759' },
|
||||
{ tag: [t.propertyName, t.attributeValue], color: '#a9b7c6' },
|
||||
{ tag: [t.string], color: '#6a8759' },
|
||||
{ tag: [t.heading, t.typeName], color: '#ffc66d' },
|
||||
{ tag: [t.attributeName], color: '#a9b7c6' },
|
||||
{ tag: [t.emphasis], fontStyle: 'italic' },
|
||||
],
|
||||
});
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Arch Btw
|
||||
* Modern terminal inspired theme
|
||||
* made by Jade
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
const hex = ['rgb(0, 0, 0)', 'rgb(82, 208, 250)', 'rgba(113, 208, 250, .4)', 'rgba(113, 208, 250, .15)'];
|
||||
|
||||
export const settings = {
|
||||
background: hex[0],
|
||||
lineBackground: 'transparent',
|
||||
foreground: hex[1],
|
||||
selection: hex[2],
|
||||
selectionMatch: hex[0],
|
||||
gutterBackground: hex[0],
|
||||
gutterForeground: hex[2],
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: hex[0],
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: hex[1],
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[1] },
|
||||
{ tag: [t.comment, t.brace, t.bracket], color: hex[2] },
|
||||
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[1] },
|
||||
{ tag: [t.attributeName, t.number], color: hex[1] },
|
||||
{ tag: t.keyword, color: hex[1] },
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[1] },
|
||||
],
|
||||
});
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Atom One
|
||||
* Atom One dark syntax theme
|
||||
*
|
||||
* https://github.com/atom/one-dark-syntax
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#272C35',
|
||||
lineBackground: '#272C3599',
|
||||
foreground: 'hsl(220, 14%, 71%)',
|
||||
caret: '#797977',
|
||||
selection: '#ffffff30',
|
||||
selectionMatch: '#2B323D',
|
||||
gutterBackground: '#272C35',
|
||||
gutterForeground: '#465063',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#2B323D',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#272C35',
|
||||
foreground: '#9d9b97',
|
||||
caret: '#797977',
|
||||
selection: '#3d4c64',
|
||||
selectionMatch: '#3d4c64',
|
||||
gutterBackground: '#272C35',
|
||||
gutterForeground: '#465063',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#2e3f5940',
|
||||
},
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: 'hsl(207, 82%, 66%)',
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: 'hsl( 29, 54%, 61%)' },
|
||||
{ tag: [t.tagName, t.heading], color: '#e06c75' },
|
||||
{ tag: t.comment, color: '#54636D' },
|
||||
{ tag: [t.variableName, t.propertyName, t.labelName], color: 'hsl(220, 14%, 71%)' },
|
||||
{ tag: [t.attributeName, t.number], color: 'hsl( 29, 54%, 61%)' },
|
||||
{ tag: t.className, color: 'hsl( 39, 67%, 69%)' },
|
||||
{ tag: t.keyword, color: 'hsl(286, 60%, 67%)' },
|
||||
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: '#98c379' },
|
||||
],
|
||||
});
|
||||
Vendored
-51
@@ -1,51 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#21202e',
|
||||
lineBackground: '#21202e99',
|
||||
foreground: '#edecee',
|
||||
caret: '#a277ff',
|
||||
selection: '#3d375e7f',
|
||||
selectionMatch: '#3d375e7f',
|
||||
gutterBackground: '#21202e',
|
||||
gutterForeground: '#edecee',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#a394f033',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#21202e',
|
||||
foreground: '#edecee',
|
||||
caret: '#a277ff',
|
||||
selection: '#5a51898f',
|
||||
selectionMatch: '#5a51898f',
|
||||
gutterBackground: '#21202e',
|
||||
gutterForeground: '#edecee',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#a394f033',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#a277ff' },
|
||||
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#edecee' },
|
||||
{ tag: [t.propertyName], color: '#ffca85' },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#61ffca' },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: '#ffca85' },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#61ffca' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#edecee' },
|
||||
{ tag: [t.className], color: '#82e2ff' },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#61ffca' },
|
||||
{ tag: [t.typeName], color: '#82e2ff' },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: '#a277ff' },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#61ffca' },
|
||||
{ tag: [t.meta, t.comment], color: '#6d6d6d' },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#a277ff' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#edecee' },
|
||||
{ tag: t.invalid, color: '#ff6767' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
],
|
||||
});
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#FFFFFF',
|
||||
lineBackground: '#FFFFFF99',
|
||||
foreground: '#000000',
|
||||
caret: '#FBAC52',
|
||||
selection: '#FFD420',
|
||||
selectionMatch: '#FFD420',
|
||||
gutterBackground: '#f5f5f5',
|
||||
gutterForeground: '#4D4D4C',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#00000012',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#FFFFFF',
|
||||
foreground: '#000000',
|
||||
caret: '#FBAC52',
|
||||
selection: '#FFD420',
|
||||
selectionMatch: '#FFD420',
|
||||
gutterBackground: '#f5f5f5',
|
||||
gutterForeground: '#4D4D4C',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#00000012',
|
||||
},
|
||||
styles: [
|
||||
{ tag: [t.meta, t.comment], color: '#804000' },
|
||||
{ tag: [t.keyword, t.strong], color: '#0000FF' },
|
||||
{ tag: [t.number], color: '#FF0080' },
|
||||
{ tag: [t.string], color: '#FF0080' },
|
||||
{ tag: [t.variableName], color: '#006600' },
|
||||
{ tag: [t.escape], color: '#33CC33' },
|
||||
{ tag: [t.tagName], color: '#1C02FF' },
|
||||
{ tag: [t.heading], color: '#0C07FF' },
|
||||
{ tag: [t.quote], color: '#000000' },
|
||||
{ tag: [t.list], color: '#B90690' },
|
||||
{ tag: [t.documentMeta], color: '#888888' },
|
||||
{ tag: [t.function(t.variableName)], color: '#0000A2' },
|
||||
{ tag: [t.definition(t.typeName), t.typeName], color: '#6D79DE' },
|
||||
],
|
||||
});
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
export const settings = {
|
||||
background: 'black',
|
||||
foreground: 'white', // whats that?
|
||||
caret: 'white',
|
||||
selection: '#ffffff20',
|
||||
selectionMatch: '#036dd626',
|
||||
lineHighlight: '#ffffff10',
|
||||
lineBackground: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.labelName, color: 'inherit' },
|
||||
{ tag: t.keyword, color: 'inherit' },
|
||||
{ tag: t.operator, color: 'inherit' },
|
||||
{ tag: t.special(t.variableName), color: 'inherit' },
|
||||
{ tag: t.typeName, color: 'inherit' },
|
||||
{ tag: t.atom, color: 'inherit' },
|
||||
{ tag: t.number, color: 'inherit' },
|
||||
{ tag: t.definition(t.variableName), color: 'inherit' },
|
||||
{ tag: t.string, color: 'inherit' },
|
||||
{ tag: t.special(t.string), color: 'inherit' },
|
||||
{ tag: t.comment, color: 'inherit' },
|
||||
{ tag: t.variableName, color: 'inherit' },
|
||||
{ tag: t.tagName, color: 'inherit' },
|
||||
{ tag: t.bracket, color: 'inherit' },
|
||||
{ tag: t.meta, color: 'inherit' },
|
||||
{ tag: t.attributeName, color: 'inherit' },
|
||||
{ tag: t.propertyName, color: 'inherit' },
|
||||
{ tag: t.className, color: 'inherit' },
|
||||
{ tag: t.invalid, color: 'inherit' },
|
||||
{ tag: [t.unit, t.punctuation], color: 'inherit' },
|
||||
],
|
||||
});
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
export const settings = {
|
||||
background: '#051DB5',
|
||||
lineBackground: '#051DB550',
|
||||
foreground: 'white', // whats that?
|
||||
caret: 'white',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#036dd626',
|
||||
// lineHighlight: '#8a91991a', // original
|
||||
lineHighlight: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
// gutterForeground: '#8a919966',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.labelName, color: 'inherit' },
|
||||
{ tag: t.keyword, color: 'inherit' },
|
||||
{ tag: t.operator, color: 'inherit' },
|
||||
{ tag: t.special(t.variableName), color: 'inherit' },
|
||||
{ tag: t.typeName, color: 'inherit' },
|
||||
{ tag: t.atom, color: 'inherit' },
|
||||
{ tag: t.number, color: 'inherit' },
|
||||
{ tag: t.definition(t.variableName), color: 'inherit' },
|
||||
{ tag: t.string, color: 'inherit' },
|
||||
{ tag: t.special(t.string), color: 'inherit' },
|
||||
{ tag: t.comment, color: 'inherit' },
|
||||
{ tag: t.variableName, color: 'inherit' },
|
||||
{ tag: t.tagName, color: 'inherit' },
|
||||
{ tag: t.bracket, color: 'inherit' },
|
||||
{ tag: t.meta, color: 'inherit' },
|
||||
{ tag: t.attributeName, color: 'inherit' },
|
||||
{ tag: t.propertyName, color: 'inherit' },
|
||||
{ tag: t.className, color: 'inherit' },
|
||||
{ tag: t.invalid, color: 'inherit' },
|
||||
{ tag: [t.unit, t.punctuation], color: 'inherit' },
|
||||
],
|
||||
});
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* A lighter blue screen theme
|
||||
* made by Jade
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
const hex = ['rgb(75, 130, 247)', 'rgb(47, 108, 246)', 'rgb(255, 255, 255)', 'rgba(255, 255, 255,.3)'];
|
||||
|
||||
export const settings = {
|
||||
background: hex[0],
|
||||
lineBackground: 'transparent',
|
||||
foreground: hex[2],
|
||||
selection: hex[3],
|
||||
selectionMatch: hex[0],
|
||||
gutterBackground: hex[0],
|
||||
gutterForeground: hex[2],
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: hex[1],
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: hex[2],
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[2] },
|
||||
{ tag: [t.comment, t.bracket, t.brace, t.compareOperator], color: hex[3] },
|
||||
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[2] },
|
||||
{ tag: [t.attributeName, t.number], color: hex[2] },
|
||||
{ tag: t.keyword, color: hex[2] },
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[2] },
|
||||
],
|
||||
});
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* darcula
|
||||
* Name: IntelliJ IDEA darcula theme
|
||||
* From IntelliJ IDEA by JetBrains
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
export const settings = {
|
||||
background: '#242424',
|
||||
lineBackground: '#24242499',
|
||||
foreground: '#f8f8f2',
|
||||
caret: '#FFFFFF',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
selectionMatch: 'rgba(255, 255, 255, 0.2)',
|
||||
gutterBackground: 'rgba(255, 255, 255, 0.1)',
|
||||
gutterForeground: '#999',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#242424',
|
||||
foreground: '#f8f8f2',
|
||||
caret: '#FFFFFF',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
selectionMatch: 'rgba(255, 255, 255, 0.2)',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#999',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.labelName, color: '#CCCCCC' },
|
||||
{ tag: [t.atom, t.number], color: '#7A9EC2' },
|
||||
{ tag: [t.comment], color: '#707070' },
|
||||
{ tag: [t.string], color: '#6A8759' },
|
||||
{ tag: [t.variableName, t.operator], color: '#CCCCCC' },
|
||||
{ tag: [t.function(t.variableName), t.propertyName], color: '#FFC66D' },
|
||||
{ tag: [t.meta, t.className], color: '#FFC66D' },
|
||||
{ tag: [t.propertyName], color: '#FFC66D' },
|
||||
{ tag: [t.keyword], color: '#CC7832' },
|
||||
{ tag: [t.tagName], color: '#ff79c6' },
|
||||
{ tag: [t.typeName], color: '#ffb86c' },
|
||||
],
|
||||
});
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* @name dracula
|
||||
* Michael Kaminsky (http://github.com/mkaminsky11)
|
||||
* Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)
|
||||
*/
|
||||
// this is different from https://thememirror.net/dracula
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#282a36',
|
||||
lineBackground: '#282a3699',
|
||||
foreground: '#f8f8f2',
|
||||
caret: '#f8f8f0',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
selectionMatch: 'rgba(255, 255, 255, 0.2)',
|
||||
gutterBackground: '#282a36',
|
||||
gutterForeground: '#6272a4',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
};
|
||||
|
||||
const purple = '#BD93F9';
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#282a36',
|
||||
foreground: '#f8f8f2',
|
||||
caret: '#f8f8f0',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
selectionMatch: 'rgba(255, 255, 255, 0.2)',
|
||||
gutterBackground: '#282a36',
|
||||
gutterForeground: '#6272a4',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.comment, color: '#6272a4' },
|
||||
{ tag: t.string, color: '#f1fa8c' },
|
||||
{ tag: [t.atom, t.number], color: purple },
|
||||
{ tag: [t.meta, t.labelName, t.variableName], color: '#f8f8f2' },
|
||||
{
|
||||
tag: [t.keyword, t.tagName, t.arithmeticOperator],
|
||||
color: '#ff79c6',
|
||||
},
|
||||
{ tag: [t.function(t.variableName), t.propertyName], color: '#50fa7b' },
|
||||
{ tag: t.atom, color: '#bd93f9' },
|
||||
],
|
||||
});
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* duotone
|
||||
* author Bram de Haan
|
||||
* by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#2a2734',
|
||||
lineBackground: '#2a273499',
|
||||
foreground: '#eeebff',
|
||||
caret: '#ffad5c',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
gutterBackground: '#2a2734',
|
||||
gutterForeground: '#545167',
|
||||
lineHighlight: '#36334280',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#2a2734',
|
||||
foreground: '#6c6783',
|
||||
caret: '#ffad5c',
|
||||
selection: '#9a86fd',
|
||||
selectionMatch: '#9a86fd',
|
||||
gutterBackground: '#2a2734',
|
||||
gutterForeground: '#545167',
|
||||
lineHighlight: '#36334280',
|
||||
},
|
||||
styles: [
|
||||
{ tag: [t.comment, t.bracket, t.operator], color: '#6c6783' },
|
||||
{ tag: [t.atom, t.number, t.keyword, t.link, t.attributeName, t.quote], color: '#ffcc99' },
|
||||
{ tag: [t.emphasis, t.heading, t.tagName, t.propertyName, t.className, t.variableName], color: '#eeebff' },
|
||||
{ tag: [t.typeName, t.url], color: '#eeebff' },
|
||||
{ tag: t.string, color: '#ffb870' },
|
||||
/* { tag: [t.propertyName], color: '#9a86fd' }, */
|
||||
{ tag: [t.propertyName], color: '#eeebff' },
|
||||
{ tag: t.labelName, color: '#eeebff' },
|
||||
],
|
||||
});
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* duotone
|
||||
* author Bram de Haan
|
||||
* by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#faf8f5',
|
||||
lineBackground: '#faf8f599',
|
||||
foreground: '#b29762',
|
||||
caret: '#93abdc',
|
||||
selection: '#e3dcce',
|
||||
selectionMatch: '#e3dcce',
|
||||
gutterBackground: '#faf8f5',
|
||||
gutterForeground: '#cdc4b1',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#EFEFEF',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#faf8f5',
|
||||
foreground: '#b29762',
|
||||
caret: '#93abdc',
|
||||
selection: '#e3dcce',
|
||||
selectionMatch: '#e3dcce',
|
||||
gutterBackground: '#faf8f5',
|
||||
gutterForeground: '#cdc4b1',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#ddceb154',
|
||||
},
|
||||
styles: [
|
||||
{ tag: [t.comment, t.bracket], color: '#b6ad9a' },
|
||||
{ tag: [t.atom, t.number, t.keyword, t.link, t.attributeName, t.quote], color: '#063289' },
|
||||
{ tag: [t.emphasis, t.heading, t.tagName, t.propertyName, t.variableName], color: '#2d2006' },
|
||||
{ tag: [t.typeName, t.url, t.string], color: '#896724' },
|
||||
{ tag: [t.operator, t.string], color: '#1659df' },
|
||||
{ tag: [t.propertyName], color: '#b29762' },
|
||||
{ tag: [t.unit, t.punctuation], color: '#063289' },
|
||||
],
|
||||
});
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#fff',
|
||||
lineBackground: '#ffffff99',
|
||||
foreground: '#000',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#d7d4f0',
|
||||
selectionMatch: '#d7d4f0',
|
||||
gutterBackground: '#f7f7f7',
|
||||
gutterForeground: '#999',
|
||||
lineHighlight: '#e8f2ff',
|
||||
gutterBorder: 'transparent',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#fff',
|
||||
foreground: '#000',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#d7d4f0',
|
||||
selectionMatch: '#d7d4f0',
|
||||
gutterBackground: '#f7f7f7',
|
||||
gutterForeground: '#999',
|
||||
lineHighlight: '#006fff1c',
|
||||
gutterBorder: 'transparent',
|
||||
},
|
||||
styles: [
|
||||
{ tag: [t.comment], color: '#3F7F5F' },
|
||||
{ tag: [t.documentMeta], color: '#FF1717' },
|
||||
{ tag: t.keyword, color: '#7F0055', fontWeight: 'bold' },
|
||||
{ tag: t.atom, color: '#00f' },
|
||||
{ tag: t.number, color: '#164' },
|
||||
{ tag: t.propertyName, color: '#164' },
|
||||
{ tag: [t.variableName, t.definition(t.variableName)], color: '#0000C0' },
|
||||
{ tag: t.function(t.variableName), color: '#0000C0' },
|
||||
{ tag: t.string, color: '#2A00FF' },
|
||||
{ tag: t.operator, color: 'black' },
|
||||
{ tag: t.tagName, color: '#170' },
|
||||
{ tag: t.attributeName, color: '#00c' },
|
||||
{ tag: t.link, color: '#219' },
|
||||
],
|
||||
});
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Fruit Daw
|
||||
* made by Jade
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
const hex = [
|
||||
'rgb(84, 93, 98)',
|
||||
'rgb(255, 255, 255)',
|
||||
'rgba(255, 255, 255, .25)',
|
||||
'rgb(67, 76, 81)',
|
||||
'rgb(186, 230, 115)',
|
||||
'rgb(252, 184, 67)',
|
||||
'rgb(124, 206, 254)',
|
||||
'rgb(83, 101, 102)',
|
||||
'rgba(46, 62, 72,.5)',
|
||||
'rgb(94, 100, 108)',
|
||||
'rgb(167, 216, 177)',
|
||||
];
|
||||
|
||||
export const settings = {
|
||||
background: hex[0],
|
||||
lineBackground: 'transparent',
|
||||
foreground: hex[10],
|
||||
selection: hex[8],
|
||||
selectionMatch: hex[0],
|
||||
gutterBackground: hex[3],
|
||||
gutterForeground: hex[2],
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: hex[3],
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: hex[1],
|
||||
},
|
||||
{ tag: [t.bool, t.special(t.variableName)], color: hex[1] },
|
||||
{ tag: [t.comment, t.brace, t.bracket], color: hex[2] },
|
||||
{ tag: [t.variableName], color: hex[1] },
|
||||
{ tag: [t.labelName, t.propertyName, t.self, t.atom], color: hex[5] },
|
||||
{ tag: [t.attributeName, t.number], color: hex[6] },
|
||||
{ tag: t.keyword, color: hex[5] },
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[4] },
|
||||
],
|
||||
});
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#0d1117',
|
||||
lineBackground: '#0d111799',
|
||||
foreground: '#c9d1d9',
|
||||
caret: '#c9d1d9',
|
||||
selection: '#003d73',
|
||||
selectionMatch: '#003d73',
|
||||
lineHighlight: '#36334280',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#0d1117',
|
||||
foreground: '#c9d1d9',
|
||||
caret: '#c9d1d9',
|
||||
selection: '#003d73',
|
||||
selectionMatch: '#003d73',
|
||||
lineHighlight: '#36334280',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.labelName, color: '#d2a8ff' },
|
||||
{ tag: [t.standard(t.tagName), t.tagName], color: '#7ee787' },
|
||||
{ tag: [t.comment, t.bracket], color: '#8b949e' },
|
||||
{ tag: [t.className, t.propertyName], color: '#d2a8ff' },
|
||||
{ tag: [t.variableName, t.attributeName], color: '#d2a8ff' },
|
||||
{ tag: [t.number, t.operator], color: '#79c0ff' },
|
||||
{ tag: [t.keyword, t.typeName, t.typeOperator, t.typeName], color: '#ff7b72' },
|
||||
{ tag: [t.string, t.meta, t.regexp], color: '#a5d6ff' },
|
||||
{ tag: [t.name, t.quote], color: '#7ee787' },
|
||||
{ tag: [t.heading, t.strong], color: '#d2a8ff', fontWeight: 'bold' },
|
||||
{ tag: [t.emphasis], color: '#d2a8ff', fontStyle: 'italic' },
|
||||
{ tag: [t.deleted], color: '#ffdcd7', backgroundColor: 'ffeef0' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#ffab70' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: t.invalid, color: '#f97583' },
|
||||
],
|
||||
});
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#fff',
|
||||
lineBackground: '#ffffff99',
|
||||
foreground: '#24292e',
|
||||
selection: '#BBDFFF',
|
||||
selectionMatch: '#BBDFFF',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#6e7781',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#fff',
|
||||
foreground: '#24292e',
|
||||
selection: '#BBDFFF',
|
||||
selectionMatch: '#BBDFFF',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#6e7781',
|
||||
},
|
||||
styles: [
|
||||
{ tag: [t.standard(t.tagName), t.tagName], color: '#116329' },
|
||||
{ tag: [t.comment, t.bracket], color: '#6a737d' },
|
||||
{ tag: [t.className, t.propertyName], color: '#6f42c1' },
|
||||
{ tag: [t.variableName, t.attributeName, t.number, t.operator], color: '#005cc5' },
|
||||
{ tag: [t.keyword, t.typeName, t.typeOperator, t.typeName], color: '#d73a49' },
|
||||
{ tag: [t.string, t.meta, t.regexp], color: '#032f62' },
|
||||
{ tag: [t.name, t.quote], color: '#22863a' },
|
||||
{ tag: [t.heading, t.strong], color: '#24292e', fontWeight: 'bold' },
|
||||
{ tag: [t.emphasis], color: '#24292e', fontStyle: 'italic' },
|
||||
{ tag: [t.deleted], color: '#b31d28', backgroundColor: 'ffeef0' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#e36209' },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#032f62' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: t.invalid, color: '#cb2431' },
|
||||
],
|
||||
});
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Atom One
|
||||
* Atom One dark syntax theme
|
||||
*
|
||||
* https://github.com/atom/one-dark-syntax
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
const hex = ['#000000', '#8ed675', '#56bd2a', '#54636D', '#171717'];
|
||||
|
||||
export const settings = {
|
||||
background: hex[0],
|
||||
lineBackground: 'transparent',
|
||||
foreground: hex[2],
|
||||
selection: hex[4],
|
||||
selectionMatch: hex[0],
|
||||
gutterBackground: hex[0],
|
||||
gutterForeground: hex[3],
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: hex[0],
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: hex[2],
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[1] },
|
||||
{ tag: t.comment, color: hex[3] },
|
||||
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[2] },
|
||||
{ tag: [t.attributeName, t.number], color: hex[1] },
|
||||
{ tag: t.keyword, color: hex[2] },
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[1] },
|
||||
],
|
||||
});
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* gruvbox-dark
|
||||
* author morhetz
|
||||
* From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-dark.css
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#282828',
|
||||
lineBackground: '#28282899',
|
||||
foreground: '#ebdbb2',
|
||||
caret: '#ebdbb2',
|
||||
selection: '#bdae93',
|
||||
selectionMatch: '#bdae93',
|
||||
lineHighlight: '#3c3836',
|
||||
gutterBackground: '#282828',
|
||||
gutterForeground: '#7c6f64',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#282828',
|
||||
foreground: '#ebdbb2',
|
||||
caret: '#ebdbb2',
|
||||
selection: '#b99d555c',
|
||||
selectionMatch: '#b99d555c',
|
||||
lineHighlight: '#baa1602b',
|
||||
gutterBackground: '#282828',
|
||||
gutterForeground: '#7c6f64',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#fb4934' },
|
||||
{ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: '#8ec07c' },
|
||||
{ tag: [t.variableName], color: '#83a598' },
|
||||
{ tag: [t.function(t.variableName)], color: '#8ec07c', fontStyle: 'bold' },
|
||||
{ tag: [t.labelName], color: '#ebdbb2' },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#d3869b' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#ebdbb2' },
|
||||
{ tag: [t.brace], color: '#ebdbb2' },
|
||||
{ tag: [t.annotation], color: '#fb4934d' },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#d3869b' },
|
||||
{ tag: [t.typeName, t.className], color: '#fabd2f' },
|
||||
{ tag: [t.operatorKeyword], color: '#fb4934' },
|
||||
{
|
||||
tag: [t.tagName],
|
||||
color: '#8ec07c',
|
||||
fontStyle: 'bold',
|
||||
},
|
||||
{ tag: [t.squareBracket], color: '#fe8019' },
|
||||
{ tag: [t.angleBracket], color: '#83a598' },
|
||||
{ tag: [t.attributeName], color: '#8ec07c' },
|
||||
{ tag: [t.regexp], color: '#8ec07c' },
|
||||
{ tag: [t.quote], color: '#928374' },
|
||||
{ tag: [t.string], color: '#ebdbb2' },
|
||||
{
|
||||
tag: t.link,
|
||||
color: '#a89984',
|
||||
textDecoration: 'underline',
|
||||
textUnderlinePosition: 'under',
|
||||
},
|
||||
{ tag: [t.url, t.escape, t.special(t.string)], color: '#d3869b' },
|
||||
{ tag: [t.meta], color: '#fabd2f' },
|
||||
{ tag: [t.comment], color: '#928374', fontStyle: 'italic' },
|
||||
{ tag: t.strong, fontWeight: 'bold', color: '#fe8019' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic', color: '#b8bb26' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#b8bb26' },
|
||||
{ tag: [t.heading1, t.heading2], fontWeight: 'bold', color: '#b8bb26' },
|
||||
{
|
||||
tag: [t.heading3, t.heading4],
|
||||
fontWeight: 'bold',
|
||||
color: '#fabd2f',
|
||||
},
|
||||
{ tag: [t.heading5, t.heading6], color: '#fabd2f' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#d3869b' },
|
||||
{ tag: [t.processingInstruction, t.inserted], color: '#83a598' },
|
||||
{ tag: [t.contentSeparator], color: '#fb4934' },
|
||||
{ tag: t.invalid, color: '#fe8019', borderBottom: `1px dotted #fb4934d` },
|
||||
],
|
||||
});
|
||||
-130
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* gruvbox-light
|
||||
* author morhetz
|
||||
* From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-light.css
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#fbf1c7',
|
||||
lineBackground: '#fbf1c799',
|
||||
foreground: '#3c3836',
|
||||
caret: '#af3a03',
|
||||
selection: '#ebdbb2',
|
||||
selectionMatch: '#bdae93',
|
||||
lineHighlight: '#ebdbb2',
|
||||
gutterBackground: '#ebdbb2',
|
||||
gutterForeground: '#665c54',
|
||||
gutterBorder: 'transparent',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#fbf1c7',
|
||||
foreground: '#3c3836',
|
||||
caret: '#af3a03',
|
||||
selection: '#bdae9391',
|
||||
selectionMatch: '#bdae9391',
|
||||
lineHighlight: '#a37f2238',
|
||||
gutterBackground: '#ebdbb2',
|
||||
gutterForeground: '#665c54',
|
||||
gutterBorder: 'transparent',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#9d0006' },
|
||||
{
|
||||
tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
|
||||
color: '#427b58',
|
||||
},
|
||||
{ tag: [t.variableName], color: '#076678' },
|
||||
{ tag: [t.function(t.variableName)], color: '#79740e', fontStyle: 'bold' },
|
||||
{ tag: [t.labelName], color: '#3c3836' },
|
||||
{
|
||||
tag: [t.color, t.constant(t.name), t.standard(t.name)],
|
||||
color: '#8f3f71',
|
||||
},
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#3c3836' },
|
||||
{ tag: [t.brace], color: '#3c3836' },
|
||||
{
|
||||
tag: [t.annotation],
|
||||
color: '#9d0006',
|
||||
},
|
||||
{
|
||||
tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
|
||||
color: '#8f3f71',
|
||||
},
|
||||
{
|
||||
tag: [t.typeName, t.className],
|
||||
color: '#b57614',
|
||||
},
|
||||
{
|
||||
tag: [t.operator, t.operatorKeyword],
|
||||
color: '#9d0006',
|
||||
},
|
||||
{
|
||||
tag: [t.tagName],
|
||||
color: '#427b58',
|
||||
fontStyle: 'bold',
|
||||
},
|
||||
{
|
||||
tag: [t.squareBracket],
|
||||
color: '#af3a03',
|
||||
},
|
||||
{
|
||||
tag: [t.angleBracket],
|
||||
color: '#076678',
|
||||
},
|
||||
{
|
||||
tag: [t.attributeName],
|
||||
color: '#427b58',
|
||||
},
|
||||
{
|
||||
tag: [t.regexp],
|
||||
color: '#427b58',
|
||||
},
|
||||
{
|
||||
tag: [t.quote],
|
||||
color: '#928374',
|
||||
},
|
||||
{ tag: [t.string], color: '#3c3836' },
|
||||
{
|
||||
tag: t.link,
|
||||
color: '#7c6f64',
|
||||
textDecoration: 'underline',
|
||||
textUnderlinePosition: 'under',
|
||||
},
|
||||
{
|
||||
tag: [t.url, t.escape, t.special(t.string)],
|
||||
color: '#8f3f71',
|
||||
},
|
||||
{ tag: [t.meta], color: '#b57614' },
|
||||
{ tag: [t.comment], color: '#928374', fontStyle: 'italic' },
|
||||
{ tag: t.strong, fontWeight: 'bold', color: '#af3a03' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic', color: '#79740e' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#79740e' },
|
||||
{ tag: [t.heading1, t.heading2], fontWeight: 'bold', color: '#79740e' },
|
||||
{
|
||||
tag: [t.heading3, t.heading4],
|
||||
fontWeight: 'bold',
|
||||
color: '#b57614',
|
||||
},
|
||||
{
|
||||
tag: [t.heading5, t.heading6],
|
||||
color: '#b57614',
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#8f3f71' },
|
||||
{
|
||||
tag: [t.processingInstruction, t.inserted],
|
||||
color: '#076678',
|
||||
},
|
||||
{
|
||||
tag: [t.contentSeparator],
|
||||
color: '#9d0006',
|
||||
},
|
||||
{ tag: t.invalid, color: '#af3a03', borderBottom: `1px dotted #9d0006` },
|
||||
],
|
||||
});
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#212121',
|
||||
lineBackground: '#21212199',
|
||||
foreground: '#bdbdbd',
|
||||
caret: '#a0a4ae',
|
||||
selection: '#d7d4f0',
|
||||
selectionMatch: '#d7d4f0',
|
||||
gutterBackground: '#212121',
|
||||
gutterForeground: '#999',
|
||||
gutterActiveForeground: '#4f5b66',
|
||||
lineHighlight: '#111111',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#212121',
|
||||
foreground: '#bdbdbd',
|
||||
caret: '#a0a4ae',
|
||||
selection: '#d7d4f063',
|
||||
selectionMatch: '#d7d4f063',
|
||||
gutterBackground: '#212121',
|
||||
gutterForeground: '#999',
|
||||
gutterActiveForeground: '#4f5b66',
|
||||
lineHighlight: '#333333',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#cf6edf' },
|
||||
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#56c8d8' },
|
||||
{ tag: [t.propertyName], color: '#82AAFF' },
|
||||
{ tag: [t.variableName], color: '#bdbdbd' },
|
||||
{ tag: [t.function(t.variableName)], color: '#82AAFF' },
|
||||
{ tag: [t.labelName], color: '#cf6edf' },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#facf4e' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#56c8d8' },
|
||||
{ tag: [t.brace], color: '#cf6edf' },
|
||||
{ tag: [t.annotation], color: '#f07178' },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#f07178' },
|
||||
{ tag: [t.typeName, t.className], color: '#f07178' },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: '#82AAFF' },
|
||||
{ tag: [t.tagName], color: '#99d066' },
|
||||
{ tag: [t.squareBracket], color: '#f07178' },
|
||||
{ tag: [t.angleBracket], color: '#606f7a' },
|
||||
{ tag: [t.attributeName], color: '#bdbdbd' },
|
||||
{ tag: [t.regexp], color: '#f07178' },
|
||||
{ tag: [t.quote], color: '#6abf69' },
|
||||
{ tag: [t.string], color: '#99d066' },
|
||||
{
|
||||
tag: t.link,
|
||||
color: '#56c8d8',
|
||||
textDecoration: 'underline',
|
||||
textUnderlinePosition: 'under',
|
||||
},
|
||||
{ tag: [t.url, t.escape, t.special(t.string)], color: '#facf4e' },
|
||||
{ tag: [t.meta], color: '#707d8b' },
|
||||
{ tag: [t.comment], color: '#707d8b', fontStyle: 'italic' },
|
||||
{ tag: t.monospace, color: '#bdbdbd' },
|
||||
{ tag: t.strong, fontWeight: 'bold', color: '#f07178' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic', color: '#99d066' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#facf4e' },
|
||||
{ tag: t.heading1, fontWeight: 'bold', color: '#facf4e' },
|
||||
{
|
||||
tag: [t.heading2, t.heading3, t.heading4],
|
||||
fontWeight: 'bold',
|
||||
color: '#facf4e',
|
||||
},
|
||||
{ tag: [t.heading5, t.heading6], color: '#facf4e' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#56c8d8' },
|
||||
{ tag: [t.processingInstruction, t.inserted], color: '#f07178' },
|
||||
{ tag: [t.contentSeparator], color: '#56c8d8' },
|
||||
{ tag: t.invalid, color: '#606f7a', borderBottom: `1px dotted #f07178` },
|
||||
],
|
||||
});
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#FAFAFA',
|
||||
lineBackground: '#FAFAFA99',
|
||||
foreground: '#90A4AE',
|
||||
caret: '#272727',
|
||||
selection: '#80CBC440',
|
||||
selectionMatch: '#FAFAFA',
|
||||
gutterBackground: '#FAFAFA',
|
||||
gutterForeground: '#90A4AE',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#CCD7DA50',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#FAFAFA',
|
||||
foreground: '#90A4AE',
|
||||
caret: '#272727',
|
||||
selection: '#80CBC440',
|
||||
selectionMatch: '#80CBC440',
|
||||
gutterBackground: '#FAFAFA',
|
||||
gutterForeground: '#90A4AE',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#CCD7DA50',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#39ADB5' },
|
||||
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#90A4AE' },
|
||||
{ tag: [t.propertyName], color: '#6182B8' },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#91B859' },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: '#6182B8' },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#39ADB5' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#90A4AE' },
|
||||
{ tag: [t.className], color: '#E2931D' },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#F76D47' },
|
||||
{ tag: [t.typeName], color: '#E2931D', fontStyle: '#E2931D' },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: '#39ADB5' },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#91B859' },
|
||||
{ tag: [t.meta, t.comment], color: '#90A4AE' },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#39ADB5' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#90A4AE' },
|
||||
{ tag: t.invalid, color: '#E5393570' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
],
|
||||
});
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#272822',
|
||||
lineBackground: '#27282299',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#49483E',
|
||||
selectionMatch: '#49483E',
|
||||
gutterBackground: '#272822',
|
||||
gutterForeground: '#FFFFFF70',
|
||||
lineHighlight: '#00000059',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#272822',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#49483E',
|
||||
selectionMatch: '#49483E',
|
||||
gutterBackground: '#272822',
|
||||
gutterForeground: '#FFFFFF70',
|
||||
lineHighlight: '#0000003b',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.labelName, color: '#bababa' },
|
||||
{ tag: [t.comment, t.documentMeta], color: '#8292a2' },
|
||||
{ tag: [t.number, t.bool, t.null, t.atom], color: '#ae81ff' },
|
||||
{ tag: [t.attributeValue, t.className, t.name], color: '#e6db74' },
|
||||
{ tag: [t.propertyName, t.attributeName], color: '#a6e22e' },
|
||||
{ tag: [t.variableName], color: '#9effff' },
|
||||
{ tag: [t.squareBracket], color: '#bababa' },
|
||||
{ tag: [t.string, t.special(t.brace)], color: '#e6db74' },
|
||||
{ tag: [t.regexp, t.className, t.typeName, t.definition(t.typeName)], color: '#66d9ef' },
|
||||
{
|
||||
tag: [t.definition(t.variableName), t.definition(t.propertyName), t.function(t.variableName)],
|
||||
color: '#a6e22e',
|
||||
},
|
||||
// { tag: t.keyword, color: '#f92672' },
|
||||
{ tag: [t.keyword, t.definitionKeyword, t.modifier, t.tagName, t.angleBracket], color: '#f92672' },
|
||||
],
|
||||
});
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#f2f1f8',
|
||||
lineBackground: '#f2f1f899',
|
||||
foreground: '#0c006b',
|
||||
caret: '#5c49e9',
|
||||
selection: '#d5d1f2',
|
||||
selectionMatch: '#d5d1f2',
|
||||
gutterBackground: '#f2f1f8',
|
||||
gutterForeground: '#0c006b70',
|
||||
lineHighlight: '#e1def3',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#f2f1f8',
|
||||
foreground: '#0c006b',
|
||||
caret: '#5c49e9',
|
||||
selection: '#d5d1f2',
|
||||
selectionMatch: '#d5d1f2',
|
||||
gutterBackground: '#f2f1f8',
|
||||
gutterForeground: '#0c006b70',
|
||||
lineHighlight: '#16067911',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.comment, color: '#9995b7' },
|
||||
{
|
||||
tag: t.keyword,
|
||||
color: '#ff5792',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
{ tag: [t.definitionKeyword, t.modifier], color: '#ff5792' },
|
||||
{ tag: [t.className, t.tagName, t.definition(t.typeName)], color: '#0094f0' },
|
||||
{ tag: [t.number, t.bool, t.null, t.special(t.brace)], color: '#5842ff' },
|
||||
{ tag: [t.definition(t.propertyName), t.function(t.variableName)], color: '#0095a8' },
|
||||
{ tag: t.typeName, color: '#b3694d' },
|
||||
{ tag: [t.propertyName, t.variableName], color: '#fa8900' },
|
||||
{ tag: t.operator, color: '#ff5792' },
|
||||
{ tag: t.self, color: '#e64100' },
|
||||
{ tag: [t.string, t.regexp], color: '#00b368' },
|
||||
{ tag: [t.paren, t.bracket], color: '#0431fa' },
|
||||
{ tag: t.labelName, color: '#00bdd6' },
|
||||
{ tag: t.attributeName, color: '#e64100' },
|
||||
{ tag: t.angleBracket, color: '#9995b7' },
|
||||
],
|
||||
});
|
||||
Vendored
-78
@@ -1,78 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#2e3440',
|
||||
lineBackground: '#2e344099',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#3b4252',
|
||||
selectionMatch: '#e5e9f0',
|
||||
gutterBackground: '#2e3440',
|
||||
gutterForeground: '#4c566a',
|
||||
gutterActiveForeground: '#d8dee9',
|
||||
lineHighlight: '#4c566a',
|
||||
};
|
||||
|
||||
// Colors from https://www.nordtheme.com/docs/colors-and-palettes
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#2e3440',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#00000073',
|
||||
selectionMatch: '#00000073',
|
||||
gutterBackground: '#2e3440',
|
||||
gutterForeground: '#4c566a',
|
||||
gutterActiveForeground: '#d8dee9',
|
||||
lineHighlight: '#4c566a29',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#5e81ac' },
|
||||
{ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: '#88c0d0' },
|
||||
{ tag: [t.variableName], color: '#8fbcbb' },
|
||||
{ tag: [t.function(t.variableName)], color: '#8fbcbb' },
|
||||
{ tag: [t.labelName], color: '#81a1c1' },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#5e81ac' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#a3be8c' },
|
||||
{ tag: [t.brace], color: '#8fbcbb' },
|
||||
{ tag: [t.annotation], color: '#d30102' },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#b48ead' },
|
||||
{ tag: [t.typeName, t.className], color: '#ebcb8b' },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: '#a3be8c' },
|
||||
{ tag: [t.tagName], color: '#b48ead' },
|
||||
{ tag: [t.squareBracket], color: '#bf616a' },
|
||||
{ tag: [t.angleBracket], color: '#d08770' },
|
||||
{ tag: [t.attributeName], color: '#ebcb8b' },
|
||||
{ tag: [t.regexp], color: '#5e81ac' },
|
||||
{ tag: [t.quote], color: '#b48ead' },
|
||||
{ tag: [t.string], color: '#a3be8c' },
|
||||
{
|
||||
tag: t.link,
|
||||
color: '#a3be8c',
|
||||
textDecoration: 'underline',
|
||||
textUnderlinePosition: 'under',
|
||||
},
|
||||
{ tag: [t.url, t.escape, t.special(t.string)], color: '#8fbcbb' },
|
||||
{ tag: [t.meta], color: '#88c0d0' },
|
||||
{ tag: [t.monospace], color: '#d8dee9', fontStyle: 'italic' },
|
||||
{ tag: [t.comment], color: '#4c566a', fontStyle: 'italic' },
|
||||
{ tag: t.strong, fontWeight: 'bold', color: '#5e81ac' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic', color: '#5e81ac' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#5e81ac' },
|
||||
{ tag: t.special(t.heading1), fontWeight: 'bold', color: '#5e81ac' },
|
||||
{ tag: t.heading1, fontWeight: 'bold', color: '#5e81ac' },
|
||||
{
|
||||
tag: [t.heading2, t.heading3, t.heading4],
|
||||
fontWeight: 'bold',
|
||||
color: '#5e81ac',
|
||||
},
|
||||
{ tag: [t.heading5, t.heading6], color: '#5e81ac' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#d08770' },
|
||||
{ tag: [t.processingInstruction, t.inserted], color: '#8fbcbb' },
|
||||
{ tag: [t.contentSeparator], color: '#ebcb8b' },
|
||||
{ tag: t.invalid, color: '#434c5e', borderBottom: `1px dotted #d30102` },
|
||||
],
|
||||
});
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
|
||||
// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors
|
||||
|
||||
const chalky = '#e5c07b',
|
||||
coral = '#e06c75',
|
||||
cyan = '#56b6c2',
|
||||
invalid = '#ffffff',
|
||||
ivory = '#abb2bf',
|
||||
stone = '#7d8799', // Brightened compared to original to increase contrast
|
||||
malibu = '#61afef',
|
||||
sage = '#98c379',
|
||||
whiskey = '#d19a66',
|
||||
violet = '#c678dd',
|
||||
darkBackground = '#21252b',
|
||||
highlightBackground = '#2c313a',
|
||||
background = '#282c34',
|
||||
tooltipBackground = '#353a42',
|
||||
selection = '#3E4451',
|
||||
cursor = '#528bff';
|
||||
|
||||
/// The colors used in the theme, as CSS color strings.
|
||||
export const color = {
|
||||
chalky,
|
||||
coral,
|
||||
cyan,
|
||||
invalid,
|
||||
ivory,
|
||||
stone,
|
||||
malibu,
|
||||
sage,
|
||||
whiskey,
|
||||
violet,
|
||||
darkBackground,
|
||||
highlightBackground,
|
||||
background,
|
||||
tooltipBackground,
|
||||
selection,
|
||||
cursor,
|
||||
};
|
||||
|
||||
/// The editor theme styles for One Dark.
|
||||
export const oneDarkTheme = EditorView.theme(
|
||||
{
|
||||
'&': {
|
||||
color: ivory,
|
||||
backgroundColor: background,
|
||||
},
|
||||
|
||||
'.cm-content': {
|
||||
caretColor: cursor,
|
||||
},
|
||||
|
||||
'.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':
|
||||
{ backgroundColor: selection },
|
||||
|
||||
'.cm-panels': { backgroundColor: darkBackground, color: ivory },
|
||||
'.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
|
||||
'.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
|
||||
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: '#72a1ff59',
|
||||
outline: '1px solid #457dff',
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: '#6199ff2f',
|
||||
},
|
||||
|
||||
'.cm-activeLine': { backgroundColor: '#6699ff0b' },
|
||||
'.cm-selectionMatch': { backgroundColor: '#aafe661a' },
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
backgroundColor: '#bad0f847',
|
||||
},
|
||||
|
||||
'.cm-gutters': {
|
||||
backgroundColor: background,
|
||||
color: stone,
|
||||
border: 'none',
|
||||
},
|
||||
|
||||
'.cm-activeLineGutter': {
|
||||
backgroundColor: highlightBackground,
|
||||
},
|
||||
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: '#ddd',
|
||||
},
|
||||
|
||||
'.cm-tooltip': {
|
||||
border: 'none',
|
||||
backgroundColor: tooltipBackground,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent',
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: tooltipBackground,
|
||||
borderBottomColor: tooltipBackground,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
backgroundColor: highlightBackground,
|
||||
color: ivory,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ dark: true },
|
||||
);
|
||||
|
||||
/// The highlighting style for code in the One Dark theme.
|
||||
export const oneDarkHighlightStyle = HighlightStyle.define([
|
||||
{ tag: t.keyword, color: violet },
|
||||
{ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: coral },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: malibu },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey },
|
||||
{ tag: [t.definition(t.name), t.separator], color: ivory },
|
||||
{ tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: chalky },
|
||||
{ tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], color: cyan },
|
||||
{ tag: [t.meta, t.comment], color: stone },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: t.link, color: stone, textDecoration: 'underline' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: coral },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted], color: sage },
|
||||
{ tag: t.invalid, color: invalid },
|
||||
]);
|
||||
|
||||
/// Extension to enable the One Dark theme (both the editor theme and
|
||||
/// the highlight style).
|
||||
export const oneDark = [oneDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)];
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Atom One
|
||||
* Atom One dark syntax theme
|
||||
*
|
||||
* https://github.com/atom/one-dark-syntax
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
const hex = ['#000000', '#ff5356', '#bd312a', '#54636D', '#171717'];
|
||||
|
||||
export const settings = {
|
||||
background: hex[0],
|
||||
lineBackground: 'transparent',
|
||||
foreground: hex[2],
|
||||
selection: hex[4],
|
||||
selectionMatch: hex[0],
|
||||
gutterBackground: hex[0],
|
||||
gutterForeground: hex[3],
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: hex[0],
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: hex[2],
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[1] },
|
||||
{ tag: t.comment, color: hex[3] },
|
||||
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[2] },
|
||||
{ tag: [t.attributeName, t.number], color: hex[1] },
|
||||
{ tag: t.keyword, color: hex[2] },
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[1] },
|
||||
],
|
||||
});
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#002b36',
|
||||
lineBackground: '#002b3699',
|
||||
foreground: '#93a1a1',
|
||||
caret: '#839496',
|
||||
selection: '#173541',
|
||||
selectionMatch: '#aafe661a',
|
||||
gutterBackground: '#00252f',
|
||||
gutterForeground: '#839496',
|
||||
lineHighlight: '#173541',
|
||||
};
|
||||
|
||||
const c = {
|
||||
background: '#002B36',
|
||||
foreground: '#839496',
|
||||
selection: '#004454AA',
|
||||
selectionMatch: '#005A6FAA',
|
||||
cursor: '#D30102',
|
||||
dropdownBackground: '#00212B',
|
||||
dropdownBorder: '#2AA19899',
|
||||
activeLine: '#00cafe11',
|
||||
matchingBracket: '#073642',
|
||||
keyword: '#859900',
|
||||
storage: '#93A1A1',
|
||||
variable: '#268BD2',
|
||||
parameter: '#268BD2',
|
||||
function: '#268BD2',
|
||||
string: '#2AA198',
|
||||
constant: '#CB4B16',
|
||||
type: '#859900',
|
||||
class: '#268BD2',
|
||||
number: '#D33682',
|
||||
comment: '#586E75',
|
||||
heading: '#268BD2',
|
||||
invalid: '#DC322F',
|
||||
regexp: '#DC322F',
|
||||
tag: '#268BD2',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: c.background,
|
||||
foreground: c.foreground,
|
||||
caret: c.cursor,
|
||||
selection: c.selection,
|
||||
selectionMatch: c.selection,
|
||||
gutterBackground: c.background,
|
||||
gutterForeground: c.foreground,
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: c.activeLine,
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: c.keyword },
|
||||
{ tag: [t.name, t.deleted, t.character, t.macroName], color: c.variable },
|
||||
{ tag: [t.propertyName], color: c.function },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: c.string },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: c.function },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: c.constant },
|
||||
{ tag: [t.definition(t.name), t.separator], color: c.variable },
|
||||
{ tag: [t.className], color: c.class },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: c.number },
|
||||
{ tag: [t.typeName], color: c.type, fontStyle: c.type },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: c.keyword },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: c.regexp },
|
||||
{ tag: [t.meta, t.comment], color: c.comment },
|
||||
{ tag: t.tagName, color: c.tag },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: c.heading },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: c.variable },
|
||||
{ tag: t.invalid, color: c.invalid },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
],
|
||||
});
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
// this is slightly different from https://thememirror.net/solarized-light
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#fdf6e3',
|
||||
lineBackground: '#fdf6e399',
|
||||
foreground: '#657b83',
|
||||
caret: '#586e75',
|
||||
selection: '#dfd9c8',
|
||||
selectionMatch: '#dfd9c8',
|
||||
gutterBackground: '#00000010',
|
||||
gutterForeground: '#657b83',
|
||||
lineHighlight: '#dfd9c8',
|
||||
};
|
||||
|
||||
const c = {
|
||||
background: '#FDF6E3',
|
||||
foreground: '#657B83',
|
||||
selection: '#EEE8D5',
|
||||
selectionMatch: '#EEE8D5',
|
||||
cursor: '#657B83',
|
||||
dropdownBackground: '#EEE8D5',
|
||||
dropdownBorder: '#D3AF86',
|
||||
activeLine: '#3d392d11',
|
||||
matchingBracket: '#EEE8D5',
|
||||
keyword: '#859900',
|
||||
storage: '#586E75',
|
||||
variable: '#268BD2',
|
||||
parameter: '#268BD2',
|
||||
function: '#268BD2',
|
||||
string: '#2AA198',
|
||||
constant: '#CB4B16',
|
||||
type: '#859900',
|
||||
class: '#268BD2',
|
||||
number: '#D33682',
|
||||
comment: '#93A1A1',
|
||||
heading: '#268BD2',
|
||||
invalid: '#DC322F',
|
||||
regexp: '#DC322F',
|
||||
tag: '#268BD2',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: c.background,
|
||||
foreground: c.foreground,
|
||||
caret: c.cursor,
|
||||
selection: c.selection,
|
||||
selectionMatch: c.selectionMatch,
|
||||
gutterBackground: c.background,
|
||||
gutterForeground: c.foreground,
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: c.activeLine,
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: c.keyword },
|
||||
{ tag: [t.name, t.deleted, t.character, t.macroName], color: c.variable },
|
||||
{ tag: [t.propertyName], color: c.function },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: c.string },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: c.function },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: c.constant },
|
||||
{ tag: [t.definition(t.name), t.separator], color: c.variable },
|
||||
{ tag: [t.className], color: c.class },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: c.number },
|
||||
{ tag: [t.typeName], color: c.type, fontStyle: c.type },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: c.keyword },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: c.regexp },
|
||||
{ tag: [t.meta, t.comment], color: c.comment },
|
||||
{ tag: t.tagName, color: c.tag },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: c.heading },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: c.variable },
|
||||
{ tag: t.invalid, color: c.invalid },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
],
|
||||
});
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Atom One
|
||||
* Atom One dark syntax theme
|
||||
*
|
||||
* https://github.com/atom/one-dark-syntax
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
const hex = ['#1e1e1e', '#fbde2d', '#ff1493', '#4c83ff', '#ededed', '#cccccc', '#ffffff30', '#dc2f8c'];
|
||||
|
||||
export const settings = {
|
||||
background: '#000000',
|
||||
lineBackground: 'transparent',
|
||||
foreground: hex[4],
|
||||
selection: hex[6],
|
||||
gutterBackground: hex[0],
|
||||
gutterForeground: hex[5],
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: hex[0],
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: hex[4],
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[3] },
|
||||
|
||||
{ tag: t.comment, color: '#54636D' },
|
||||
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[4] },
|
||||
{ tag: [t.attributeName, t.number], color: hex[3] },
|
||||
{ tag: t.keyword, color: hex[1] },
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[2] },
|
||||
],
|
||||
});
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#303841',
|
||||
lineBackground: '#30384199',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FBAC52',
|
||||
selection: '#4C5964',
|
||||
selectionMatch: '#3A546E',
|
||||
gutterBackground: '#303841',
|
||||
gutterForeground: '#FFFFFF70',
|
||||
lineHighlight: '#00000059',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#303841',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FBAC52',
|
||||
selection: '#4C5964',
|
||||
selectionMatch: '#3A546E',
|
||||
gutterBackground: '#303841',
|
||||
gutterForeground: '#FFFFFF70',
|
||||
lineHighlight: '#00000059',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.labelName, color: '#A2A9B5' },
|
||||
{ tag: [t.meta, t.comment], color: '#A2A9B5' },
|
||||
{ tag: [t.attributeName, t.keyword], color: '#B78FBA' },
|
||||
{ tag: t.function(t.variableName), color: '#5AB0B0' },
|
||||
{ tag: [t.string, t.regexp, t.attributeValue], color: '#99C592' },
|
||||
{ tag: t.operator, color: '#f47954' },
|
||||
// { tag: t.moduleKeyword, color: 'red' },
|
||||
{ tag: [t.tagName, t.modifier], color: '#E35F63' },
|
||||
{ tag: [t.number, t.definition(t.tagName), t.className, t.definition(t.variableName)], color: '#fbac52' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#E35F63' },
|
||||
{ tag: t.variableName, color: '#539ac4' },
|
||||
{ tag: [t.propertyName, t.typeName], color: '#629ccd' },
|
||||
{ tag: t.propertyName, color: '#36b7b5' },
|
||||
],
|
||||
});
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
let colorA = '#6edee4';
|
||||
//let colorB = 'magenta';
|
||||
let colorB = 'white';
|
||||
let colorC = 'red';
|
||||
let colorD = '#f8fc55';
|
||||
|
||||
export const settings = {
|
||||
background: '#000000',
|
||||
foreground: colorA, // whats that?
|
||||
caret: colorC,
|
||||
selection: colorD,
|
||||
selectionMatch: colorA,
|
||||
lineHighlight: '#6edee440', // panel bg
|
||||
lineBackground: '#00000040',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
// customStyle: '.cm-line { line-height: 1 }',
|
||||
};
|
||||
|
||||
let punctuation = colorD;
|
||||
let mini = colorB;
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.labelName, color: colorB },
|
||||
{ tag: t.keyword, color: colorA },
|
||||
{ tag: t.operator, color: mini },
|
||||
{ tag: t.special(t.variableName), color: colorA },
|
||||
{ tag: t.typeName, color: colorA },
|
||||
{ tag: t.atom, color: colorA },
|
||||
{ tag: t.number, color: mini },
|
||||
{ tag: t.definition(t.variableName), color: colorA },
|
||||
{ tag: t.string, color: mini },
|
||||
{ tag: t.special(t.string), color: mini },
|
||||
{ tag: t.comment, color: punctuation },
|
||||
{ tag: t.variableName, color: colorA },
|
||||
{ tag: t.tagName, color: colorA },
|
||||
{ tag: t.bracket, color: punctuation },
|
||||
{ tag: t.meta, color: colorA },
|
||||
{ tag: t.attributeName, color: colorA },
|
||||
{ tag: t.propertyName, color: colorA }, // methods
|
||||
{ tag: t.className, color: colorA },
|
||||
{ tag: t.invalid, color: colorC },
|
||||
{ tag: [t.unit, t.punctuation], color: punctuation },
|
||||
],
|
||||
});
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
export const settings = {
|
||||
background: 'black',
|
||||
foreground: '#41FF00', // whats that?
|
||||
caret: '#41FF00',
|
||||
selection: '#ffffff20',
|
||||
selectionMatch: '#036dd626',
|
||||
lineHighlight: '#ffffff10',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.labelName, color: 'inherit' },
|
||||
{ tag: t.keyword, color: 'inherit' },
|
||||
{ tag: t.operator, color: 'inherit' },
|
||||
{ tag: t.special(t.variableName), color: 'inherit' },
|
||||
{ tag: t.typeName, color: 'inherit' },
|
||||
{ tag: t.atom, color: 'inherit' },
|
||||
{ tag: t.number, color: 'inherit' },
|
||||
{ tag: t.definition(t.variableName), color: 'inherit' },
|
||||
{ tag: t.string, color: 'inherit' },
|
||||
{ tag: t.special(t.string), color: 'inherit' },
|
||||
{ tag: t.comment, color: 'inherit' },
|
||||
{ tag: t.variableName, color: 'inherit' },
|
||||
{ tag: t.tagName, color: 'inherit' },
|
||||
{ tag: t.bracket, color: 'inherit' },
|
||||
{ tag: t.meta, color: 'inherit' },
|
||||
{ tag: t.attributeName, color: 'inherit' },
|
||||
{ tag: t.propertyName, color: 'inherit' },
|
||||
{ tag: t.className, color: 'inherit' },
|
||||
{ tag: t.invalid, color: 'inherit' },
|
||||
],
|
||||
});
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { syntaxHighlighting } from '@codemirror/language';
|
||||
import { HighlightStyle } from '@codemirror/language';
|
||||
|
||||
export const createTheme = ({ theme, settings, styles }) => {
|
||||
const _theme = EditorView.theme(
|
||||
{
|
||||
'&': {
|
||||
color: settings.foreground,
|
||||
backgroundColor: settings.background,
|
||||
},
|
||||
'.cm-gutters': {
|
||||
backgroundColor: settings.gutterBackground,
|
||||
color: settings.gutterForeground,
|
||||
//borderRightColor: settings.gutterBorder
|
||||
},
|
||||
'.cm-content': {
|
||||
caretColor: settings.caret,
|
||||
},
|
||||
'.cm-cursor, .cm-dropCursor': {
|
||||
borderLeftColor: settings.caret,
|
||||
},
|
||||
'.cm-activeLineGutter': {
|
||||
// color: settings.gutterActiveForeground
|
||||
backgroundColor: settings.lineHighlight,
|
||||
},
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: settings.lineHighlight,
|
||||
},
|
||||
'&.cm-focused .cm-selectionBackground, & .cm-line::selection, & .cm-selectionLayer .cm-selectionBackground, .cm-content ::selection':
|
||||
{
|
||||
background: settings.selection + ' !important',
|
||||
},
|
||||
'& .cm-selectionMatch': {
|
||||
backgroundColor: settings.selectionMatch,
|
||||
},
|
||||
},
|
||||
{ dark: theme === 'dark' },
|
||||
);
|
||||
const highlightStyle = HighlightStyle.define(styles);
|
||||
return [_theme, syntaxHighlighting(highlightStyle)];
|
||||
};
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#24283b',
|
||||
lineBackground: '#24283b99',
|
||||
foreground: '#7982a9',
|
||||
caret: '#c0caf5',
|
||||
selection: '#6f7bb630',
|
||||
selectionMatch: '#1f2335',
|
||||
gutterBackground: '#24283b',
|
||||
gutterForeground: '#7982a9',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#292e42',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#24283b',
|
||||
foreground: '#7982a9',
|
||||
caret: '#c0caf5',
|
||||
selection: '#6f7bb630',
|
||||
selectionMatch: '#343b5f',
|
||||
gutterBackground: '#24283b',
|
||||
gutterForeground: '#7982a9',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#292e427a',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#bb9af7' },
|
||||
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#c0caf5' },
|
||||
{ tag: [t.propertyName], color: '#7aa2f7' },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#9ece6a' },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: '#7aa2f7' },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#bb9af7' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#c0caf5' },
|
||||
{ tag: [t.className], color: '#c0caf5' },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#ff9e64' },
|
||||
{ tag: [t.typeName], color: '#2ac3de', fontStyle: '#2ac3de' },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: '#bb9af7' },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#b4f9f8' },
|
||||
{ tag: [t.meta, t.comment], color: '#565f89' },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#89ddff' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#c0caf5' },
|
||||
{ tag: t.invalid, color: '#ff5370' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
],
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user