mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-14 14:53:45 -04:00
Compare commits
566 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b312ff63a9 | |||
| 93bc353b6b | |||
| 14a0dea826 | |||
| 009f5445be | |||
| 3d37c73103 | |||
| 0b0f9fe13b | |||
| 1d189763b2 | |||
| a4b55776d2 | |||
| 0287be547a | |||
| 6a83ede33d | |||
| 1e3f546918 | |||
| faa60be211 | |||
| dde2b9c6f8 | |||
| 382dce65df | |||
| 63f4f202f1 | |||
| 366637026b | |||
| 608dfd515e | |||
| 9bd7b66228 | |||
| 5e4eb7fc53 | |||
| 45bccf13ff | |||
| 65f1760fad | |||
| b8d2d02466 | |||
| 058cb36640 | |||
| 5c3501caa4 | |||
| 1341bd9c92 | |||
| 760d1abec0 | |||
| 2b44fc48a4 | |||
| 99779171f5 | |||
| 64ebfb57c3 | |||
| 099d321dbd | |||
| 59bdfd2cb8 | |||
| 56d3436fda | |||
| 440ce0cd3b | |||
| 98346261ed | |||
| 38e4718826 | |||
| 38908baccd | |||
| 8d02d4270a | |||
| 9cf1cd2656 | |||
| 4c079b6ebe | |||
| e9e2e881e5 | |||
| 685b4f172b | |||
| 53e50d81b0 | |||
| 8f58d5788f | |||
| 306a6db8dc | |||
| 757d78252b | |||
| 6f2e026e4a | |||
| 22f114be59 | |||
| f993056fb1 | |||
| fdbbcd2dd2 | |||
| 7bec92ca99 | |||
| 5702914661 | |||
| f2a9e2d4e6 | |||
| 6398b5fb38 | |||
| 9ceebd4679 | |||
| 177f5c1431 | |||
| d6c2a88496 | |||
| 3ee7d0cd4f | |||
| 53d43dc44c | |||
| 637c619345 | |||
| 47fb441feb | |||
| d02b1fc05c | |||
| 480b5ff58b | |||
| c088c0c9d2 | |||
| f32abac06f | |||
| 697f0a82e6 | |||
| 98970417c3 | |||
| b5d48b2f0b | |||
| c48aba50f0 | |||
| f539930509 | |||
| 759cdb6276 | |||
| 3dae2a80ab | |||
| 7cc44a8829 | |||
| 90506f9e61 | |||
| c852b5fab0 | |||
| bd08b74ebc | |||
| 8f4dcb6bc6 | |||
| 17e1ebeb93 | |||
| ad25ce7b21 | |||
| 6e3aa693d5 | |||
| 2968017300 | |||
| ab76b4424a | |||
| b64daf6284 | |||
| 2b69004eb4 | |||
| a32bb58fb3 | |||
| 9e233fe587 | |||
| b1090a1dd8 | |||
| 344d9ca1a8 | |||
| b92a28242e | |||
| 695ac6a5d3 | |||
| a468201822 | |||
| 05ea9c86fb | |||
| 6e29210bdc | |||
| e81345ca2f | |||
| 6b8ee8ad11 | |||
| c2d4b529f6 | |||
| 999c0b2349 | |||
| 5ed28d6168 | |||
| bceb37e8ea | |||
| 2f4cba4e94 | |||
| d8e4055c6e | |||
| 6f9bcc53dc | |||
| 8962e5a4f0 | |||
| e55b7c6ce3 | |||
| 9b4574c7b0 | |||
| 6d355a803a | |||
| dee75bc759 | |||
| dc8e5d5394 | |||
| 282180ef1c | |||
| 3eea908636 | |||
| bfbd920b1d | |||
| 739fb8562f | |||
| cbd448e934 | |||
| b192c6823c | |||
| dccfefacd7 | |||
| 38876e135b | |||
| f652c2ca86 | |||
| 4f6c4bb95f | |||
| f5bb88a696 | |||
| b27c9dbf37 | |||
| 5fcb96f73c | |||
| 17df418935 | |||
| 2ccb95aec0 | |||
| 3c2692bdda | |||
| 756a65016d | |||
| a18d485283 | |||
| fcf6450ff0 | |||
| 09dd374722 | |||
| 9c9ce50b03 | |||
| edc2903106 | |||
| b15843f3a7 | |||
| 86333ab6f6 | |||
| 003e3b790d | |||
| 4048ced7e6 | |||
| 64b99f5a8c | |||
| b4af840240 | |||
| 8a502e886e | |||
| c7ee90922f | |||
| 530339ed8f | |||
| fece4e8b57 | |||
| afdcd42039 | |||
| 690b8648a8 | |||
| 7c1e968d41 | |||
| c0b3187029 | |||
| af1f7658c7 | |||
| b531962b1f | |||
| 8867a843ac | |||
| 6e972f4380 | |||
| 65282bf5ea | |||
| 3df9744d79 | |||
| 5a5bbfc8da | |||
| f66478b9da | |||
| 904c213d03 | |||
| d9c3f051b9 | |||
| 470a345296 | |||
| 0925ea56dd | |||
| 1f233b9e7d | |||
| 5c5d411588 | |||
| bbb88a2302 | |||
| 64c94b3623 | |||
| 42c2262576 | |||
| 4959bff060 | |||
| 1ded398468 | |||
| b016720696 | |||
| c639e44ca7 | |||
| b664bd3d1f | |||
| dfb7d7bb7f | |||
| 3e6a45cf8b | |||
| f47f5c1344 | |||
| 47f65da621 | |||
| c210ea3f89 | |||
| 50f076084f | |||
| 5cd98880ca | |||
| aabc82bedd | |||
| 84581b22aa | |||
| 3575fc3cc8 | |||
| 43523b73c4 | |||
| b98ebc696d | |||
| f802f18d68 | |||
| 919c2854d5 | |||
| cf8203a4db | |||
| cda79f3e5c | |||
| 07b0a2e0f0 | |||
| 5e81b542dc | |||
| 451cdcc3a9 | |||
| 5ff1d35272 | |||
| 8bde61fa27 | |||
| efd4071637 | |||
| 981ad1e242 | |||
| 49e9b84962 | |||
| 54c5454306 | |||
| 9b279ff671 | |||
| 20dcae68c1 | |||
| 52d1443cf8 | |||
| 848edb1f69 | |||
| 88fb0b3b75 | |||
| dc8becc610 | |||
| e99f229b57 | |||
| c810a02a79 | |||
| ed7dc4ef6e | |||
| 4636d824fa | |||
| 018d1ccd31 | |||
| d86df33b8c | |||
| b74d5becd5 | |||
| b87b2aff9a | |||
| 95b1548e5f | |||
| 0b1bc74c3b | |||
| ce9d23049a | |||
| 0338c5b222 | |||
| 1bf74024d4 | |||
| 63ee218d53 | |||
| 3d37478577 | |||
| 9d5d86bb09 | |||
| e2b7c3a62b | |||
| 1fb68af5af | |||
| b611c9c2b4 | |||
| ceb466ab9c | |||
| d09809ed12 | |||
| f558b4b8f6 | |||
| f26a2af38e | |||
| 75f5f45652 | |||
| 5cef1a8cf9 | |||
| bcb1da826e | |||
| 1f5f1a63a6 | |||
| 44828e2047 | |||
| bcef67e5a1 | |||
| b4ef786108 | |||
| d03fc43f03 | |||
| ca00f7c5d0 | |||
| dbbe2d7c98 | |||
| 1058c272a4 | |||
| 81e97a5643 | |||
| f105ea6677 | |||
| 6307f755f7 | |||
| fac698e39c | |||
| d54022a65a | |||
| 892a686462 | |||
| fbf9d06d84 | |||
| 06415d7f14 | |||
| d353eff1f6 | |||
| ce5add4a56 | |||
| 212a478161 | |||
| 33b31fb434 | |||
| b51dacb11c | |||
| 1e59b4efa4 | |||
| e31a9965d5 | |||
| 6c537cb134 | |||
| 6062a2e757 | |||
| ac83ff8817 | |||
| b55661b21e | |||
| 638e247a5f | |||
| ff4d4357b2 | |||
| 1460defa42 | |||
| 1053c7eb38 | |||
| c09b590cf6 | |||
| 63f9f6cba0 | |||
| 9112a0c4f5 | |||
| e754070a59 | |||
| afa202e2a8 | |||
| fc3d7ade11 | |||
| d192201e07 | |||
| c9bdfe57dd | |||
| 7e1c6c1c22 | |||
| 5d3778fb27 | |||
| f362f1c9f6 | |||
| fd68ad9bde | |||
| 7222c29c55 | |||
| 234beff00e | |||
| 90fe4d3816 | |||
| 5a188f7461 | |||
| 5d98fd2ab3 | |||
| 46400effb7 | |||
| 710da1d28d | |||
| f4b8753171 | |||
| aa1efcc7ea | |||
| 3e721d91a5 | |||
| badbcdcf25 | |||
| 4f35027d9f | |||
| de7c652706 | |||
| 0e59a233ed | |||
| b3e0f180b3 | |||
| a2c896f765 | |||
| c7cd03a2aa | |||
| e6604491a9 | |||
| 1d7f755b89 | |||
| 4f6e38f714 | |||
| 23ed342635 | |||
| 443760c0d1 | |||
| bb56ed479f | |||
| c5d7f95441 | |||
| 3ffe3957ba | |||
| 3189b365c8 | |||
| e19d059c0f | |||
| b8b999eab5 | |||
| 393d17a41b | |||
| 35911f3082 | |||
| 024f424bf5 | |||
| 1b8ccc05e1 | |||
| 77267a80e3 | |||
| 0ca540b671 | |||
| 5731ae0cdc | |||
| 5d562e8cd6 | |||
| 9432f780f2 | |||
| d06a75a2cd | |||
| 57c48f0c45 | |||
| a4792e29f5 | |||
| 52ebfa1d09 | |||
| 6cde970e0d | |||
| 188d318125 | |||
| f95eadab2f | |||
| 1349ecb4af | |||
| 268c66cd3d | |||
| 8d28317d0e | |||
| fc41bd1a70 | |||
| 773420fd65 | |||
| 45fffa514a | |||
| a8803784e1 | |||
| bfb4eee122 | |||
| 3325a8dbe9 | |||
| 13a4512601 | |||
| cdf50cbaab | |||
| f61916fffa | |||
| 00b0341839 | |||
| d5ab391a98 | |||
| d2a9b9b750 | |||
| 2348023b22 | |||
| 6e5d8eed19 | |||
| c33c8b5469 | |||
| 7b6074ccef | |||
| e085819fe2 | |||
| 155ef9e95f | |||
| 55c226cda6 | |||
| c242f5f625 | |||
| 859f153ec6 | |||
| c5de21ef61 | |||
| 36374fb3fb | |||
| 570c7a0d6b | |||
| c3be21e6e5 | |||
| 7410ac5cec | |||
| f5127147de | |||
| 1d144faed7 | |||
| 7b914e7f22 | |||
| 052addd67e | |||
| 3d162536aa | |||
| 2443aad01b | |||
| 08f6c1ad5e | |||
| 974376d404 | |||
| 1da80db048 | |||
| 42519581c1 | |||
| 1e3bbc57cc | |||
| 9bf0f624f5 | |||
| c6418789c4 | |||
| 62d6ef2287 | |||
| 8b909c54db | |||
| 9d029ac914 | |||
| 8644b3d163 | |||
| 426c5edf25 | |||
| d30623dad4 | |||
| 49d4fb499f | |||
| e4f89f072e | |||
| e12dadb33f | |||
| 991d8e4717 | |||
| 26cc7e2920 | |||
| 04eac3e7c5 | |||
| 7cf7575e6d | |||
| 0be9f439bc | |||
| 36fe942e24 | |||
| 06958a5281 | |||
| 033b123ae0 | |||
| b7d8593a4d | |||
| a5b05d211e | |||
| 111cea2381 | |||
| 896a3828f6 | |||
| eb02997836 | |||
| 8a33c171f5 | |||
| f87f279198 | |||
| 4cc019f038 | |||
| 34f4afad01 | |||
| 947b263b9c | |||
| 0a408e464a | |||
| 0d660277f6 | |||
| 33fef379ad | |||
| 0531369613 | |||
| c66f19108c | |||
| ef53367ba9 | |||
| f01d51f76a | |||
| d741b4d17a | |||
| f33ec0e2af | |||
| 726abf76ce | |||
| a7d74ed6d6 | |||
| bd69ffb4b7 | |||
| 648fbf99fa | |||
| b913e1c50d | |||
| 23a4bf6641 | |||
| ec470aa2c6 | |||
| ae335ac40c | |||
| 9ecad810b1 | |||
| f18cf76dde | |||
| 08c918f13e | |||
| 2e72c7f4ed | |||
| 614f393a5d | |||
| 75f7f4deac | |||
| 2a67698616 | |||
| 801879bddf | |||
| 7d8984b706 | |||
| 1a2847d953 | |||
| e71db24073 | |||
| f61de0c47c | |||
| 53b8bbb732 | |||
| aeaf447142 | |||
| 1e6e0a61b3 | |||
| dd696ba663 | |||
| be4c8cbdce | |||
| b6b54a8553 | |||
| 1d05051fd6 | |||
| 6bca8e2a2f | |||
| 630ba62cc3 | |||
| 8ac98262bc | |||
| 6057e9ada1 | |||
| 94e8b18ffa | |||
| 2ec20c4ed4 | |||
| c4fe2c7497 | |||
| df2d28f0e5 | |||
| d9bd6f9e7d | |||
| 1e0cc2ab17 | |||
| 835c7b6879 | |||
| 721e7f06b8 | |||
| a46bbeaa10 | |||
| e3a3c0c5f9 | |||
| 67964c7e23 | |||
| 9e5f9d37ef | |||
| 0c1234a35f | |||
| c6cc487fc6 | |||
| dd171f8320 | |||
| c22c786cf2 | |||
| c191471e1e | |||
| 0d0cb3fd93 | |||
| bb4b7dc533 | |||
| 8440fa42ea | |||
| 61f070744a | |||
| 2e6a561184 | |||
| 8703d684c4 | |||
| b0d2115f43 | |||
| 96802a271c | |||
| a91f4576f3 | |||
| ad080d02b3 | |||
| e9e7e02547 | |||
| 2ffc6c9601 | |||
| 3a9349d9f5 | |||
| bd3f2845f8 | |||
| 2d320c459b | |||
| ff9f4a8a0d | |||
| d7b94807cb | |||
| 2c16a37a68 | |||
| 717e4a2c1d | |||
| c225b4719f | |||
| 25f762c393 | |||
| b75561e3c7 | |||
| abaab89061 | |||
| e409086b34 | |||
| 532f1435d9 | |||
| 6cb61ade03 | |||
| a40219ba06 | |||
| 9c99f5ada2 | |||
| 2d72c8faa0 | |||
| b188fc6b6d | |||
| bfb22228cc | |||
| 9714d88d61 | |||
| bdddb91510 | |||
| 07bcaa65a9 | |||
| 5bf1805534 | |||
| 202685e8c4 | |||
| aba633e16e | |||
| da45ce689c | |||
| acea7e435e | |||
| cb4810cbbc | |||
| 5ec205b383 | |||
| ebc0b293a9 | |||
| 4608c321bd | |||
| c4243f41c6 | |||
| d95b8bc667 | |||
| 263c13c36e | |||
| ddceb36613 | |||
| ff8ec628be | |||
| 11a07df120 | |||
| 8befa4382b | |||
| 39006e4d71 | |||
| 812ed6b60d | |||
| 8b6c8eb35d | |||
| dcda9a5753 | |||
| d3733d4209 | |||
| ef6af96947 | |||
| d86ff0a536 | |||
| 2c9ea03fde | |||
| 4eae366bee | |||
| 117f7fa4b5 | |||
| 73ee84a1fb | |||
| 9c899d5308 | |||
| b60beaf508 | |||
| 796f741a92 | |||
| 8da1d9fbf3 | |||
| 5345986b5b | |||
| 3945abd7e9 | |||
| 053e4ee54b | |||
| 25d61a2e31 | |||
| 5e6131af24 | |||
| 2899ee5aef | |||
| 9d1674b13c | |||
| bfead58212 | |||
| 926c893c4f | |||
| 3cd31e4841 | |||
| c2be2ed76a | |||
| ac0dc7a8de | |||
| cba9169b2f | |||
| e9a0a06b77 | |||
| d80c06cd55 | |||
| d850726910 | |||
| 1b01f41ffd | |||
| ad00fa65f8 | |||
| 814babbf7a | |||
| 28334ec94d | |||
| 7d2110d75f | |||
| 94ccfa631e | |||
| c7c56a1357 | |||
| d40e4dac2f | |||
| 686247f0cb | |||
| dffeac41af | |||
| 6e496b7a4a | |||
| 71e843e1a3 | |||
| 61ba33043e | |||
| 943d78f268 | |||
| 5ac59e95dd | |||
| 03bbc0bc27 | |||
| 4c3457438d | |||
| 85bccdcc19 | |||
| 94c07b6b7a | |||
| 42000a7628 | |||
| f514cd85b1 | |||
| d53929cf68 | |||
| a4bd0ae100 | |||
| 283a071c86 | |||
| b5fbcd108f | |||
| 6350cfdb9c | |||
| 5e9da78e64 | |||
| f3b83b41f5 | |||
| 1d95da7a3a | |||
| 2a2ddf205a | |||
| 6c9e0aaa1a | |||
| d23038d386 | |||
| 248f67d1ab | |||
| f3fa1483d7 | |||
| 11102adb75 | |||
| 9f958abb08 | |||
| b72e71b0bd | |||
| 065b24a2ee | |||
| 758b94776d | |||
| e6f3b1f1ef | |||
| e62d5759e7 | |||
| 69a5d62027 | |||
| 226cf356d6 | |||
| 73d49f152a | |||
| 83e46037d2 | |||
| 95ae5191ce | |||
| 5ca9afa9e8 | |||
| b233c33f56 | |||
| e3b6884b86 | |||
| cdba9efb59 |
@@ -1,25 +0,0 @@
|
||||
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
|
||||
+2
-1
@@ -14,5 +14,6 @@
|
||||
"rules": {
|
||||
"no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }],
|
||||
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}]
|
||||
}
|
||||
},
|
||||
"files": ["**/*.mjs", "**/*.js"]
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@ jobs:
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8.11.0
|
||||
- uses: actions/setup-node@v3
|
||||
version: 9.12.2
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
@@ -39,11 +39,11 @@ jobs:
|
||||
uses: actions/configure-pages@v2
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
# Upload entire repository
|
||||
path: "./website/dist"
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
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 libasound2-dev
|
||||
|
||||
- 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
|
||||
@@ -11,10 +11,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8.11.0
|
||||
- uses: actions/setup-node@v3
|
||||
version: 9.12.2
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
||||
@@ -11,3 +11,5 @@ pnpm-lock.yaml
|
||||
pnpm-workspace.yaml
|
||||
**/dev-dist
|
||||
website/.astro
|
||||
!tidal-drum-machines.json
|
||||
!tidal-drum-machines-alias.json
|
||||
|
||||
@@ -138,6 +138,18 @@ pnpm --filter "./packages/**" publish --access public
|
||||
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.
|
||||
|
||||
|
||||
## useful commands
|
||||
```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
|
||||
```
|
||||
## Have Fun
|
||||
|
||||
Remember to have fun, and that this project is driven by the passion of volunteers!
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# strudel
|
||||
|
||||
[](https://github.com/tidalcycles/strudel/actions)
|
||||
[](https://github.com/tidalcycles/strudel/actions) [](https://doi.org/10.5281/zenodo.6659278)
|
||||
|
||||
An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using web technologies. This software is a bit more stable now, but please continue to tread carefully.
|
||||
|
||||
@@ -14,10 +14,16 @@ An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using w
|
||||
|
||||
After cloning the project, you can run the REPL locally:
|
||||
|
||||
```bash
|
||||
pnpm i
|
||||
pnpm dev
|
||||
```
|
||||
1. Install [Node.js](https://nodejs.org/)
|
||||
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
|
||||
```
|
||||
|
||||
## Using Strudel In Your Project
|
||||
|
||||
@@ -25,6 +31,10 @@ This project is organized into many [packages](./packages), which are also avail
|
||||
|
||||
Read more about how to use these in your own project [here](https://strudel.cc/technical-manual/project-start).
|
||||
|
||||
You will need to abide by the terms of the [GNU Affero Public Licence v3](LICENSE.md). 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.
|
||||
|
||||
## Contributing
|
||||
|
||||
There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md).
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { queryCode, testCycles } from '../test/runtime.mjs';
|
||||
import * as tunes from '../website/src/repl/tunes.mjs';
|
||||
import { describe, bench } from 'vitest';
|
||||
import { calculateTactus } from '../packages/core/index.mjs';
|
||||
import { calculateSteps } from '../packages/core/index.mjs';
|
||||
|
||||
const tuneKeys = Object.keys(tunes);
|
||||
|
||||
describe('renders tunes', () => {
|
||||
tuneKeys.forEach((key) => {
|
||||
describe(key, () => {
|
||||
calculateTactus(true);
|
||||
bench(`+tactus`, async () => {
|
||||
calculateSteps(true);
|
||||
bench(`+steps`, async () => {
|
||||
await queryCode(tunes[key], testCycles[key] || 1);
|
||||
});
|
||||
calculateTactus(false);
|
||||
bench(`-tactus`, async () => {
|
||||
calculateSteps(false);
|
||||
bench(`-steps`, async () => {
|
||||
await queryCode(tunes[key], testCycles[key] || 1);
|
||||
});
|
||||
calculateTactus(true);
|
||||
calculateSteps(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
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',
|
||||
'**/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,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -9,7 +9,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.10"
|
||||
"vite": "^6.0.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strudel/codemirror": "workspace:*",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.10"
|
||||
"vite": "^6.0.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strudel/web": "workspace:*"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.10"
|
||||
"vite": "^6.0.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strudel/core": "workspace:*",
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"superdough": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,6 @@
|
||||
"hs2js": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.8"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
+17
-13
@@ -21,7 +21,7 @@
|
||||
"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 . --ext mjs,js --quiet",
|
||||
"lint": "eslint . --quiet",
|
||||
"codeformat": "prettier --write .",
|
||||
"format-check": "prettier --check .",
|
||||
"report-undocumented": "npm run jsdoc-json && node jsdoc/undocumented.mjs > undocumented.json",
|
||||
@@ -55,19 +55,23 @@
|
||||
"@strudel/xen": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.5.9",
|
||||
"@vitest/ui": "^1.1.0",
|
||||
"acorn": "^8.11.3",
|
||||
"dependency-tree": "^10.0.9",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"@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",
|
||||
"events": "^3.3.0",
|
||||
"jsdoc": "^4.0.2",
|
||||
"globals": "^15.14.0",
|
||||
"jsdoc": "^4.0.4",
|
||||
"jsdoc-json": "^2.0.2",
|
||||
"jsdoc-to-markdown": "^8.0.0",
|
||||
"lerna": "^8.0.1",
|
||||
"prettier": "^3.1.1",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"vitest": "^1.1.0"
|
||||
"lerna": "^8.1.9",
|
||||
"prettier": "^3.4.2",
|
||||
"vitest": "^3.0.4",
|
||||
"vite-plugin-bundle-audioworklet": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { closeBrackets } from '@codemirror/autocomplete';
|
||||
export { toggleComment, toggleBlockComment, toggleLineComment, toggleBlockCommentByLine } from '@codemirror/commands';
|
||||
// import { search, highlightSelectionMatches } from '@codemirror/search';
|
||||
import { history } from '@codemirror/commands';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
@@ -12,7 +13,7 @@ import {
|
||||
lineNumbers,
|
||||
drawSelection,
|
||||
} from '@codemirror/view';
|
||||
import { Pattern, repl } from '@strudel/core';
|
||||
import { repl, registerControl } from '@strudel/core';
|
||||
import { Drawer, cleanupDraw } from '@strudel/draw';
|
||||
import { isAutoCompletionEnabled } from './autocomplete.mjs';
|
||||
import { isTooltipEnabled } from './tooltip.mjs';
|
||||
@@ -66,6 +67,7 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
||||
const initialSettings = Object.keys(compartments).map((key) =>
|
||||
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
|
||||
);
|
||||
|
||||
initTheme(settings.theme);
|
||||
let state = EditorState.create({
|
||||
doc: initialCode,
|
||||
@@ -98,10 +100,8 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
||||
},
|
||||
{
|
||||
key: 'Alt-.',
|
||||
run: (_, e) => {
|
||||
e.preventDefault();
|
||||
onStop?.();
|
||||
},
|
||||
preventDefault: true,
|
||||
run: () => onStop?.(),
|
||||
},
|
||||
/* {
|
||||
key: 'Ctrl-Shift-.',
|
||||
@@ -134,6 +134,7 @@ export class StrudelMirror {
|
||||
autodraw,
|
||||
prebake,
|
||||
bgFill = true,
|
||||
solo = true,
|
||||
...replOptions
|
||||
} = options;
|
||||
this.code = initialCode;
|
||||
@@ -144,6 +145,7 @@ export class StrudelMirror {
|
||||
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));
|
||||
@@ -160,12 +162,14 @@ export class StrudelMirror {
|
||||
replOptions?.onToggle?.(started);
|
||||
if (started) {
|
||||
this.drawer.start(this.repl.scheduler);
|
||||
// stop other repls when this one is started
|
||||
document.dispatchEvent(
|
||||
new CustomEvent('start-repl', {
|
||||
detail: this.id,
|
||||
}),
|
||||
);
|
||||
if (this.solo) {
|
||||
// stop other repls when this one is started
|
||||
document.dispatchEvent(
|
||||
new CustomEvent('start-repl', {
|
||||
detail: this.id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.drawer.stop();
|
||||
updateMiniLocations(this.editor, []);
|
||||
@@ -220,7 +224,7 @@ export class StrudelMirror {
|
||||
|
||||
// stop this repl when another repl is started
|
||||
this.onStartRepl = (e) => {
|
||||
if (e.detail !== this.id) {
|
||||
if (this.solo && e.detail !== this.id) {
|
||||
this.stop();
|
||||
}
|
||||
};
|
||||
@@ -355,3 +359,12 @@ function s4() {
|
||||
.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,7 +11,9 @@ 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: #FFCA2880` } });
|
||||
const mark = Decoration.mark({
|
||||
attributes: { style: `background-color: rgba(255,255,255, .4); filter: invert(10%)` },
|
||||
});
|
||||
flash = Decoration.set([mark.range(0, tr.newDoc.length)]);
|
||||
} else {
|
||||
flash = Decoration.set([]);
|
||||
|
||||
@@ -93,6 +93,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}`;
|
||||
// Get explicit channels for color values
|
||||
/*
|
||||
const swatch = document.createElement('div');
|
||||
@@ -114,7 +115,7 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
|
||||
to,
|
||||
Decoration.mark({
|
||||
// attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` },
|
||||
attributes: { style: `outline: solid 2px ${color}` },
|
||||
attributes: { style },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/codemirror",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Codemirror Extensions for Strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
@@ -32,26 +32,24 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.11.1",
|
||||
"@codemirror/commands": "^6.3.3",
|
||||
"@codemirror/lang-javascript": "^6.2.1",
|
||||
"@codemirror/language": "^6.10.0",
|
||||
"@codemirror/search": "^6.5.5",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
"@lezer/highlight": "^1.2.0",
|
||||
"@nanostores/persistent": "^0.9.1",
|
||||
"@replit/codemirror-emacs": "^6.0.1",
|
||||
"@replit/codemirror-vim": "^6.1.0",
|
||||
"@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.2.1",
|
||||
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
||||
"@strudel/core": "workspace:*",
|
||||
"@strudel/draw": "workspace:*",
|
||||
"@strudel/transpiler": "workspace:*",
|
||||
"@uiw/codemirror-themes": "^4.21.21",
|
||||
"@uiw/codemirror-themes-all": "^4.21.21",
|
||||
"nanostores": "^0.9.5"
|
||||
"nanostores": "^0.11.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+84
-407
@@ -1,440 +1,117 @@
|
||||
import {
|
||||
abcdef,
|
||||
androidstudio,
|
||||
atomone,
|
||||
aura,
|
||||
bespin,
|
||||
darcula,
|
||||
dracula,
|
||||
duotoneDark,
|
||||
eclipse,
|
||||
githubDark,
|
||||
gruvboxDark,
|
||||
materialDark,
|
||||
nord,
|
||||
okaidia,
|
||||
solarizedDark,
|
||||
sublime,
|
||||
tokyoNight,
|
||||
tokyoNightStorm,
|
||||
vscodeDark,
|
||||
xcodeDark,
|
||||
bbedit,
|
||||
duotoneLight,
|
||||
githubLight,
|
||||
gruvboxLight,
|
||||
materialLight,
|
||||
noctisLilac,
|
||||
solarizedLight,
|
||||
tokyoNightDay,
|
||||
xcodeLight,
|
||||
} from '@uiw/codemirror-themes-all';
|
||||
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 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 strudelTheme from './themes/strudel-theme';
|
||||
import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen';
|
||||
import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen';
|
||||
import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen';
|
||||
import teletext, { settings as teletextSettings } from './themes/teletext';
|
||||
import algoboy, { settings as algoboySettings } from './themes/algoboy';
|
||||
import terminal, { settings as terminalSettings } from './themes/terminal';
|
||||
import { setTheme } from '@strudel/draw';
|
||||
|
||||
export const themes = {
|
||||
strudelTheme,
|
||||
bluescreen,
|
||||
blackscreen,
|
||||
whitescreen,
|
||||
teletext,
|
||||
algoboy,
|
||||
terminal,
|
||||
abcdef,
|
||||
androidstudio,
|
||||
atomone,
|
||||
aura,
|
||||
bespin,
|
||||
bbedit,
|
||||
blackscreen,
|
||||
bluescreen,
|
||||
CutiePi,
|
||||
darcula,
|
||||
dracula,
|
||||
duotoneDark,
|
||||
eclipse,
|
||||
githubDark,
|
||||
githubLight,
|
||||
greenText,
|
||||
gruvboxDark,
|
||||
gruvboxLight,
|
||||
sonicPink,
|
||||
materialDark,
|
||||
materialLight,
|
||||
monokai,
|
||||
noctisLilac,
|
||||
nord,
|
||||
okaidia,
|
||||
redText,
|
||||
solarizedDark,
|
||||
solarizedLight,
|
||||
sublime,
|
||||
teletext,
|
||||
tokyoNight,
|
||||
tokyoNightDay,
|
||||
tokyoNightStorm,
|
||||
vscodeDark,
|
||||
xcodeDark,
|
||||
bbedit,
|
||||
duotoneLight,
|
||||
githubLight,
|
||||
gruvboxLight,
|
||||
materialLight,
|
||||
noctisLilac,
|
||||
solarizedLight,
|
||||
tokyoNightDay,
|
||||
vscodeLight,
|
||||
whitescreen,
|
||||
xcodeLight,
|
||||
};
|
||||
|
||||
// lineBackground is background with 50% opacity, to make sure the selection below is visible
|
||||
|
||||
export const settings = {
|
||||
strudelTheme: {
|
||||
background: '#222',
|
||||
lineBackground: '#22222299',
|
||||
foreground: '#fff',
|
||||
// foreground: '#75baff',
|
||||
caret: '#ffcc00',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#036dd626',
|
||||
// lineHighlight: '#8a91991a', // original
|
||||
lineHighlight: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
// gutterForeground: '#8a919966',
|
||||
gutterForeground: '#8a919966',
|
||||
},
|
||||
strudelTheme: strudelThemeSettings,
|
||||
bluescreen: bluescreenSettings,
|
||||
blackscreen: blackscreenSettings,
|
||||
whitescreen: whitescreenSettings,
|
||||
teletext: teletextSettings,
|
||||
algoboy: algoboySettings,
|
||||
terminal: terminalSettings,
|
||||
abcdef: {
|
||||
background: '#0f0f0f',
|
||||
lineBackground: '#0f0f0f99',
|
||||
foreground: '#defdef',
|
||||
caret: '#00FF00',
|
||||
selection: '#515151',
|
||||
selectionMatch: '#515151',
|
||||
gutterBackground: '#555',
|
||||
gutterForeground: '#FFFFFF',
|
||||
lineHighlight: '#314151',
|
||||
},
|
||||
androidstudio: {
|
||||
background: '#282b2e',
|
||||
lineBackground: '#282b2e99',
|
||||
foreground: '#a9b7c6',
|
||||
caret: '#00FF00',
|
||||
selection: '#343739',
|
||||
selectionMatch: '#343739',
|
||||
lineHighlight: '#343739',
|
||||
},
|
||||
atomone: {
|
||||
background: '#272C35',
|
||||
lineBackground: '#272C3599',
|
||||
foreground: '#9d9b97',
|
||||
caret: '#797977',
|
||||
selection: '#ffffff30',
|
||||
selectionMatch: '#2B323D',
|
||||
gutterBackground: '#272C35',
|
||||
gutterForeground: '#465063',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#2B323D',
|
||||
},
|
||||
aura: {
|
||||
background: '#21202e',
|
||||
lineBackground: '#21202e99',
|
||||
foreground: '#edecee',
|
||||
caret: '#a277ff',
|
||||
selection: '#3d375e7f',
|
||||
selectionMatch: '#3d375e7f',
|
||||
gutterBackground: '#21202e',
|
||||
gutterForeground: '#edecee',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#a394f033',
|
||||
},
|
||||
bbedit: {
|
||||
light: true,
|
||||
background: '#FFFFFF',
|
||||
lineBackground: '#FFFFFF99',
|
||||
foreground: '#000000',
|
||||
caret: '#FBAC52',
|
||||
selection: '#FFD420',
|
||||
selectionMatch: '#FFD420',
|
||||
gutterBackground: '#f5f5f5',
|
||||
gutterForeground: '#4D4D4C',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#00000012',
|
||||
},
|
||||
bespin: {
|
||||
background: '#28211c',
|
||||
lineBackground: '#28211c99',
|
||||
foreground: '#9d9b97',
|
||||
caret: '#797977',
|
||||
selection: '#36312e',
|
||||
selectionMatch: '#4f382b',
|
||||
gutterBackground: '#28211c',
|
||||
gutterForeground: '#666666',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
darcula: {
|
||||
background: '#2B2B2B',
|
||||
lineBackground: '#2B2B2B99',
|
||||
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)',
|
||||
},
|
||||
dracula: {
|
||||
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: '#6D8A88',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
duotoneLight: {
|
||||
light: true,
|
||||
background: '#faf8f5',
|
||||
lineBackground: '#faf8f599',
|
||||
foreground: '#b29762',
|
||||
caret: '#93abdc',
|
||||
selection: '#e3dcce',
|
||||
selectionMatch: '#e3dcce',
|
||||
gutterBackground: '#faf8f5',
|
||||
gutterForeground: '#cdc4b1',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#EFEFEF',
|
||||
},
|
||||
duotoneDark: {
|
||||
background: '#2a2734',
|
||||
lineBackground: '#2a273499',
|
||||
foreground: '#6c6783',
|
||||
caret: '#ffad5c',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
gutterBackground: '#2a2734',
|
||||
gutterForeground: '#545167',
|
||||
lineHighlight: '#36334280',
|
||||
},
|
||||
eclipse: {
|
||||
light: true,
|
||||
background: '#fff',
|
||||
lineBackground: '#ffffff99',
|
||||
foreground: '#000',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#d7d4f0',
|
||||
selectionMatch: '#d7d4f0',
|
||||
gutterBackground: '#f7f7f7',
|
||||
gutterForeground: '#999',
|
||||
lineHighlight: '#e8f2ff',
|
||||
gutterBorder: 'transparent',
|
||||
},
|
||||
githubLight: {
|
||||
light: true,
|
||||
background: '#fff',
|
||||
lineBackground: '#ffffff99',
|
||||
foreground: '#24292e',
|
||||
selection: '#BBDFFF',
|
||||
selectionMatch: '#BBDFFF',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#6e7781',
|
||||
},
|
||||
githubDark: {
|
||||
background: '#0d1117',
|
||||
lineBackground: '#0d111799',
|
||||
foreground: '#c9d1d9',
|
||||
caret: '#c9d1d9',
|
||||
selection: '#003d73',
|
||||
selectionMatch: '#003d73',
|
||||
lineHighlight: '#36334280',
|
||||
},
|
||||
gruvboxDark: {
|
||||
background: '#282828',
|
||||
lineBackground: '#28282899',
|
||||
foreground: '#ebdbb2',
|
||||
caret: '#ebdbb2',
|
||||
selection: '#bdae93',
|
||||
selectionMatch: '#bdae93',
|
||||
lineHighlight: '#3c3836',
|
||||
gutterBackground: '#282828',
|
||||
gutterForeground: '#7c6f64',
|
||||
},
|
||||
gruvboxLight: {
|
||||
light: true,
|
||||
background: '#fbf1c7',
|
||||
lineBackground: '#fbf1c799',
|
||||
foreground: '#3c3836',
|
||||
caret: '#af3a03',
|
||||
selection: '#ebdbb2',
|
||||
selectionMatch: '#bdae93',
|
||||
lineHighlight: '#ebdbb2',
|
||||
gutterBackground: '#ebdbb2',
|
||||
gutterForeground: '#665c54',
|
||||
gutterBorder: 'transparent',
|
||||
},
|
||||
materialDark: {
|
||||
background: '#2e3235',
|
||||
lineBackground: '#2e323599',
|
||||
foreground: '#bdbdbd',
|
||||
caret: '#a0a4ae',
|
||||
selection: '#d7d4f0',
|
||||
selectionMatch: '#d7d4f0',
|
||||
gutterBackground: '#2e3235',
|
||||
gutterForeground: '#999',
|
||||
gutterActiveForeground: '#4f5b66',
|
||||
lineHighlight: '#545b61',
|
||||
},
|
||||
materialLight: {
|
||||
light: true,
|
||||
background: '#FAFAFA',
|
||||
lineBackground: '#FAFAFA99',
|
||||
foreground: '#90A4AE',
|
||||
caret: '#272727',
|
||||
selection: '#80CBC440',
|
||||
selectionMatch: '#FAFAFA',
|
||||
gutterBackground: '#FAFAFA',
|
||||
gutterForeground: '#90A4AE',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#CCD7DA50',
|
||||
},
|
||||
noctisLilac: {
|
||||
light: true,
|
||||
background: '#f2f1f8',
|
||||
lineBackground: '#f2f1f899',
|
||||
foreground: '#0c006b',
|
||||
caret: '#5c49e9',
|
||||
selection: '#d5d1f2',
|
||||
selectionMatch: '#d5d1f2',
|
||||
gutterBackground: '#f2f1f8',
|
||||
gutterForeground: '#0c006b70',
|
||||
lineHighlight: '#e1def3',
|
||||
},
|
||||
nord: {
|
||||
background: '#2e3440',
|
||||
lineBackground: '#2e344099',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#3b4252',
|
||||
selectionMatch: '#e5e9f0',
|
||||
gutterBackground: '#2e3440',
|
||||
gutterForeground: '#4c566a',
|
||||
gutterActiveForeground: '#d8dee9',
|
||||
lineHighlight: '#4c566a',
|
||||
},
|
||||
okaidia: {
|
||||
background: '#272822',
|
||||
lineBackground: '#27282299',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#49483E',
|
||||
selectionMatch: '#49483E',
|
||||
gutterBackground: '#272822',
|
||||
gutterForeground: '#FFFFFF70',
|
||||
lineHighlight: '#00000059',
|
||||
},
|
||||
solarizedLight: {
|
||||
light: true,
|
||||
background: '#fdf6e3',
|
||||
lineBackground: '#fdf6e399',
|
||||
foreground: '#657b83',
|
||||
caret: '#586e75',
|
||||
selection: '#dfd9c8',
|
||||
selectionMatch: '#dfd9c8',
|
||||
gutterBackground: '#00000010',
|
||||
gutterForeground: '#657b83',
|
||||
lineHighlight: '#dfd9c8',
|
||||
},
|
||||
solarizedDark: {
|
||||
background: '#002b36',
|
||||
lineBackground: '#002b3699',
|
||||
foreground: '#93a1a1',
|
||||
caret: '#839496',
|
||||
selection: '#173541',
|
||||
selectionMatch: '#aafe661a',
|
||||
gutterBackground: '#00252f',
|
||||
gutterForeground: '#839496',
|
||||
lineHighlight: '#173541',
|
||||
},
|
||||
sublime: {
|
||||
background: '#303841',
|
||||
lineBackground: '#30384199',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FBAC52',
|
||||
selection: '#4C5964',
|
||||
selectionMatch: '#3A546E',
|
||||
gutterBackground: '#303841',
|
||||
gutterForeground: '#FFFFFF70',
|
||||
lineHighlight: '#00000059',
|
||||
},
|
||||
tokyoNightDay: {
|
||||
light: true,
|
||||
background: '#e1e2e7',
|
||||
lineBackground: '#e1e2e799',
|
||||
foreground: '#3760bf',
|
||||
caret: '#3760bf',
|
||||
selection: '#99a7df',
|
||||
selectionMatch: '#99a7df',
|
||||
gutterBackground: '#e1e2e7',
|
||||
gutterForeground: '#3760bf',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#5f5faf11',
|
||||
},
|
||||
tokyoNightStorm: {
|
||||
background: '#24283b',
|
||||
lineBackground: '#24283b99',
|
||||
foreground: '#7982a9',
|
||||
caret: '#c0caf5',
|
||||
selection: '#6f7bb630',
|
||||
selectionMatch: '#1f2335',
|
||||
gutterBackground: '#24283b',
|
||||
gutterForeground: '#7982a9',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#292e42',
|
||||
},
|
||||
tokyoNight: {
|
||||
background: '#1a1b26',
|
||||
lineBackground: '#1a1b2699',
|
||||
foreground: '#787c99',
|
||||
caret: '#c0caf5',
|
||||
selection: '#515c7e40',
|
||||
selectionMatch: '#16161e',
|
||||
gutterBackground: '#1a1b26',
|
||||
gutterForeground: '#787c99',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#1e202e',
|
||||
},
|
||||
vscodeDark: {
|
||||
background: '#1e1e1e',
|
||||
lineBackground: '#1e1e1e99',
|
||||
foreground: '#9cdcfe',
|
||||
caret: '#c6c6c6',
|
||||
selection: '#6199ff2f',
|
||||
selectionMatch: '#72a1ff59',
|
||||
lineHighlight: '#ffffff0f',
|
||||
gutterBackground: '#1e1e1e',
|
||||
gutterForeground: '#838383',
|
||||
gutterActiveForeground: '#fff',
|
||||
},
|
||||
xcodeLight: {
|
||||
light: true,
|
||||
background: '#fff',
|
||||
lineBackground: '#ffffff99',
|
||||
foreground: '#3D3D3D',
|
||||
selection: '#BBDFFF',
|
||||
selectionMatch: '#BBDFFF',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#AFAFAF',
|
||||
lineHighlight: '#EDF4FF',
|
||||
},
|
||||
xcodeDark: {
|
||||
background: '#292A30',
|
||||
lineBackground: '#292A3099',
|
||||
foreground: '#CECFD0',
|
||||
caret: '#fff',
|
||||
selection: '#727377',
|
||||
selectionMatch: '#727377',
|
||||
lineHighlight: '#2F3239',
|
||||
},
|
||||
androidstudio: androidstudioSettings,
|
||||
atomone: atomOneSettings,
|
||||
aura: auraSettings,
|
||||
bbedit: bbeditSettings,
|
||||
darcula: darculaSettings,
|
||||
dracula: draculaSettings,
|
||||
duotoneDark: duotoneDarkSettings,
|
||||
eclipse: eclipseSettings,
|
||||
CutiePi: CutiePiSettings,
|
||||
sonicPink: sonicPinkSettings,
|
||||
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) {
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 },
|
||||
],
|
||||
});
|
||||
+50
-31
@@ -1,42 +1,61 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
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: '#9bbc0f',
|
||||
foreground: '#0f380f', // whats that?
|
||||
caret: '#0f380f',
|
||||
selection: '#306230',
|
||||
selectionMatch: '#ffffff26',
|
||||
lineHighlight: '#8bac0f',
|
||||
lineBackground: '#9bbc0f50',
|
||||
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: '#0f380f',
|
||||
light: true,
|
||||
gutterForeground: palette[0],
|
||||
light: false,
|
||||
// customStyle: '.cm-line { line-height: 1 }',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.labelName, color: '#0f380f' },
|
||||
{ tag: t.keyword, color: '#0f380f' },
|
||||
{ tag: t.operator, color: '#0f380f' },
|
||||
{ tag: t.special(t.variableName), color: '#0f380f' },
|
||||
{ tag: t.typeName, color: '#0f380f' },
|
||||
{ tag: t.atom, color: '#0f380f' },
|
||||
{ tag: t.number, color: '#0f380f' },
|
||||
{ tag: t.definition(t.variableName), color: '#0f380f' },
|
||||
{ tag: t.string, color: '#0f380f' },
|
||||
{ tag: t.special(t.string), color: '#0f380f' },
|
||||
{ tag: t.comment, color: '#0f380f' },
|
||||
{ tag: t.variableName, color: '#0f380f' },
|
||||
{ tag: t.tagName, color: '#0f380f' },
|
||||
{ tag: t.bracket, color: '#0f380f' },
|
||||
{ tag: t.meta, color: '#0f380f' },
|
||||
{ tag: t.attributeName, color: '#0f380f' },
|
||||
{ tag: t.propertyName, color: '#0f380f' },
|
||||
{ tag: t.className, color: '#0f380f' },
|
||||
{ tag: t.invalid, color: '#0f380f' },
|
||||
{ tag: [t.unit, t.punctuation], color: '#0f380f' },
|
||||
{ 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
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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' },
|
||||
],
|
||||
});
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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
@@ -0,0 +1,51 @@
|
||||
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
@@ -0,0 +1,46 @@
|
||||
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' },
|
||||
],
|
||||
});
|
||||
+21
-21
@@ -1,5 +1,5 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
export const settings = {
|
||||
background: 'black',
|
||||
foreground: 'white', // whats that?
|
||||
@@ -15,25 +15,25 @@ export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.labelName, color: 'white' },
|
||||
{ tag: t.keyword, color: 'white' },
|
||||
{ tag: t.operator, color: 'white' },
|
||||
{ tag: t.special(t.variableName), color: 'white' },
|
||||
{ tag: t.typeName, color: 'white' },
|
||||
{ tag: t.atom, color: 'white' },
|
||||
{ tag: t.number, color: 'white' },
|
||||
{ tag: t.definition(t.variableName), color: 'white' },
|
||||
{ tag: t.string, color: 'white' },
|
||||
{ tag: t.special(t.string), color: 'white' },
|
||||
{ tag: t.comment, color: 'white' },
|
||||
{ tag: t.variableName, color: 'white' },
|
||||
{ tag: t.tagName, color: 'white' },
|
||||
{ tag: t.bracket, color: 'white' },
|
||||
{ tag: t.meta, color: 'white' },
|
||||
{ tag: t.attributeName, color: 'white' },
|
||||
{ tag: t.propertyName, color: 'white' },
|
||||
{ tag: t.className, color: 'white' },
|
||||
{ tag: t.invalid, color: 'white' },
|
||||
{ tag: [t.unit, t.punctuation], color: 'white' },
|
||||
{ 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' },
|
||||
],
|
||||
});
|
||||
|
||||
+21
-21
@@ -1,5 +1,5 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
export const settings = {
|
||||
background: '#051DB5',
|
||||
lineBackground: '#051DB550',
|
||||
@@ -18,25 +18,25 @@ export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.labelName, color: 'white' },
|
||||
{ tag: t.keyword, color: 'white' },
|
||||
{ tag: t.operator, color: 'white' },
|
||||
{ tag: t.special(t.variableName), color: 'white' },
|
||||
{ tag: t.typeName, color: 'white' },
|
||||
{ tag: t.atom, color: 'white' },
|
||||
{ tag: t.number, color: 'white' },
|
||||
{ tag: t.definition(t.variableName), color: 'white' },
|
||||
{ tag: t.string, color: 'white' },
|
||||
{ tag: t.special(t.string), color: 'white' },
|
||||
{ tag: t.comment, color: 'white' },
|
||||
{ tag: t.variableName, color: 'white' },
|
||||
{ tag: t.tagName, color: 'white' },
|
||||
{ tag: t.bracket, color: 'white' },
|
||||
{ tag: t.meta, color: 'white' },
|
||||
{ tag: t.attributeName, color: 'white' },
|
||||
{ tag: t.propertyName, color: 'white' },
|
||||
{ tag: t.className, color: 'white' },
|
||||
{ tag: t.invalid, color: 'white' },
|
||||
{ tag: [t.unit, t.punctuation], color: 'white' },
|
||||
{ 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' },
|
||||
],
|
||||
});
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* @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
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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
@@ -0,0 +1,46 @@
|
||||
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' },
|
||||
],
|
||||
});
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
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
@@ -0,0 +1,42 @@
|
||||
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
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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
@@ -0,0 +1,77 @@
|
||||
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
@@ -0,0 +1,52 @@
|
||||
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
@@ -0,0 +1,45 @@
|
||||
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
@@ -0,0 +1,50 @@
|
||||
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
@@ -0,0 +1,78 @@
|
||||
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` },
|
||||
],
|
||||
});
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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
@@ -0,0 +1,79 @@
|
||||
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
@@ -0,0 +1,82 @@
|
||||
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
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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] },
|
||||
],
|
||||
});
|
||||
+16
-13
@@ -1,20 +1,23 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#222',
|
||||
lineBackground: '#22222299',
|
||||
foreground: '#fff',
|
||||
caret: '#ffcc00',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#036dd626',
|
||||
lineHighlight: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#222',
|
||||
foreground: '#75baff', // whats that?
|
||||
caret: '#ffcc00',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#036dd626',
|
||||
// lineHighlight: '#8a91991a', // original
|
||||
lineHighlight: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
// gutterForeground: '#8a919966',
|
||||
gutterForeground: '#8a919966',
|
||||
},
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#89ddff' },
|
||||
{ tag: t.labelName, color: '#89ddff' },
|
||||
{ tag: t.keyword, color: '#c792ea' },
|
||||
{ tag: t.operator, color: '#89ddff' },
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
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' },
|
||||
],
|
||||
});
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
let colorA = '#6edee4';
|
||||
//let colorB = 'magenta';
|
||||
|
||||
+20
-20
@@ -1,5 +1,5 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
export const settings = {
|
||||
background: 'black',
|
||||
foreground: '#41FF00', // whats that?
|
||||
@@ -14,24 +14,24 @@ export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.labelName, color: '#41FF00' },
|
||||
{ tag: t.keyword, color: '#41FF00' },
|
||||
{ tag: t.operator, color: '#41FF00' },
|
||||
{ tag: t.special(t.variableName), color: '#41FF00' },
|
||||
{ tag: t.typeName, color: '#41FF00' },
|
||||
{ tag: t.atom, color: '#41FF00' },
|
||||
{ tag: t.number, color: '#41FF00' },
|
||||
{ tag: t.definition(t.variableName), color: '#41FF00' },
|
||||
{ tag: t.string, color: '#41FF00' },
|
||||
{ tag: t.special(t.string), color: '#41FF00' },
|
||||
{ tag: t.comment, color: '#41FF00' },
|
||||
{ tag: t.variableName, color: '#41FF00' },
|
||||
{ tag: t.tagName, color: '#41FF00' },
|
||||
{ tag: t.bracket, color: '#41FF00' },
|
||||
{ tag: t.meta, color: '#41FF00' },
|
||||
{ tag: t.attributeName, color: '#41FF00' },
|
||||
{ tag: t.propertyName, color: '#41FF00' },
|
||||
{ tag: t.className, color: '#41FF00' },
|
||||
{ tag: t.invalid, color: '#41FF00' },
|
||||
{ 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
@@ -0,0 +1,42 @@
|
||||
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
@@ -0,0 +1,52 @@
|
||||
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' },
|
||||
],
|
||||
});
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#1a1b26',
|
||||
lineBackground: '#1a1b2699',
|
||||
foreground: '#787c99',
|
||||
caret: '#c0caf5',
|
||||
selection: '#515c7e40',
|
||||
selectionMatch: '#16161e',
|
||||
gutterBackground: '#1a1b26',
|
||||
gutterForeground: '#787c99',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#1e202e',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#1a1b26',
|
||||
foreground: '#787c99',
|
||||
caret: '#c0caf5',
|
||||
selection: '#515c7e40',
|
||||
selectionMatch: '#16161e',
|
||||
gutterBackground: '#1a1b26',
|
||||
gutterForeground: '#787c99',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#474b6611',
|
||||
},
|
||||
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: '#0db9d7' },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: '#bb9af7' },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#b4f9f8' },
|
||||
{ tag: [t.meta, t.comment], color: '#444b6a' },
|
||||
{ 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' },
|
||||
],
|
||||
});
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#e1e2e7',
|
||||
lineBackground: '#e1e2e799',
|
||||
foreground: '#3760bf',
|
||||
caret: '#3760bf',
|
||||
selection: '#99a7df',
|
||||
selectionMatch: '#99a7df',
|
||||
gutterBackground: '#e1e2e7',
|
||||
gutterForeground: '#3760bf',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#5f5faf11',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#e1e2e7',
|
||||
foreground: '#3760bf',
|
||||
caret: '#3760bf',
|
||||
selection: '#99a7df',
|
||||
selectionMatch: '#99a7df',
|
||||
gutterBackground: '#e1e2e7',
|
||||
gutterForeground: '#3760bf',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#5f5faf11',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#007197' },
|
||||
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#3760bf' },
|
||||
{ tag: [t.propertyName], color: '#3760bf' },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#587539' },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: '#3760bf' },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#3760bf' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#3760bf' },
|
||||
{ tag: [t.className], color: '#3760bf' },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#b15c00' },
|
||||
{ tag: [t.typeName], color: '#007197', fontStyle: '#007197' },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: '#007197' },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#587539' },
|
||||
{ tag: [t.meta, t.comment], color: '#848cb5' },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#b15c00' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#3760bf' },
|
||||
{ tag: t.invalid, color: '#f52a65' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
],
|
||||
});
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#1e1e1e',
|
||||
lineBackground: '#1e1e1e99',
|
||||
foreground: '#fff',
|
||||
caret: '#c6c6c6',
|
||||
selection: '#6199ff2f',
|
||||
selectionMatch: '#72a1ff59',
|
||||
lineHighlight: '#ffffff0f',
|
||||
gutterBackground: '#1e1e1e',
|
||||
gutterForeground: '#838383',
|
||||
gutterActiveForeground: '#fff',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#1e1e1e',
|
||||
foreground: '#fff',
|
||||
caret: '#c6c6c6',
|
||||
selection: '#6199ff2f',
|
||||
selectionMatch: '#72a1ff59',
|
||||
lineHighlight: '#ffffff0f',
|
||||
gutterBackground: '#1e1e1e',
|
||||
gutterForeground: '#838383',
|
||||
gutterActiveForeground: '#fff',
|
||||
fontFamily: 'Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace',
|
||||
},
|
||||
styles: [
|
||||
{
|
||||
tag: [
|
||||
t.keyword,
|
||||
t.operatorKeyword,
|
||||
t.modifier,
|
||||
t.color,
|
||||
t.constant(t.name),
|
||||
t.standard(t.name),
|
||||
t.standard(t.tagName),
|
||||
t.special(t.brace),
|
||||
t.atom,
|
||||
t.bool,
|
||||
t.special(t.variableName),
|
||||
],
|
||||
color: '#569cd6',
|
||||
},
|
||||
{ tag: [t.controlKeyword, t.moduleKeyword], color: '#c586c0' },
|
||||
{
|
||||
tag: [
|
||||
t.name,
|
||||
t.deleted,
|
||||
t.character,
|
||||
t.macroName,
|
||||
t.propertyName,
|
||||
t.variableName,
|
||||
t.labelName,
|
||||
t.definition(t.name),
|
||||
],
|
||||
color: '#9cdcfe',
|
||||
},
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#9cdcfe' },
|
||||
{
|
||||
tag: [t.typeName, t.className, t.tagName, t.number, t.changed, t.annotation, t.self, t.namespace],
|
||||
color: '#4ec9b0',
|
||||
},
|
||||
{ tag: [t.function(t.variableName), t.function(t.propertyName)], color: '#dcdcaa' },
|
||||
{ tag: [t.number], color: '#b5cea8' },
|
||||
{ tag: [t.operator, t.punctuation, t.separator, t.url, t.escape, t.regexp], color: '#d4d4d4' },
|
||||
{ tag: [t.regexp], color: '#d16969' },
|
||||
{ tag: [t.special(t.string), t.processingInstruction, t.string, t.inserted], color: '#ce9178' },
|
||||
{ tag: [t.angleBracket], color: '#808080' },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: [t.meta, t.comment], color: '#6a9955' },
|
||||
{ tag: t.link, color: '#6a9955', textDecoration: 'underline' },
|
||||
{ tag: t.invalid, color: '#ff0000' },
|
||||
],
|
||||
});
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#ffffff',
|
||||
lineBackground: '#ffffff50',
|
||||
foreground: '#383a42',
|
||||
caret: '#000',
|
||||
selection: '#add6ff',
|
||||
selectionMatch: '#a8ac94',
|
||||
lineHighlight: '#99999926',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#237893',
|
||||
gutterActiveForeground: '#0b216f',
|
||||
fontFamily: 'Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#ffffff',
|
||||
foreground: '#383a42',
|
||||
caret: '#000',
|
||||
selection: '#add6ff',
|
||||
selectionMatch: '#a8ac94',
|
||||
lineHighlight: '#99999926',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#237893',
|
||||
gutterActiveForeground: '#0b216f',
|
||||
fontFamily: 'Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace',
|
||||
},
|
||||
styles: [
|
||||
{
|
||||
tag: [
|
||||
t.keyword,
|
||||
t.operatorKeyword,
|
||||
t.modifier,
|
||||
t.color,
|
||||
t.constant(t.name),
|
||||
t.standard(t.name),
|
||||
t.standard(t.tagName),
|
||||
t.special(t.brace),
|
||||
t.atom,
|
||||
t.bool,
|
||||
t.special(t.variableName),
|
||||
],
|
||||
color: '#0000ff',
|
||||
},
|
||||
{ tag: [t.moduleKeyword, t.controlKeyword], color: '#af00db' },
|
||||
{
|
||||
tag: [
|
||||
t.name,
|
||||
t.deleted,
|
||||
t.character,
|
||||
t.macroName,
|
||||
t.propertyName,
|
||||
t.variableName,
|
||||
t.labelName,
|
||||
t.definition(t.name),
|
||||
],
|
||||
color: '#0070c1',
|
||||
},
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#0070c1' },
|
||||
{
|
||||
tag: [t.typeName, t.className, t.tagName, t.number, t.changed, t.annotation, t.self, t.namespace],
|
||||
color: '#267f99',
|
||||
},
|
||||
{ tag: [t.function(t.variableName), t.function(t.propertyName)], color: '#795e26' },
|
||||
{ tag: [t.number], color: '#098658' },
|
||||
{ tag: [t.operator, t.punctuation, t.separator, t.url, t.escape, t.regexp], color: '#383a42' },
|
||||
{ tag: [t.regexp], color: '#af00db' },
|
||||
{ tag: [t.special(t.string), t.processingInstruction, t.string, t.inserted], color: '#a31515' },
|
||||
{ tag: [t.angleBracket], color: '#383a42' },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: [t.meta, t.comment], color: '#008000' },
|
||||
{ tag: t.link, color: '#4078f2', textDecoration: 'underline' },
|
||||
{ tag: t.invalid, color: '#e45649' },
|
||||
],
|
||||
});
|
||||
+20
-20
@@ -1,5 +1,5 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
export const settings = {
|
||||
background: 'white',
|
||||
foreground: 'black', // whats that?
|
||||
@@ -16,24 +16,24 @@ export default createTheme({
|
||||
theme: 'light',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.labelName, color: 'black' },
|
||||
{ tag: t.keyword, color: 'black' },
|
||||
{ tag: t.operator, color: 'black' },
|
||||
{ tag: t.special(t.variableName), color: 'black' },
|
||||
{ tag: t.typeName, color: 'black' },
|
||||
{ tag: t.atom, color: 'black' },
|
||||
{ tag: t.number, color: 'black' },
|
||||
{ tag: t.definition(t.variableName), color: 'black' },
|
||||
{ tag: t.string, color: 'black' },
|
||||
{ tag: t.special(t.string), color: 'black' },
|
||||
{ tag: t.comment, color: 'black' },
|
||||
{ tag: t.variableName, color: 'black' },
|
||||
{ tag: t.tagName, color: 'black' },
|
||||
{ tag: t.bracket, color: 'black' },
|
||||
{ tag: t.meta, color: 'black' },
|
||||
{ tag: t.attributeName, color: 'black' },
|
||||
{ tag: t.propertyName, color: 'black' },
|
||||
{ tag: t.className, color: 'black' },
|
||||
{ tag: t.invalid, color: 'black' },
|
||||
{ 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' },
|
||||
],
|
||||
});
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#fff',
|
||||
lineBackground: '#ffffff99',
|
||||
foreground: '#3D3D3D',
|
||||
selection: '#BBDFFF',
|
||||
selectionMatch: '#BBDFFF',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#AFAFAF',
|
||||
lineHighlight: '#EDF4FF',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#fff',
|
||||
foreground: '#3D3D3D',
|
||||
selection: '#BBDFFF',
|
||||
selectionMatch: '#BBDFFF',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#AFAFAF',
|
||||
lineHighlight: '#d5e6ff69',
|
||||
},
|
||||
styles: [
|
||||
{ tag: [t.comment, t.quote], color: '#707F8D' },
|
||||
{ tag: [t.typeName, t.typeOperator], color: '#aa0d91' },
|
||||
{ tag: [t.keyword], color: '#aa0d91', fontWeight: 'bold' },
|
||||
{ tag: [t.string, t.meta], color: '#D23423' },
|
||||
{ tag: [t.name], color: '#032f62' },
|
||||
{ tag: [t.typeName], color: '#522BB2' },
|
||||
{ tag: [t.variableName], color: '#23575C' },
|
||||
{ tag: [t.definition(t.variableName)], color: '#327A9E' },
|
||||
{ tag: [t.regexp, t.link], color: '#0e0eff' },
|
||||
],
|
||||
});
|
||||
@@ -133,3 +133,10 @@ registerWidget('_pitchwheel', (id, options = {}, pat) => {
|
||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||
return pat.pitchwheel({ ...options, ctx, id });
|
||||
});
|
||||
|
||||
registerWidget('_spectrum', (id, options = {}, pat) => {
|
||||
let _size = options.size || 200;
|
||||
options = { width: _size, height: _size, ...options, size: _size / 5 };
|
||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||
return pat.spectrum({ ...options, ctx, id });
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import { calculateTactus, sequence, stack } from '../index.mjs';
|
||||
|
||||
const pat64 = sequence(...Array(64).keys());
|
||||
|
||||
describe('tactus', () => {
|
||||
describe('steps', () => {
|
||||
calculateTactus(true);
|
||||
bench(
|
||||
'+tactus',
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
// import createClock from './zyklus.mjs';
|
||||
|
||||
function getTime() {
|
||||
const precision = 10 ** 4;
|
||||
const seconds = performance.now() / 1000;
|
||||
return Math.round(seconds * precision) / precision;
|
||||
const seconds = performance.now() * 0.001;
|
||||
return seconds;
|
||||
// return Math.round(seconds * precision) / precision;
|
||||
}
|
||||
|
||||
let num_cycles_at_cps_change = 0;
|
||||
@@ -24,27 +24,20 @@ const sendMessage = (type, payload) => {
|
||||
|
||||
const sendTick = (phase, duration, tick, time) => {
|
||||
const num_seconds_since_cps_change = num_ticks_since_cps_change * duration;
|
||||
|
||||
const tickdeadline = phase - time;
|
||||
const lastTick = time + tickdeadline;
|
||||
const num_cycles_since_cps_change = num_seconds_since_cps_change * cps;
|
||||
|
||||
const begin = num_cycles_at_cps_change + num_cycles_since_cps_change;
|
||||
const secondsSinceLastTick = time - lastTick - duration;
|
||||
|
||||
const eventLength = duration * cps;
|
||||
const end = begin + eventLength;
|
||||
|
||||
const cycle = begin + secondsSinceLastTick * cps;
|
||||
|
||||
sendMessage('tick', {
|
||||
begin,
|
||||
end,
|
||||
cps,
|
||||
tickdeadline,
|
||||
num_cycles_at_cps_change,
|
||||
num_seconds_at_cps_change,
|
||||
num_seconds_since_cps_change,
|
||||
time,
|
||||
cycle,
|
||||
});
|
||||
num_ticks_since_cps_change++;
|
||||
|
||||
+284
-17
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
controls.mjs - <short description TODO>
|
||||
controls.mjs - Registers audio controls for pattern manipulation and effects.
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/controls.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -47,12 +47,16 @@ export function createParam(names) {
|
||||
return func;
|
||||
}
|
||||
|
||||
// maps control alias names to the "main" control name
|
||||
const controlAlias = new Map();
|
||||
|
||||
export function registerControl(names, ...aliases) {
|
||||
const name = Array.isArray(names) ? names[0] : names;
|
||||
let bag = {};
|
||||
bag[name] = createParam(names);
|
||||
aliases.forEach((alias) => {
|
||||
bag[alias] = bag[name];
|
||||
controlAlias.set(alias, name);
|
||||
Pattern.prototype[alias] = Pattern.prototype[name];
|
||||
});
|
||||
return bag;
|
||||
@@ -73,8 +77,6 @@ export function registerControl(names, ...aliases) {
|
||||
*/
|
||||
export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound');
|
||||
|
||||
export const { rec } = registerControl(['rec', 'n']);
|
||||
|
||||
/**
|
||||
* Define a custom webaudio node to use as a sound source.
|
||||
*
|
||||
@@ -443,6 +445,32 @@ export const { coarse } = registerControl('coarse');
|
||||
*/
|
||||
export const { drive } = registerControl('drive');
|
||||
|
||||
/**
|
||||
* Create byte beats with custom expressions
|
||||
*
|
||||
* @name byteBeatExpression
|
||||
* @synonyms bbexpr
|
||||
*
|
||||
* @param {number | Pattern} byteBeatExpression bitwise expression for creating bytebeat
|
||||
* @example
|
||||
* s("bytebeat").bbexpr('t*(t>>15^t>>66)')
|
||||
*
|
||||
*/
|
||||
export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr');
|
||||
|
||||
/**
|
||||
* Create byte beats with custom expressions
|
||||
*
|
||||
* @name byteBeatStartTime
|
||||
* @synonyms bbst
|
||||
*
|
||||
* @param {number | Pattern} byteBeatStartTime in samples (t)
|
||||
* @example
|
||||
* note("c3!8".add("{0 0 12 0 7 5 3}%8")).s("bytebeat:5").bbst("<3 1>".mul(10000))._scope()
|
||||
*
|
||||
*/
|
||||
export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime', 'bbst');
|
||||
|
||||
/**
|
||||
* Allows you to set the output channels on the interface
|
||||
*
|
||||
@@ -456,8 +484,40 @@ export const { drive } = registerControl('drive');
|
||||
*/
|
||||
export const { channels, ch } = registerControl('channels', 'ch');
|
||||
|
||||
// superdirt only
|
||||
export const { phaserrate, phasr } = registerControl('phaserrate', 'phasr');
|
||||
/**
|
||||
* controls the pulsewidth of the pulse oscillator
|
||||
*
|
||||
* @name pw
|
||||
* @param {number | Pattern} pulsewidth
|
||||
* @example
|
||||
* note("{f a c e}%16").s("pulse").pw(".8:1:.2")
|
||||
* @example
|
||||
* n(run(8)).scale("D:pentatonic").s("pulse").pw("0 .75 .5 1")
|
||||
*/
|
||||
export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']);
|
||||
|
||||
/**
|
||||
* controls the lfo rate for the pulsewidth of the pulse oscillator
|
||||
*
|
||||
* @name pwrate
|
||||
* @param {number | Pattern} rate
|
||||
* @example
|
||||
* n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>")
|
||||
|
||||
*
|
||||
*/
|
||||
export const { pwrate } = registerControl('pwrate');
|
||||
|
||||
/**
|
||||
* controls the lfo sweep for the pulsewidth of the pulse oscillator
|
||||
*
|
||||
* @name pwsweep
|
||||
* @param {number | Pattern} sweep
|
||||
* @example
|
||||
* n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>")
|
||||
*
|
||||
*/
|
||||
export const { pwsweep } = registerControl('pwsweep');
|
||||
|
||||
/**
|
||||
* Phaser audio effect that approximates popular guitar pedals.
|
||||
@@ -470,7 +530,11 @@ export const { phaserrate, phasr } = registerControl('phaserrate', 'phasr');
|
||||
* .phaser("<1 2 4 8>")
|
||||
*
|
||||
*/
|
||||
export const { phaser, ph } = registerControl(['phaser', 'phaserdepth', 'phasercenter', 'phasersweep'], 'ph');
|
||||
export const { phaserrate, ph, phaser } = registerControl(
|
||||
['phaserrate', 'phaserdepth', 'phasercenter', 'phasersweep'],
|
||||
'ph',
|
||||
'phaser',
|
||||
);
|
||||
|
||||
/**
|
||||
* The frequency sweep range of the lfo for the phaser effect. Defaults to 2000
|
||||
@@ -1385,6 +1449,17 @@ export const { compressorRelease } = registerControl('compressorRelease');
|
||||
*
|
||||
*/
|
||||
export const { speed } = registerControl('speed');
|
||||
|
||||
/**
|
||||
* Changes the speed of sample playback, i.e. a cheap way of changing pitch.
|
||||
*
|
||||
* @name stretch
|
||||
* @param {number | Pattern} factor -inf to inf, negative numbers play the sample backwards.
|
||||
* @example
|
||||
* s("gm_flute").stretch("1 2 .5")
|
||||
*
|
||||
*/
|
||||
export const { stretch } = registerControl('stretch');
|
||||
/**
|
||||
* Used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`.
|
||||
*
|
||||
@@ -1395,6 +1470,7 @@ export const { speed } = registerControl('speed');
|
||||
* @superdirtOnly
|
||||
*
|
||||
*/
|
||||
|
||||
export const { unit } = registerControl('unit');
|
||||
/**
|
||||
* Made by Calum Gunn. Reminiscent of some weird mixture of filter, ring-modulator and pitch-shifter. The SuperCollider manual defines Squiz as:
|
||||
@@ -1446,7 +1522,7 @@ export const { vowel } = registerControl('vowel');
|
||||
* @name waveloss
|
||||
*/
|
||||
export const { waveloss } = registerControl('waveloss');
|
||||
/*
|
||||
/**
|
||||
* Noise crackle density
|
||||
*
|
||||
* @name density
|
||||
@@ -1498,20 +1574,11 @@ export const { scram } = registerControl('scram');
|
||||
export const { binshift } = registerControl('binshift');
|
||||
export const { hbrick } = registerControl('hbrick');
|
||||
export const { lbrick } = registerControl('lbrick');
|
||||
export const { midichan } = registerControl('midichan');
|
||||
export const { control } = registerControl('control');
|
||||
export const { ccn } = registerControl('ccn');
|
||||
export const { ccv } = registerControl('ccv');
|
||||
export const { polyTouch } = registerControl('polyTouch');
|
||||
export const { midibend } = registerControl('midibend');
|
||||
export const { miditouch } = registerControl('miditouch');
|
||||
export const { ctlNum } = registerControl('ctlNum');
|
||||
|
||||
export const { frameRate } = registerControl('frameRate');
|
||||
export const { frames } = registerControl('frames');
|
||||
export const { hours } = registerControl('hours');
|
||||
export const { midicmd } = registerControl('midicmd');
|
||||
export const { minutes } = registerControl('minutes');
|
||||
export const { progNum } = registerControl('progNum');
|
||||
export const { seconds } = registerControl('seconds');
|
||||
export const { songPtr } = registerControl('songPtr');
|
||||
export const { uid } = registerControl('uid');
|
||||
@@ -1603,3 +1670,203 @@ export const ar = register('ar', (t, pat) => {
|
||||
const [attack, release = attack] = t;
|
||||
return pat.set({ attack, release });
|
||||
});
|
||||
|
||||
//MIDI
|
||||
|
||||
/**
|
||||
* MIDI channel: Sets the MIDI channel for the event.
|
||||
*
|
||||
* @name midichan
|
||||
* @param {number | Pattern} channel MIDI channel number (0-15)
|
||||
* @example
|
||||
* note("c4").midichan(1).midi()
|
||||
*/
|
||||
export const { midichan } = registerControl('midichan');
|
||||
|
||||
export const { midimap } = registerControl('midimap');
|
||||
|
||||
/**
|
||||
* MIDI port: Sets the MIDI port for the event.
|
||||
*
|
||||
* @name midiport
|
||||
* @param {number | Pattern} port MIDI port
|
||||
* @example
|
||||
* note("c a f e").midiport("<0 1 2 3>").midi()
|
||||
*/
|
||||
export const { midiport } = registerControl('midiport');
|
||||
|
||||
/**
|
||||
* MIDI command: Sends a MIDI command message.
|
||||
*
|
||||
* @name midicmd
|
||||
* @param {number | Pattern} command MIDI command
|
||||
* @example
|
||||
* midicmd("clock*48,<start stop>/2").midi()
|
||||
*/
|
||||
export const { midicmd } = registerControl('midicmd');
|
||||
|
||||
/**
|
||||
* MIDI control: Sends a MIDI control change message.
|
||||
*
|
||||
* @name control
|
||||
* @param {number | Pattern} MIDI control number (0-127)
|
||||
* @param {number | Pattern} MIDI controller value (0-127)
|
||||
*/
|
||||
export const control = register('control', (args, pat) => {
|
||||
if (!Array.isArray(args)) {
|
||||
throw new Error('control expects an array of [ccn, ccv]');
|
||||
}
|
||||
const [_ccn, _ccv] = args;
|
||||
return pat.ccn(_ccn).ccv(_ccv);
|
||||
});
|
||||
|
||||
/**
|
||||
* MIDI control number: Sends a MIDI control change message.
|
||||
*
|
||||
* @name ccn
|
||||
* @param {number | Pattern} MIDI control number (0-127)
|
||||
*/
|
||||
export const { ccn } = registerControl('ccn');
|
||||
/**
|
||||
* MIDI control value: Sends a MIDI control change message.
|
||||
*
|
||||
* @name ccv
|
||||
* @param {number | Pattern} MIDI control value (0-127)
|
||||
*/
|
||||
export const { ccv } = registerControl('ccv');
|
||||
export const { ctlNum } = registerControl('ctlNum');
|
||||
// TODO: ctlVal?
|
||||
|
||||
/**
|
||||
* MIDI NRPN non-registered parameter number: Sends a MIDI NRPN non-registered parameter number message.
|
||||
* @name nrpnn
|
||||
* @param {number | Pattern} nrpnn MIDI NRPN non-registered parameter number (0-127)
|
||||
* @example
|
||||
* note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi()
|
||||
*/
|
||||
export const { nrpnn } = registerControl('nrpnn');
|
||||
/**
|
||||
* MIDI NRPN non-registered parameter value: Sends a MIDI NRPN non-registered parameter value message.
|
||||
* @name nrpv
|
||||
* @param {number | Pattern} nrpv MIDI NRPN non-registered parameter value (0-127)
|
||||
* @example
|
||||
* note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi()
|
||||
*/
|
||||
export const { nrpv } = registerControl('nrpv');
|
||||
|
||||
/**
|
||||
* MIDI program number: Sends a MIDI program change message.
|
||||
*
|
||||
* @name progNum
|
||||
* @param {number | Pattern} program MIDI program number (0-127)
|
||||
* @example
|
||||
* note("c4").progNum(10).midichan(1).midi()
|
||||
*/
|
||||
export const { progNum } = registerControl('progNum');
|
||||
|
||||
/**
|
||||
* MIDI sysex: Sends a MIDI sysex message.
|
||||
* @name sysex
|
||||
* @param {number | Pattern} id Sysex ID
|
||||
* @param {number | Pattern} data Sysex data
|
||||
* @example
|
||||
* note("c4").sysex(["0x77", "0x01:0x02:0x03:0x04"]).midichan(1).midi()
|
||||
*/
|
||||
export const sysex = register('sysex', (args, pat) => {
|
||||
if (!Array.isArray(args)) {
|
||||
throw new Error('sysex expects an array of [id, data]');
|
||||
}
|
||||
const [id, data] = args;
|
||||
return pat.sysexid(id).sysexdata(data);
|
||||
});
|
||||
/**
|
||||
* MIDI sysex ID: Sends a MIDI sysex identifier message.
|
||||
* @name sysexid
|
||||
* @param {number | Pattern} id Sysex ID
|
||||
* @example
|
||||
* note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi()
|
||||
*/
|
||||
export const { sysexid } = registerControl('sysexid');
|
||||
/**
|
||||
* MIDI sysex data: Sends a MIDI sysex message.
|
||||
* @name sysexdata
|
||||
* @param {number | Pattern} data Sysex data
|
||||
* @example
|
||||
* note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi()
|
||||
*/
|
||||
export const { sysexdata } = registerControl('sysexdata');
|
||||
|
||||
/**
|
||||
* MIDI pitch bend: Sends a MIDI pitch bend message.
|
||||
* @name midibend
|
||||
* @param {number | Pattern} midibend MIDI pitch bend (-1 - 1)
|
||||
* @example
|
||||
* note("c4").midibend(sine.slow(4).range(-0.4,0.4)).midi()
|
||||
*/
|
||||
export const { midibend } = registerControl('midibend');
|
||||
/**
|
||||
* MIDI key after touch: Sends a MIDI key after touch message.
|
||||
* @name miditouch
|
||||
* @param {number | Pattern} miditouch MIDI key after touch (0-1)
|
||||
* @example
|
||||
* note("c4").miditouch(sine.slow(4).range(0,1)).midi()
|
||||
*/
|
||||
export const { miditouch } = registerControl('miditouch');
|
||||
|
||||
// TODO: what is this?
|
||||
export const { polyTouch } = registerControl('polyTouch');
|
||||
|
||||
export const getControlName = (alias) => {
|
||||
if (controlAlias.has(alias)) {
|
||||
return controlAlias.get(alias);
|
||||
}
|
||||
return alias;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets properties in a batch.
|
||||
*
|
||||
* @name as
|
||||
* @param {String | Array} mapping the control names that are set
|
||||
* @example
|
||||
* "c:.5 a:1 f:.25 e:.8".as("note:clip")
|
||||
* @example
|
||||
* "{0@2 0.25 0 0.5 .3 .5}%8".as("begin").s("sax_vib").clip(1)
|
||||
*/
|
||||
export const as = register('as', (mapping, pat) => {
|
||||
mapping = Array.isArray(mapping) ? mapping : [mapping];
|
||||
return pat.fmap((v) => {
|
||||
v = Array.isArray(v) ? v : [v];
|
||||
v = Object.fromEntries(mapping.map((prop, i) => [getControlName(prop), v[i]]));
|
||||
return v;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Allows you to scrub an audio file like a tape loop by passing values that represents the position in the audio file
|
||||
* in the optional array syntax ex: "0.5:2", the second value controls the speed of playback
|
||||
* @name scrub
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* samples('github:switchangel/pad')
|
||||
* s("swpad:0").scrub("{0.1!2 .25@3 0.7!2 <0.8:1.5>}%8")
|
||||
* @example
|
||||
* samples('github:yaxu/clean-breaks/main');
|
||||
* s("amen/4").fit().scrub("{0@3 0@2 4@3}%8".div(16))
|
||||
*/
|
||||
|
||||
export const scrub = register(
|
||||
'scrub',
|
||||
(beginPat, pat) => {
|
||||
return beginPat.outerBind((v) => {
|
||||
if (!Array.isArray(v)) {
|
||||
v = [v];
|
||||
}
|
||||
const [beginVal, speedMultiplier = 1] = v;
|
||||
|
||||
return pat.begin(beginVal).mul(speed(speedMultiplier)).clip(1);
|
||||
});
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
@@ -68,6 +68,10 @@ export class Cyclist {
|
||||
// see https://github.com/tidalcycles/strudel/pull/1004
|
||||
const deadline = targetTime - phase;
|
||||
onTrigger?.(hap, deadline, duration, this.cps, targetTime);
|
||||
if (hap.value.cps !== undefined && this.cps != hap.value.cps) {
|
||||
this.cps = hap.value.cps;
|
||||
this.num_ticks_since_cps_change = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
@@ -4,8 +4,6 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { isPattern } from './index.mjs';
|
||||
|
||||
export const evalScope = async (...args) => {
|
||||
const results = await Promise.allSettled(args);
|
||||
const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value);
|
||||
@@ -39,6 +37,7 @@ function safeEval(str, options = {}) {
|
||||
|
||||
export const evaluate = async (code, transpiler, transpilerOptions) => {
|
||||
let meta = {};
|
||||
|
||||
if (transpiler) {
|
||||
// transform syntactically correct js code to semantically usable code
|
||||
const transpiled = transpiler(code, transpilerOptions);
|
||||
|
||||
@@ -119,10 +119,10 @@ export const lcm = (...fractions) => {
|
||||
if (fractions.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const x = fractions.pop();
|
||||
return fractions.reduce(
|
||||
(lcm, fraction) => (lcm === undefined || fraction === undefined ? undefined : lcm.lcm(fraction)),
|
||||
fraction(1),
|
||||
x,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ export * from './controls.mjs';
|
||||
export * from './hap.mjs';
|
||||
export * from './pattern.mjs';
|
||||
export * from './signal.mjs';
|
||||
export * from './pick.mjs';
|
||||
export * from './state.mjs';
|
||||
export * from './timespan.mjs';
|
||||
export * from './util.mjs';
|
||||
|
||||
@@ -5,6 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
*/
|
||||
|
||||
import { logger } from './logger.mjs';
|
||||
import { ClockCollator, cycleToSeconds } from './util.mjs';
|
||||
|
||||
export class NeoCyclist {
|
||||
constructor({ onTrigger, onToggle, getTime }) {
|
||||
@@ -13,79 +14,38 @@ export class NeoCyclist {
|
||||
this.lastTick = 0; // absolute time when last tick (clock callback) happened
|
||||
this.getTime = getTime; // get absolute time
|
||||
this.time_at_last_tick_message = 0;
|
||||
|
||||
this.num_cycles_at_cps_change = 0;
|
||||
// the clock of the worker and the audio context clock can drift apart over time
|
||||
// aditionally, the message time of the worker pinging the callback to process haps can be inconsistent.
|
||||
// we need to keep a rolling average of the time difference between the worker clock and audio context clock
|
||||
// in order to schedule events consistently.
|
||||
this.collator = new ClockCollator({ getTargetClockTime: getTime });
|
||||
this.onToggle = onToggle;
|
||||
this.latency = 0.1; // fixed trigger time offset
|
||||
this.cycle = 0;
|
||||
this.id = Math.round(Date.now() * Math.random());
|
||||
this.worker_time_dif;
|
||||
this.worker = new SharedWorker(new URL('./clockworker.js', import.meta.url));
|
||||
this.worker.port.start();
|
||||
|
||||
this.channel = new BroadcastChannel('strudeltick');
|
||||
let weight = 0; // the amount of weight that is applied to the current average when averaging a new time dif
|
||||
const maxWeight = 20;
|
||||
const precision = 10 ** 3; //round off time diff to prevent accumulating outliers
|
||||
|
||||
// the clock of the worker and the audio context clock can drift apart over time
|
||||
// aditionally, the message time of the worker pinging the callback to process haps can be inconsistent.
|
||||
// we need to keep a rolling weighted average of the time difference between the worker clock and audio context clock
|
||||
// in order to schedule events consistently.
|
||||
const setTimeReference = (num_seconds_at_cps_change, num_seconds_since_cps_change, tickdeadline) => {
|
||||
const time_dif = getTime() - (num_seconds_at_cps_change + num_seconds_since_cps_change) + tickdeadline;
|
||||
if (this.worker_time_dif == null) {
|
||||
this.worker_time_dif = time_dif;
|
||||
} else {
|
||||
const w = 1; //weight of new time diff;
|
||||
const new_dif =
|
||||
Math.round(((this.worker_time_dif * weight + time_dif * w) / (weight + w)) * precision) / precision;
|
||||
|
||||
if (new_dif != this.worker_time_dif) {
|
||||
// reset the weight so the clock recovers faster from an audio context freeze/dropout if it happens
|
||||
weight = 4;
|
||||
}
|
||||
this.worker_time_dif = new_dif;
|
||||
}
|
||||
weight = Math.min(weight + 1, maxWeight);
|
||||
};
|
||||
|
||||
const tickCallback = (payload) => {
|
||||
const {
|
||||
num_cycles_at_cps_change,
|
||||
cps,
|
||||
num_seconds_at_cps_change,
|
||||
num_seconds_since_cps_change,
|
||||
begin,
|
||||
end,
|
||||
tickdeadline,
|
||||
cycle,
|
||||
} = payload;
|
||||
const { cps, begin, end, cycle, time } = payload;
|
||||
this.cps = cps;
|
||||
this.cycle = cycle;
|
||||
|
||||
setTimeReference(num_seconds_at_cps_change, num_seconds_since_cps_change, tickdeadline);
|
||||
|
||||
processHaps(begin, end, num_cycles_at_cps_change, num_seconds_at_cps_change);
|
||||
|
||||
this.time_at_last_tick_message = this.getTime();
|
||||
const currentTime = this.collator.calculateOffset(time) + time;
|
||||
processHaps(begin, end, currentTime);
|
||||
this.time_at_last_tick_message = currentTime;
|
||||
};
|
||||
|
||||
const processHaps = (begin, end, num_cycles_at_cps_change, seconds_at_cps_change) => {
|
||||
const processHaps = (begin, end, currentTime) => {
|
||||
if (this.started === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps });
|
||||
|
||||
haps.forEach((hap) => {
|
||||
if (hap.hasOnset()) {
|
||||
const targetTime =
|
||||
(hap.whole.begin - num_cycles_at_cps_change) / this.cps +
|
||||
seconds_at_cps_change +
|
||||
this.latency +
|
||||
this.worker_time_dif;
|
||||
const duration = hap.duration / this.cps;
|
||||
const timeUntilTrigger = cycleToSeconds(hap.whole.begin - this.cycle, this.cps);
|
||||
const targetTime = timeUntilTrigger + currentTime + this.latency;
|
||||
const duration = cycleToSeconds(hap.duration, this.cps);
|
||||
onTrigger?.(hap, 0, duration, this.cps, targetTime);
|
||||
}
|
||||
});
|
||||
@@ -129,8 +89,8 @@ export class NeoCyclist {
|
||||
this.setStarted(true);
|
||||
}
|
||||
stop() {
|
||||
this.worker_time_dif = null;
|
||||
logger('[cyclist] stop');
|
||||
this.collator.reset();
|
||||
this.setStarted(false);
|
||||
}
|
||||
setPattern(pat, autostart = false) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/core",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Port of Tidal Cycles to JavaScript",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
@@ -31,11 +31,11 @@
|
||||
},
|
||||
"homepage": "https://strudel.cc",
|
||||
"dependencies": {
|
||||
"fraction.js": "^4.3.7"
|
||||
"fraction.js": "^5.2.1"
|
||||
},
|
||||
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.10",
|
||||
"vitest": "^1.1.0"
|
||||
"vite": "^6.0.11",
|
||||
"vitest": "^3.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
+586
-247
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
pick.mjs - methods that use one pattern to pick events from other patterns.
|
||||
Copyright (C) 2024 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/signal.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Pattern, reify, silence, register } from './pattern.mjs';
|
||||
|
||||
import { _mod, clamp, objectMap } from './util.mjs';
|
||||
|
||||
const _pick = function (lookup, pat, modulo = true) {
|
||||
const array = Array.isArray(lookup);
|
||||
const len = Object.keys(lookup).length;
|
||||
|
||||
lookup = objectMap(lookup, reify);
|
||||
|
||||
if (len === 0) {
|
||||
return silence;
|
||||
}
|
||||
return pat.fmap((i) => {
|
||||
let key = i;
|
||||
if (array) {
|
||||
key = modulo ? Math.round(key) % len : clamp(Math.round(key), 0, lookup.length - 1);
|
||||
}
|
||||
return lookup[key];
|
||||
});
|
||||
};
|
||||
|
||||
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
|
||||
* Similar to `inhabit`, but maintains the structure of the original patterns.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* note("<0 1 2!2 3>".pick(["g a", "e f", "f g f g" , "g c d"]))
|
||||
* @example
|
||||
* sound("<0 1 [2,0]>".pick(["bd sd", "cp cp", "hh hh"]))
|
||||
* @example
|
||||
* sound("<0!2 [0,1] 1>".pick(["bd(3,8)", "sd sd"]))
|
||||
* @example
|
||||
* s("<a!2 [a,b] b>".pick({a: "bd(3,8)", b: "sd sd"}))
|
||||
*/
|
||||
|
||||
export const pick = function (lookup, pat) {
|
||||
// backward compatibility - the args used to be flipped
|
||||
if (Array.isArray(pat)) {
|
||||
[pat, lookup] = [lookup, pat];
|
||||
}
|
||||
return __pick(lookup, pat);
|
||||
};
|
||||
|
||||
const __pick = register('pick', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).innerJoin();
|
||||
});
|
||||
|
||||
/** * The same as `pick`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* For example, if you pick the fifth pattern of a list of three, you'll get the
|
||||
* second one.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
|
||||
export const pickmod = register('pickmod', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).innerJoin();
|
||||
});
|
||||
|
||||
/** * pickF lets you use a pattern of numbers to pick which function to apply to another pattern.
|
||||
* @param {Pattern} pat
|
||||
* @param {Pattern} lookup a pattern of indices
|
||||
* @param {function[]} funcs the array of functions from which to pull
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* s("bd [rim hh]").pickF("<0 1 2>", [rev,jux(rev),fast(2)])
|
||||
* @example
|
||||
* note("<c2 d2>(3,8)").s("square")
|
||||
* .pickF("<0 2> 1", [jux(rev),fast(2),x=>x.lpf(800)])
|
||||
*/
|
||||
export const pickF = register('pickF', function (lookup, funcs, pat) {
|
||||
return pat.apply(pick(lookup, funcs));
|
||||
});
|
||||
|
||||
/** * The same as `pickF`, but if you pick a number greater than the size of the functions list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* @param {Pattern} pat
|
||||
* @param {Pattern} lookup a pattern of indices
|
||||
* @param {function[]} funcs the array of functions from which to pull
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodF = register('pickmodF', function (lookup, funcs, pat) {
|
||||
return pat.apply(pickmod(lookup, funcs));
|
||||
});
|
||||
|
||||
/** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickOut = register('pickOut', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).outerJoin();
|
||||
});
|
||||
|
||||
/** * The same as `pickOut`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodOut = register('pickmodOut', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).outerJoin();
|
||||
});
|
||||
|
||||
/** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickRestart = register('pickRestart', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).restartJoin();
|
||||
});
|
||||
|
||||
/** * The same as `pickRestart`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* "<a@2 b@2 c@2 d@2>".pickRestart({
|
||||
a: n("0 1 2 0"),
|
||||
b: n("2 3 4 ~"),
|
||||
c: n("[4 5] [4 3] 2 0"),
|
||||
d: n("0 -3 0 ~")
|
||||
}).scale("C:major").s("piano")
|
||||
*/
|
||||
export const pickmodRestart = register('pickmodRestart', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).restartJoin();
|
||||
});
|
||||
|
||||
/** * Similar to `pick`, but the choosen pattern is reset when its index is triggered.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickReset = register('pickReset', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).resetJoin();
|
||||
});
|
||||
|
||||
/** * The same as `pickReset`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodReset = register('pickmodReset', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).resetJoin();
|
||||
});
|
||||
|
||||
/** Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
|
||||
* Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern.
|
||||
* @name inhabit
|
||||
* @synonyms pickSqueeze
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* "<a b [a,b]>".inhabit({a: s("bd(3,8)"),
|
||||
b: s("cp sd")
|
||||
})
|
||||
* @example
|
||||
* s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4)
|
||||
*/
|
||||
export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).squeezeJoin();
|
||||
});
|
||||
|
||||
/** * The same as `inhabit`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* For example, if you pick the fifth pattern of a list of three, you'll get the
|
||||
* second one.
|
||||
* @name inhabitmod
|
||||
* @synonyms pickmodSqueeze
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
|
||||
export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSqueeze'], function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).squeezeJoin();
|
||||
});
|
||||
|
||||
/**
|
||||
* Pick from the list of values (or patterns of values) via the index using the given
|
||||
* pattern of integers. The selected pattern will be compressed to fit the duration of the selecting event
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* note(squeeze("<0@2 [1!2] 2>", ["g a", "f g f g" , "g a c d"]))
|
||||
*/
|
||||
|
||||
export const squeeze = (pat, xs) => {
|
||||
xs = xs.map(reify);
|
||||
if (xs.length == 0) {
|
||||
return silence;
|
||||
}
|
||||
return pat
|
||||
.fmap((i) => {
|
||||
const key = _mod(Math.round(i), xs.length);
|
||||
return xs[key];
|
||||
})
|
||||
.squeezeJoin();
|
||||
};
|
||||
+39
-2
@@ -63,11 +63,13 @@ export function repl({
|
||||
let pPatterns = {};
|
||||
let anonymousIndex = 0;
|
||||
let allTransform;
|
||||
let eachTransform;
|
||||
|
||||
const hush = function () {
|
||||
pPatterns = {};
|
||||
anonymousIndex = 0;
|
||||
allTransform = undefined;
|
||||
eachTransform = undefined;
|
||||
return silence;
|
||||
};
|
||||
|
||||
@@ -84,15 +86,42 @@ export function repl({
|
||||
const toggle = () => scheduler.toggle();
|
||||
const setCps = (cps) => scheduler.setCps(cps);
|
||||
const setCpm = (cpm) => scheduler.setCps(cpm / 60);
|
||||
|
||||
// TODO - not documented as jsdoc examples as the test framework doesn't simulate enough context for `each` and `all`..
|
||||
|
||||
/** Applies a function to all the running patterns. Note that the patterns are groups together into a single `stack` before the function is applied. This is probably what you want, but see `each` for
|
||||
* a version that applies the function to each pattern separately.
|
||||
* ```
|
||||
* $: sound("bd - cp sd")
|
||||
* $: sound("hh*8")
|
||||
* all(fast("<2 3>"))
|
||||
* ```
|
||||
* ```
|
||||
* $: sound("bd - cp sd")
|
||||
* $: sound("hh*8")
|
||||
* all(x => x.pianoroll())
|
||||
* ```
|
||||
*/
|
||||
const all = function (transform) {
|
||||
allTransform = transform;
|
||||
return silence;
|
||||
};
|
||||
/** Applies a function to each of the running patterns separately. This is intended for future use with upcoming 'stepwise' features. See `all` for a version that applies the function to all the patterns stacked together into a single pattern.
|
||||
* ```
|
||||
* $: sound("bd - cp sd")
|
||||
* $: sound("hh*8")
|
||||
* each(fast("<2 3>"))
|
||||
* ```
|
||||
*/
|
||||
const each = function (transform) {
|
||||
eachTransform = transform;
|
||||
return silence;
|
||||
};
|
||||
|
||||
// set pattern methods that use this repl via closure
|
||||
const injectPatternMethods = () => {
|
||||
Pattern.prototype.p = function (id) {
|
||||
if (id.startsWith('_') || id.endsWith('_')) {
|
||||
if (typeof id === 'string' && (id.startsWith('_') || id.endsWith('_'))) {
|
||||
// allows muting a pattern x with x_ or _x
|
||||
return silence;
|
||||
}
|
||||
@@ -131,6 +160,7 @@ export function repl({
|
||||
});
|
||||
return evalScope({
|
||||
all,
|
||||
each,
|
||||
hush,
|
||||
cpm,
|
||||
setCps,
|
||||
@@ -152,7 +182,14 @@ export function repl({
|
||||
shouldHush && hush();
|
||||
let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions);
|
||||
if (Object.keys(pPatterns).length) {
|
||||
pattern = stack(...Object.values(pPatterns));
|
||||
let patterns = Object.values(pPatterns);
|
||||
if (eachTransform) {
|
||||
// Explicit lambda so only element (not index and array) are passed
|
||||
patterns = patterns.map((x) => eachTransform(x));
|
||||
}
|
||||
pattern = stack(...patterns);
|
||||
} else if (eachTransform) {
|
||||
pattern = eachTransform(pattern);
|
||||
}
|
||||
if (allTransform) {
|
||||
pattern = allTransform(pattern);
|
||||
|
||||
+252
-230
@@ -1,13 +1,14 @@
|
||||
/*
|
||||
signal.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/signal.mjs>
|
||||
signal.mjs - continuous patterns
|
||||
Copyright (C) 2024 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/signal.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Hap } from './hap.mjs';
|
||||
import { Pattern, fastcat, reify, silence, stack, register } from './pattern.mjs';
|
||||
import { Pattern, fastcat, pure, register, reify, silence, stack, sequenceP } from './pattern.mjs';
|
||||
import Fraction from './fraction.mjs';
|
||||
import { id, _mod, clamp, objectMap } from './util.mjs';
|
||||
|
||||
import { id, keyAlias, getCurrentKeyboardState } from './util.mjs';
|
||||
|
||||
export function steady(value) {
|
||||
// A continuous value
|
||||
@@ -15,13 +16,10 @@ export function steady(value) {
|
||||
}
|
||||
|
||||
export const signal = (func) => {
|
||||
const query = (state) => [new Hap(undefined, state.span, func(state.span.midpoint()))];
|
||||
const query = (state) => [new Hap(undefined, state.span, func(state.span.begin))];
|
||||
return new Pattern(query);
|
||||
};
|
||||
|
||||
export const isaw = signal((t) => 1 - (t % 1));
|
||||
export const isaw2 = isaw.toBipolar();
|
||||
|
||||
/**
|
||||
* A sawtooth signal between 0 and 1.
|
||||
*
|
||||
@@ -35,8 +33,40 @@ export const isaw2 = isaw.toBipolar();
|
||||
*
|
||||
*/
|
||||
export const saw = signal((t) => t % 1);
|
||||
|
||||
/**
|
||||
* A sawtooth signal between -1 and 1 (like `saw`, but bipolar).
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const saw2 = saw.toBipolar();
|
||||
|
||||
/**
|
||||
* A sawtooth signal between 1 and 0 (like `saw`, but flipped).
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* note("<c3 [eb3,g3] g2 [g3,bb3]>*8")
|
||||
* .clip(isaw.slow(2))
|
||||
* @example
|
||||
* n(isaw.range(0,8).segment(8))
|
||||
* .scale('C major')
|
||||
*
|
||||
*/
|
||||
export const isaw = signal((t) => 1 - (t % 1));
|
||||
|
||||
/**
|
||||
* A sawtooth signal between 1 and -1 (like `saw2`, but flipped).
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const isaw2 = isaw.toBipolar();
|
||||
|
||||
/**
|
||||
* A sine signal between -1 and 1 (like `sine`, but bipolar).
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
|
||||
|
||||
/**
|
||||
@@ -60,6 +90,12 @@ export const sine = sine2.fromBipolar();
|
||||
*
|
||||
*/
|
||||
export const cosine = sine._early(Fraction(1).div(4));
|
||||
|
||||
/**
|
||||
* A cosine signal between -1 and 1 (like `cosine`, but bipolar).
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const cosine2 = sine2._early(Fraction(1).div(4));
|
||||
|
||||
/**
|
||||
@@ -71,6 +107,12 @@ export const cosine2 = sine2._early(Fraction(1).div(4));
|
||||
*
|
||||
*/
|
||||
export const square = signal((t) => Math.floor((t * 2) % 2));
|
||||
|
||||
/**
|
||||
* A square signal between -1 and 1 (like `square`, but bipolar).
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const square2 = square.toBipolar();
|
||||
|
||||
/**
|
||||
@@ -81,9 +123,37 @@ export const square2 = square.toBipolar();
|
||||
* n(tri.segment(8).range(0,7)).scale("C:minor")
|
||||
*
|
||||
*/
|
||||
export const tri = fastcat(isaw, saw);
|
||||
export const tri2 = fastcat(isaw2, saw2);
|
||||
export const tri = fastcat(saw, isaw);
|
||||
|
||||
/**
|
||||
* A triangle signal between -1 and 1 (like `tri`, but bipolar).
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const tri2 = fastcat(saw2, isaw2);
|
||||
|
||||
/**
|
||||
* An inverted triangle signal between 1 and 0 (like `tri`, but flipped).
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* n(itri.segment(8).range(0,7)).scale("C:minor")
|
||||
*
|
||||
*/
|
||||
export const itri = fastcat(isaw, saw);
|
||||
|
||||
/**
|
||||
* An inverted triangle signal between -1 and 1 (like `itri`, but bipolar).
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const itri2 = fastcat(isaw2, saw2);
|
||||
|
||||
/**
|
||||
* A signal representing the cycle time.
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const time = signal(id);
|
||||
|
||||
/**
|
||||
@@ -138,7 +208,7 @@ const timeToRand = (x) => Math.abs(intSeedToRand(timeToIntSeed(x)));
|
||||
const timeToRandsPrime = (seed, n) => {
|
||||
const result = [];
|
||||
// eslint-disable-next-line
|
||||
for (let i = 0; i < n; ++n) {
|
||||
for (let i = 0; i < n; ++i) {
|
||||
result.push(intSeedToRand(seed));
|
||||
seed = xorwise(seed);
|
||||
}
|
||||
@@ -157,7 +227,82 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
|
||||
* n(run(4)).scale("C4:pentatonic")
|
||||
* // n("0 1 2 3").scale("C4:pentatonic")
|
||||
*/
|
||||
export const run = (n) => saw.range(0, n).floor().segment(n);
|
||||
export const run = (n) => saw.range(0, n).round().segment(n);
|
||||
|
||||
/**
|
||||
* Creates a pattern from a binary number.
|
||||
*
|
||||
* @name binary
|
||||
* @param {number} n - input number to convert to binary
|
||||
* @example
|
||||
* "hh".s().struct(binary(5))
|
||||
* // "hh".s().struct("1 0 1")
|
||||
*/
|
||||
export const binary = (n) => {
|
||||
const nBits = reify(n).log2(0).floor().add(1);
|
||||
return binaryN(n, nBits);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a pattern from a binary number, padded to n bits long.
|
||||
*
|
||||
* @name binaryN
|
||||
* @param {number} n - input number to convert to binary
|
||||
* @param {number} nBits - pattern length, defaults to 16
|
||||
* @example
|
||||
* "hh".s().struct(binaryN(55532, 16))
|
||||
* // "hh".s().struct("1 1 0 1 1 0 0 0 1 1 1 0 1 1 0 0")
|
||||
*/
|
||||
export const binaryN = (n, nBits = 16) => {
|
||||
nBits = reify(nBits);
|
||||
// Shift and mask, putting msb on the right-side
|
||||
const bitPos = run(nBits).mul(-1).add(nBits.sub(1));
|
||||
return reify(n).segment(nBits).brshift(bitPos).band(pure(1));
|
||||
};
|
||||
|
||||
export const randrun = (n) => {
|
||||
return signal((t) => {
|
||||
// Without adding 0.5, the first cycle is always 0,1,2,3,...
|
||||
const rands = timeToRands(t.floor().add(0.5), n);
|
||||
const nums = rands
|
||||
.map((n, i) => [n, i])
|
||||
.sort((a, b) => a[0] > b[0] - a[0] < b[0])
|
||||
.map((x) => x[1]);
|
||||
const i = t.cyclePos().mul(n).floor() % n;
|
||||
return nums[i];
|
||||
})._segment(n);
|
||||
};
|
||||
|
||||
const _rearrangeWith = (ipat, n, pat) => {
|
||||
const pats = [...Array(n).keys()].map((i) => pat.zoom(Fraction(i).div(n), Fraction(i + 1).div(n)));
|
||||
return ipat.fmap((i) => pats[i].repeatCycles(n)._fast(n)).innerJoin();
|
||||
};
|
||||
|
||||
/**
|
||||
* Slices a pattern into the given number of parts, then plays those parts in random order.
|
||||
* Each part will be played exactly once per cycle.
|
||||
* @name shuffle
|
||||
* @example
|
||||
* note("c d e f").sound("piano").shuffle(4)
|
||||
* @example
|
||||
* note("c d e f".shuffle(4), "g").sound("piano")
|
||||
*/
|
||||
export const shuffle = register('shuffle', (n, pat) => {
|
||||
return _rearrangeWith(randrun(n), n, pat);
|
||||
});
|
||||
|
||||
/**
|
||||
* Slices a pattern into the given number of parts, then plays those parts at random. Similar to `shuffle`,
|
||||
* but parts might be played more than once, or not at all, per cycle.
|
||||
* @name scramble
|
||||
* @example
|
||||
* note("c d e f").sound("piano").scramble(4)
|
||||
* @example
|
||||
* note("c d e f".scramble(4), "g").sound("piano")
|
||||
*/
|
||||
export const scramble = register('scramble', (n, pat) => {
|
||||
return _rearrangeWith(_irand(n)._segment(n), n, pat);
|
||||
});
|
||||
|
||||
/**
|
||||
* A continuous pattern of random numbers, between 0 and 1.
|
||||
@@ -209,204 +354,6 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
|
||||
*/
|
||||
export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin();
|
||||
|
||||
const _pick = function (lookup, pat, modulo = true) {
|
||||
const array = Array.isArray(lookup);
|
||||
const len = Object.keys(lookup).length;
|
||||
|
||||
lookup = objectMap(lookup, reify);
|
||||
|
||||
if (len === 0) {
|
||||
return silence;
|
||||
}
|
||||
return pat.fmap((i) => {
|
||||
let key = i;
|
||||
if (array) {
|
||||
key = modulo ? Math.round(key) % len : clamp(Math.round(key), 0, lookup.length - 1);
|
||||
}
|
||||
return lookup[key];
|
||||
});
|
||||
};
|
||||
|
||||
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
|
||||
* Similar to `inhabit`, but maintains the structure of the original patterns.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* note("<0 1 2!2 3>".pick(["g a", "e f", "f g f g" , "g c d"]))
|
||||
* @example
|
||||
* sound("<0 1 [2,0]>".pick(["bd sd", "cp cp", "hh hh"]))
|
||||
* @example
|
||||
* sound("<0!2 [0,1] 1>".pick(["bd(3,8)", "sd sd"]))
|
||||
* @example
|
||||
* s("<a!2 [a,b] b>".pick({a: "bd(3,8)", b: "sd sd"}))
|
||||
*/
|
||||
|
||||
export const pick = function (lookup, pat) {
|
||||
// backward compatibility - the args used to be flipped
|
||||
if (Array.isArray(pat)) {
|
||||
[pat, lookup] = [lookup, pat];
|
||||
}
|
||||
return __pick(lookup, pat);
|
||||
};
|
||||
|
||||
const __pick = register('pick', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).innerJoin();
|
||||
});
|
||||
|
||||
/** * The same as `pick`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* For example, if you pick the fifth pattern of a list of three, you'll get the
|
||||
* second one.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
|
||||
export const pickmod = register('pickmod', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).innerJoin();
|
||||
});
|
||||
|
||||
/** * pickF lets you use a pattern of numbers to pick which function to apply to another pattern.
|
||||
* @param {Pattern} pat
|
||||
* @param {Pattern} lookup a pattern of indices
|
||||
* @param {function[]} funcs the array of functions from which to pull
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* s("bd [rim hh]").pickF("<0 1 2>", [rev,jux(rev),fast(2)])
|
||||
* @example
|
||||
* note("<c2 d2>(3,8)").s("square")
|
||||
* .pickF("<0 2> 1", [jux(rev),fast(2),x=>x.lpf(800)])
|
||||
*/
|
||||
export const pickF = register('pickF', function (lookup, funcs, pat) {
|
||||
return pat.apply(pick(lookup, funcs));
|
||||
});
|
||||
|
||||
/** * The same as `pickF`, but if you pick a number greater than the size of the functions list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* @param {Pattern} pat
|
||||
* @param {Pattern} lookup a pattern of indices
|
||||
* @param {function[]} funcs the array of functions from which to pull
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodF = register('pickmodF', function (lookup, funcs, pat) {
|
||||
return pat.apply(pickmod(lookup, funcs));
|
||||
});
|
||||
|
||||
/** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickOut = register('pickOut', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).outerJoin();
|
||||
});
|
||||
|
||||
/** * The same as `pickOut`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodOut = register('pickmodOut', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).outerJoin();
|
||||
});
|
||||
|
||||
/** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickRestart = register('pickRestart', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).restartJoin();
|
||||
});
|
||||
|
||||
/** * The same as `pickRestart`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodRestart = register('pickmodRestart', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).restartJoin();
|
||||
});
|
||||
|
||||
/** * Similar to `pick`, but the choosen pattern is reset when its index is triggered.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickReset = register('pickReset', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).resetJoin();
|
||||
});
|
||||
|
||||
/** * The same as `pickReset`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodReset = register('pickmodReset', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).resetJoin();
|
||||
});
|
||||
|
||||
/**
|
||||
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
|
||||
* Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern.
|
||||
* @name inhabit
|
||||
* @synonyms pickSqueeze
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* "<a b [a,b]>".inhabit({a: s("bd(3,8)"),
|
||||
b: s("cp sd")
|
||||
})
|
||||
* @example
|
||||
* s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4)
|
||||
*/
|
||||
export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).squeezeJoin();
|
||||
});
|
||||
|
||||
/** * The same as `inhabit`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
* For example, if you pick the fifth pattern of a list of three, you'll get the
|
||||
* second one.
|
||||
* @name inhabitmod
|
||||
* @synonyms pickmodSqueeze
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
*/
|
||||
|
||||
export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSqueeze'], function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).squeezeJoin();
|
||||
});
|
||||
|
||||
/**
|
||||
* Pick from the list of values (or patterns of values) via the index using the given
|
||||
* pattern of integers. The selected pattern will be compressed to fit the duration of the selecting event
|
||||
* @param {Pattern} pat
|
||||
* @param {*} xs
|
||||
* @returns {Pattern}
|
||||
* @example
|
||||
* note(squeeze("<0@2 [1!2] 2>", ["g a", "f g f g" , "g a c d"]))
|
||||
*/
|
||||
|
||||
export const squeeze = (pat, xs) => {
|
||||
xs = xs.map(reify);
|
||||
if (xs.length == 0) {
|
||||
return silence;
|
||||
}
|
||||
return pat
|
||||
.fmap((i) => {
|
||||
const key = _mod(Math.round(i), xs.length);
|
||||
return xs[key];
|
||||
})
|
||||
.squeezeJoin();
|
||||
};
|
||||
|
||||
export const __chooseWith = (pat, xs) => {
|
||||
xs = xs.map(reify);
|
||||
if (xs.length == 0) {
|
||||
@@ -486,19 +433,30 @@ export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs);
|
||||
export const randcat = chooseCycles;
|
||||
|
||||
const _wchooseWith = function (pat, ...pairs) {
|
||||
// A list of patterns of values
|
||||
const values = pairs.map((pair) => reify(pair[0]));
|
||||
|
||||
// A list of weight patterns
|
||||
const weights = [];
|
||||
let accum = 0;
|
||||
|
||||
let total = pure(0);
|
||||
for (const pair of pairs) {
|
||||
accum += pair[1];
|
||||
weights.push(accum);
|
||||
// 'add' accepts either values or patterns of values here, so no need
|
||||
// to explicitly reify
|
||||
total = total.add(pair[1]);
|
||||
// accumulate our list of weight patterns
|
||||
weights.push(total);
|
||||
}
|
||||
const total = accum;
|
||||
// a pattern of lists of weights
|
||||
const weightspat = sequenceP(weights);
|
||||
|
||||
// Takes a number from 0-1, returns a pattern of patterns of values
|
||||
const match = function (r) {
|
||||
const find = r * total;
|
||||
return values[weights.findIndex((x) => x > find, weights)];
|
||||
const findpat = total.mul(r);
|
||||
return weightspat.fmap((weights) => (find) => values[weights.findIndex((x) => x > find, weights)]).appLeft(findpat);
|
||||
};
|
||||
return pat.fmap(match);
|
||||
// This returns a pattern of patterns.. The innerJoin is in wchooseCycles
|
||||
return pat.bind(match);
|
||||
};
|
||||
|
||||
const wchooseWith = (...args) => _wchooseWith(...args).outerJoin();
|
||||
@@ -520,6 +478,9 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
|
||||
* wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8)
|
||||
* @example
|
||||
* wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s()
|
||||
* @example
|
||||
* // The probability can itself be a pattern
|
||||
* wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s()
|
||||
*/
|
||||
export const wchooseCycles = (...pairs) => _wchooseWith(rand.segment(1), ...pairs).innerJoin();
|
||||
|
||||
@@ -545,8 +506,11 @@ export const perlinWith = (pat) => {
|
||||
*/
|
||||
export const perlin = perlinWith(time.fmap((v) => Number(v)));
|
||||
|
||||
export const degradeByWith = register('degradeByWith', (withPat, x, pat) =>
|
||||
pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
|
||||
export const degradeByWith = register(
|
||||
'degradeByWith',
|
||||
(withPat, x, pat) => pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -562,10 +526,18 @@ export const degradeByWith = register('degradeByWith', (withPat, x, pat) =>
|
||||
* s("hh*8").degradeBy(0.2)
|
||||
* @example
|
||||
* s("[hh?0.2]*8")
|
||||
* @example
|
||||
* //beat generator
|
||||
* s("bd").segment(16).degradeBy(.5).ribbon(16,1)
|
||||
*/
|
||||
export const degradeBy = register('degradeBy', function (x, pat) {
|
||||
return pat._degradeByWith(rand, x);
|
||||
});
|
||||
export const degradeBy = register(
|
||||
'degradeBy',
|
||||
function (x, pat) {
|
||||
return pat._degradeByWith(rand, x);
|
||||
},
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -579,7 +551,7 @@ export const degradeBy = register('degradeBy', function (x, pat) {
|
||||
* @example
|
||||
* s("[hh?]*8")
|
||||
*/
|
||||
export const degrade = register('degrade', (pat) => pat._degradeBy(0.5));
|
||||
export const degrade = register('degrade', (pat) => pat._degradeBy(0.5), true, true);
|
||||
|
||||
/**
|
||||
* Inverse of `degradeBy`: Randomly removes events from the pattern by a given amount.
|
||||
@@ -599,12 +571,17 @@ export const degrade = register('degrade', (pat) => pat._degradeBy(0.5));
|
||||
* x => x.undegradeBy(0.8).pan(1)
|
||||
* )
|
||||
*/
|
||||
export const undegradeBy = register('undegradeBy', function (x, pat) {
|
||||
return pat._degradeByWith(
|
||||
rand.fmap((r) => 1 - r),
|
||||
x,
|
||||
);
|
||||
});
|
||||
export const undegradeBy = register(
|
||||
'undegradeBy',
|
||||
function (x, pat) {
|
||||
return pat._degradeByWith(
|
||||
rand.fmap((r) => 1 - r),
|
||||
x,
|
||||
);
|
||||
},
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Inverse of `degrade`: Randomly removes 50% of events from the pattern. Shorthand for `.undegradeBy(0.5)`
|
||||
@@ -621,7 +598,7 @@ export const undegradeBy = register('undegradeBy', function (x, pat) {
|
||||
* x => x.undegrade().pan(1)
|
||||
* )
|
||||
*/
|
||||
export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5));
|
||||
export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5), true, true);
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -780,3 +757,48 @@ export const never = register('never', function (_, pat) {
|
||||
export const always = register('always', function (func, pat) {
|
||||
return func(pat);
|
||||
});
|
||||
|
||||
//keyname: string | Array<string>
|
||||
//keyname reference: https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
|
||||
export function _keyDown(keyname) {
|
||||
if (Array.isArray(keyname) === false) {
|
||||
keyname = [keyname];
|
||||
}
|
||||
const keyState = getCurrentKeyboardState();
|
||||
return keyname.every((x) => {
|
||||
const keyName = keyAlias.get(x) ?? x;
|
||||
return keyState[keyName];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Do something on a keypress, or array of keypresses
|
||||
* [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values)
|
||||
*
|
||||
* @name whenKey
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("bd(5,8)").whenKey("Control:j", x => x.segment(16).color("red")).whenKey("Control:i", x => x.fast(2).color("blue"))
|
||||
*/
|
||||
|
||||
export const whenKey = register('whenKey', function (input, func, pat) {
|
||||
return pat.when(_keyDown(input), func);
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* returns true when a key or array of keys is held
|
||||
* [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values)
|
||||
*
|
||||
* @name keyDown
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* keyDown("Control:j").pick([s("bd(5,8)"), s("cp(3,8)")])
|
||||
*/
|
||||
|
||||
export const keyDown = register('keyDown', function (pat) {
|
||||
return pat.fmap(_keyDown);
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ Copyright (C) 2023 Strudel contributors - see <https://github.com/tidalcycles/st
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { s, pan } from '../controls.mjs';
|
||||
import { s, pan, getControlName } from '../controls.mjs';
|
||||
import { mini } from '../../mini/mini.mjs';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import Fraction from '../fraction.mjs';
|
||||
@@ -30,13 +30,17 @@ describe('controls', () => {
|
||||
expect(s(mini('bd').pan(1)).firstCycleValues).toEqual([{ s: 'bd', pan: 1 }]);
|
||||
expect(s(mini('bd:1').pan(1)).firstCycleValues).toEqual([{ s: 'bd', n: 1, pan: 1 }]);
|
||||
});
|
||||
it('preserves tactus of the left pattern', () => {
|
||||
expect(s(mini('bd cp mt').pan(mini('1 2 3 4'))).tactus).toEqual(Fraction(3));
|
||||
it('preserves step count of the left pattern', () => {
|
||||
expect(s(mini('bd cp mt').pan(mini('1 2 3 4')))._steps).toEqual(Fraction(3));
|
||||
});
|
||||
it('preserves tactus of the right pattern for .out', () => {
|
||||
expect(s(mini('bd cp mt').set.out(pan(mini('1 2 3 4')))).tactus).toEqual(Fraction(4));
|
||||
it('preserves step count of the right pattern for .out', () => {
|
||||
expect(s(mini('bd cp mt').set.out(pan(mini('1 2 3 4'))))._steps).toEqual(Fraction(4));
|
||||
});
|
||||
it('combines tactus of the pattern for .mix as lcm', () => {
|
||||
expect(s(mini('bd cp mt').set.mix(pan(mini('1 2 3 4')))).tactus).toEqual(Fraction(12));
|
||||
it('combines step count of the pattern for .mix as lcm', () => {
|
||||
expect(s(mini('bd cp mt').set.mix(pan(mini('1 2 3 4'))))._steps).toEqual(Fraction(12));
|
||||
});
|
||||
it('finds control name by alias', () => {
|
||||
expect(getControlName('lpf')).toEqual('cutoff');
|
||||
expect(getControlName('cutoff')).toEqual('cutoff');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,8 +21,7 @@ import {
|
||||
cat,
|
||||
sequence,
|
||||
palindrome,
|
||||
s_polymeter,
|
||||
s_polymeterSteps,
|
||||
polymeter,
|
||||
polyrhythm,
|
||||
silence,
|
||||
fast,
|
||||
@@ -46,12 +45,13 @@ import {
|
||||
rev,
|
||||
time,
|
||||
run,
|
||||
binaryN,
|
||||
pick,
|
||||
stackLeft,
|
||||
stackRight,
|
||||
stackCentre,
|
||||
s_cat,
|
||||
calculateTactus,
|
||||
stepcat,
|
||||
sometimes,
|
||||
} from '../index.mjs';
|
||||
|
||||
import { steady } from '../signal.mjs';
|
||||
@@ -608,19 +608,12 @@ describe('Pattern', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('s_polymeter()', () => {
|
||||
describe('polymeter()', () => {
|
||||
it('Can layer up cycles, stepwise, with lists', () => {
|
||||
expect(s_polymeterSteps(3, ['d', 'e']).firstCycle()).toStrictEqual(
|
||||
fastcat(pure('d'), pure('e'), pure('d')).firstCycle(),
|
||||
);
|
||||
|
||||
expect(s_polymeter(['a', 'b', 'c'], ['d', 'e']).fast(2).firstCycle()).toStrictEqual(
|
||||
expect(polymeter(['a', 'b', 'c'], ['d', 'e']).fast(2).firstCycle()).toStrictEqual(
|
||||
stack(sequence('a', 'b', 'c', 'a', 'b', 'c'), sequence('d', 'e', 'd', 'e', 'd', 'e')).firstCycle(),
|
||||
);
|
||||
});
|
||||
it('Can layer up cycles, stepwise, with weighted patterns', () => {
|
||||
sameFirst(s_polymeterSteps(3, sequence('a', 'b')).fast(2), sequence('a', 'b', 'a', 'b', 'a', 'b'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('firstOf()', () => {
|
||||
@@ -735,21 +728,15 @@ describe('Pattern', () => {
|
||||
describe('signal()', () => {
|
||||
it('Can make saw/saw2', () => {
|
||||
expect(saw.struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(1 / 8, 3 / 8, 5 / 8, 7 / 8).firstCycle(),
|
||||
sequence(0, 1 / 4, 1 / 2, 3 / 4).firstCycle(),
|
||||
);
|
||||
|
||||
expect(saw2.struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(-3 / 4, -1 / 4, 1 / 4, 3 / 4).firstCycle(),
|
||||
);
|
||||
expect(saw2.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(-1, -0.5, 0, 0.5).firstCycle());
|
||||
});
|
||||
it('Can make isaw/isaw2', () => {
|
||||
expect(isaw.struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(7 / 8, 5 / 8, 3 / 8, 1 / 8).firstCycle(),
|
||||
);
|
||||
expect(isaw.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(1, 0.75, 0.5, 0.25).firstCycle());
|
||||
|
||||
expect(isaw2.struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(3 / 4, 1 / 4, -1 / 4, -3 / 4).firstCycle(),
|
||||
);
|
||||
expect(isaw2.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(1, 0.5, 0, -0.5).firstCycle());
|
||||
});
|
||||
});
|
||||
describe('_setContext()', () => {
|
||||
@@ -887,7 +874,7 @@ describe('Pattern', () => {
|
||||
.squeezeJoin()
|
||||
.queryArc(3, 4)
|
||||
.map((x) => x.value),
|
||||
).toStrictEqual([Fraction(3.5)]);
|
||||
).toStrictEqual([Fraction(3)]);
|
||||
});
|
||||
});
|
||||
describe('ply', () => {
|
||||
@@ -937,6 +924,9 @@ describe('Pattern', () => {
|
||||
.firstCycle(),
|
||||
);
|
||||
});
|
||||
it('Can chop chops', () => {
|
||||
expect(pure({ s: 'bev' }).chop(2).chop(2).firstCycle()).toStrictEqual(pure({ s: 'bev' }).chop(4).firstCycle());
|
||||
});
|
||||
});
|
||||
describe('range', () => {
|
||||
it('Can be patterned', () => {
|
||||
@@ -955,6 +945,18 @@ describe('Pattern', () => {
|
||||
expect(run(4).firstCycle()).toStrictEqual(sequence(0, 1, 2, 3).firstCycle());
|
||||
});
|
||||
});
|
||||
describe('binaryN', () => {
|
||||
it('Can make a binary pattern from a decimal', () => {
|
||||
expect(binaryN(55532).firstCycle()).toStrictEqual(
|
||||
sequence(1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0).firstCycle(),
|
||||
);
|
||||
});
|
||||
it('Can make a binary pattern from patterned inputs', () => {
|
||||
expect(binaryN(pure(0x1337), pure(14)).firstCycle()).toStrictEqual(
|
||||
sequence(0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1).firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('ribbon', () => {
|
||||
it('Can ribbon', () => {
|
||||
expect(cat(0, 1, 2, 3, 4, 5, 6, 7).ribbon(2, 4).fast(4).firstCycle()).toStrictEqual(
|
||||
@@ -1124,89 +1126,145 @@ describe('Pattern', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('tactus', () => {
|
||||
describe('_steps', () => {
|
||||
it('Is correctly preserved/calculated through transformations', () => {
|
||||
expect(sequence(0, 1, 2, 3).linger(4).tactus).toStrictEqual(Fraction(4));
|
||||
expect(sequence(0, 1, 2, 3).iter(4).tactus).toStrictEqual(Fraction(4));
|
||||
expect(sequence(0, 1, 2, 3).fast(4).tactus).toStrictEqual(Fraction(4));
|
||||
expect(sequence(0, 1, 2, 3).hurry(4).tactus).toStrictEqual(Fraction(4));
|
||||
expect(sequence(0, 1, 2, 3).rev().tactus).toStrictEqual(Fraction(4));
|
||||
expect(sequence(1).segment(10).tactus).toStrictEqual(Fraction(10));
|
||||
expect(sequence(1, 0, 1).invert().tactus).toStrictEqual(Fraction(3));
|
||||
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).chop(4).tactus).toStrictEqual(Fraction(8));
|
||||
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).striate(4).tactus).toStrictEqual(Fraction(8));
|
||||
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).slice(4, sequence(0, 1, 2, 3)).tactus).toStrictEqual(
|
||||
expect(sequence(0, 1, 2, 3).linger(4)._steps).toStrictEqual(Fraction(4));
|
||||
expect(sequence(0, 1, 2, 3).iter(4)._steps).toStrictEqual(Fraction(4));
|
||||
expect(sequence(0, 1, 2, 3).fast(4)._steps).toStrictEqual(Fraction(4));
|
||||
expect(sequence(0, 1, 2, 3).hurry(4)._steps).toStrictEqual(Fraction(4));
|
||||
expect(sequence(0, 1, 2, 3).rev()._steps).toStrictEqual(Fraction(4));
|
||||
expect(sequence(1).segment(10)._steps).toStrictEqual(Fraction(10));
|
||||
expect(sequence(1, 0, 1).invert()._steps).toStrictEqual(Fraction(3));
|
||||
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).chop(4)._steps).toStrictEqual(Fraction(8));
|
||||
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).striate(4)._steps).toStrictEqual(Fraction(8));
|
||||
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).slice(4, sequence(0, 1, 2, 3))._steps).toStrictEqual(
|
||||
Fraction(4),
|
||||
);
|
||||
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).splice(4, sequence(0, 1, 2, 3)).tactus).toStrictEqual(
|
||||
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).splice(4, sequence(0, 1, 2, 3))._steps).toStrictEqual(
|
||||
Fraction(4),
|
||||
);
|
||||
expect(sequence({ n: 0 }, { n: 1 }, { n: 2 }).chop(4).tactus).toStrictEqual(Fraction(12));
|
||||
expect(sequence({ n: 0 }, { n: 1 }, { n: 2 }).chop(4)._steps).toStrictEqual(Fraction(12));
|
||||
expect(
|
||||
pure((x) => x + 1)
|
||||
.setTactus(3)
|
||||
.appBoth(pure(1).setTactus(2)).tactus,
|
||||
.setSteps(3)
|
||||
.appBoth(pure(1).setSteps(2))._steps,
|
||||
).toStrictEqual(Fraction(6));
|
||||
expect(
|
||||
pure((x) => x + 1)
|
||||
.setTactus(undefined)
|
||||
.appBoth(pure(1).setTactus(2)).tactus,
|
||||
.setSteps(undefined)
|
||||
.appBoth(pure(1).setSteps(2))._steps,
|
||||
).toStrictEqual(Fraction(2));
|
||||
expect(
|
||||
pure((x) => x + 1)
|
||||
.setTactus(3)
|
||||
.appBoth(pure(1).setTactus(undefined)).tactus,
|
||||
.setSteps(3)
|
||||
.appBoth(pure(1).setSteps(undefined))._steps,
|
||||
).toStrictEqual(Fraction(3));
|
||||
expect(stack(fastcat(0, 1, 2), fastcat(3, 4)).tactus).toStrictEqual(Fraction(6));
|
||||
expect(stack(fastcat(0, 1, 2), fastcat(3, 4).setTactus(undefined)).tactus).toStrictEqual(Fraction(3));
|
||||
expect(stackLeft(fastcat(0, 1, 2, 3), fastcat(3, 4)).tactus).toStrictEqual(Fraction(4));
|
||||
expect(stackRight(fastcat(0, 1, 2), fastcat(3, 4)).tactus).toStrictEqual(Fraction(3));
|
||||
expect(stack(fastcat(0, 1, 2), fastcat(3, 4))._steps).toStrictEqual(Fraction(6));
|
||||
expect(stack(fastcat(0, 1, 2), fastcat(3, 4).setSteps(undefined))._steps).toStrictEqual(Fraction(3));
|
||||
expect(stackLeft(fastcat(0, 1, 2, 3), fastcat(3, 4))._steps).toStrictEqual(Fraction(4));
|
||||
expect(stackRight(fastcat(0, 1, 2), fastcat(3, 4))._steps).toStrictEqual(Fraction(3));
|
||||
// maybe this should double when they are either all even or all odd
|
||||
expect(stackCentre(fastcat(0, 1, 2), fastcat(3, 4)).tactus).toStrictEqual(Fraction(3));
|
||||
expect(fastcat(0, 1).ply(3).tactus).toStrictEqual(Fraction(6));
|
||||
expect(fastcat(0, 1).setTactus(undefined).ply(3).tactus).toStrictEqual(undefined);
|
||||
expect(fastcat(0, 1).fast(3).tactus).toStrictEqual(Fraction(2));
|
||||
expect(fastcat(0, 1).setTactus(undefined).fast(3).tactus).toStrictEqual(undefined);
|
||||
expect(stackCentre(fastcat(0, 1, 2), fastcat(3, 4))._steps).toStrictEqual(Fraction(3));
|
||||
expect(fastcat(0, 1).ply(3)._steps).toStrictEqual(Fraction(6));
|
||||
expect(fastcat(0, 1).setSteps(undefined).ply(3)._steps).toStrictEqual(undefined);
|
||||
expect(fastcat(0, 1).fast(3)._steps).toStrictEqual(Fraction(2));
|
||||
expect(fastcat(0, 1).setSteps(undefined).fast(3)._steps).toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
describe('s_cat', () => {
|
||||
describe('stepcat', () => {
|
||||
it('can cat', () => {
|
||||
expect(sameFirst(s_cat(fastcat(0, 1, 2, 3), fastcat(4, 5)), fastcat(0, 1, 2, 3, 4, 5)));
|
||||
expect(sameFirst(s_cat(pure(1), pure(2), pure(3)), fastcat(1, 2, 3)));
|
||||
expect(sameFirst(stepcat(fastcat(0, 1, 2, 3), fastcat(4, 5)), fastcat(0, 1, 2, 3, 4, 5)));
|
||||
expect(sameFirst(stepcat(pure(1), pure(2), pure(3)), fastcat(1, 2, 3)));
|
||||
});
|
||||
it('calculates undefined tactuses as the average', () => {
|
||||
expect(sameFirst(s_cat(pure(1), pure(2), pure(3).setTactus(undefined)), fastcat(1, 2, 3)));
|
||||
it('calculates undefined steps as the average', () => {
|
||||
expect(sameFirst(stepcat(pure(1), pure(2), pure(3).setSteps(undefined)), fastcat(1, 2, 3)));
|
||||
});
|
||||
});
|
||||
describe('s_taper', () => {
|
||||
it('can taper', () => {
|
||||
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_taper(1, 5), sequence(0, 1, 2, 3, 4, 0, 1, 2, 3, 0, 1, 2, 0, 1, 0)));
|
||||
describe('shrink', () => {
|
||||
it('can shrink', () => {
|
||||
expect(sameFirst(sequence(0, 1, 2, 3, 4).shrink(1), sequence(0, 1, 2, 3, 4, 1, 2, 3, 4, 2, 3, 4, 3, 4, 4)));
|
||||
});
|
||||
it('can taper backwards', () => {
|
||||
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_taper(-1, 5), sequence(0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4)));
|
||||
it('can shrink backwards', () => {
|
||||
expect(sameFirst(sequence(0, 1, 2, 3, 4).shrink(-1), sequence(0, 1, 2, 3, 4, 0, 1, 2, 3, 0, 1, 2, 0, 1, 0)));
|
||||
});
|
||||
});
|
||||
describe('s_add and s_sub', () => {
|
||||
it('can add from the left', () => {
|
||||
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_add(2), sequence(0, 1)));
|
||||
describe('grow', () => {
|
||||
it('can grow', () => {
|
||||
expect(sameFirst(sequence(0, 1, 2, 3, 4).grow(1), sequence(0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4)));
|
||||
});
|
||||
it('can sub to the left', () => {
|
||||
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_sub(2), sequence(0, 1, 2)));
|
||||
it('can grow backwards', () => {
|
||||
expect(sameFirst(sequence(0, 1, 2, 3, 4).grow(-1), sequence(4, 3, 4, 2, 3, 4, 1, 2, 3, 4, 0, 1, 2, 3, 4)));
|
||||
});
|
||||
it('can add from the right', () => {
|
||||
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_add(-2), sequence(3, 4)));
|
||||
});
|
||||
describe('take and drop', () => {
|
||||
it('can take from the left', () => {
|
||||
expect(sameFirst(sequence(0, 1, 2, 3, 4).take(2), sequence(0, 1)));
|
||||
});
|
||||
it('can sub to the right', () => {
|
||||
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_sub(-2), sequence(2, 3, 4)));
|
||||
it('can drop from the left', () => {
|
||||
expect(sameFirst(sequence(0, 1, 2, 3, 4).drop(2), sequence(2, 3, 4)));
|
||||
});
|
||||
it('can subtract nothing', () => {
|
||||
expect(sameFirst(pure('a').s_sub(0), pure('a')));
|
||||
it('can take from the right', () => {
|
||||
expect(sameFirst(sequence(0, 1, 2, 3, 4).take(-2), sequence(3, 4)));
|
||||
});
|
||||
it('can subtract nothing, repeatedly', () => {
|
||||
expect(sameFirst(pure('a').s_sub(0, 0), fastcat('a', 'a')));
|
||||
it('can drop from the right', () => {
|
||||
expect(sameFirst(sequence(0, 1, 2, 3, 4).drop(-2), sequence(0, 1, 2)));
|
||||
});
|
||||
it('can drop nothing', () => {
|
||||
expect(sameFirst(pure('a').drop(0), pure('a')));
|
||||
});
|
||||
it('can drop nothing, repeatedly', () => {
|
||||
expect(sameFirst(pure('a').drop(0, 0), fastcat('a', 'a')));
|
||||
for (var i = 0; i < 100; ++i) {
|
||||
expect(sameFirst(pure('a').s_sub(...Array(i).fill(0)), fastcat(...Array(i).fill('a'))));
|
||||
expect(sameFirst(pure('a').drop(...Array(i).fill(0)), fastcat(...Array(i).fill('a'))));
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('expand', () => {
|
||||
it('can expand four things in half', () => {
|
||||
expect(
|
||||
sameFirst(sequence(0, 1, 2, 3).expand(1, 0.5), stepcat(sequence(0, 1, 2, 3), sequence(0, 1, 2, 3).expand(0.5))),
|
||||
);
|
||||
});
|
||||
it('can expand five things in half', () => {
|
||||
expect(
|
||||
sameFirst(
|
||||
sequence(0, 1, 2, 3, 4).expand(1, 0.5),
|
||||
stepcat(sequence(0, 1, 2, 3, 4), sequence(0, 1, 2, 3, 4).expand(0.5)),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('stepJoin', () => {
|
||||
it('can join a pattern with steps of 2', () => {
|
||||
expect(
|
||||
sameFirst(
|
||||
sequence(pure(pure('a')), pure(pure('b').setSteps(2))).stepJoin(),
|
||||
stepcat(pure('a'), pure('b').setSteps(2)),
|
||||
),
|
||||
);
|
||||
});
|
||||
it('can join a pattern with steps of 0.5', () => {
|
||||
expect(
|
||||
sameFirst(
|
||||
sequence(pure(pure('a')), pure(pure('b').setSteps(0.5))).stepJoin(),
|
||||
stepcat(pure('a'), pure('b').setSteps(0.5)),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('loopAt', () => {
|
||||
it('maintains steps', () => {
|
||||
expect(s('bev').chop(8).loopAt(2)._steps).toStrictEqual(Fraction(4));
|
||||
});
|
||||
});
|
||||
describe('bite', () => {
|
||||
it('works with uneven patterns', () => {
|
||||
sameFirst(
|
||||
fastcat(slowcat('a', 'b', 'c', 'd', 'e'), slowcat(1, 2, 3, 4, 5))
|
||||
.bite(2, stepcat(pure(0), pure(1).expand(2)))
|
||||
.fast(5),
|
||||
stepcat(slowcat('a', 'b', 'c', 'd', 'e'), slowcat(1, 2, 3, 4, 5).expand(2)).fast(5),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -91,6 +91,9 @@ export const midi2note = (n) => {
|
||||
// modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does)
|
||||
export const _mod = (n, m) => ((n % m) + m) % m;
|
||||
|
||||
// average numbers in an array
|
||||
export const averageArray = (arr) => arr.reduce((a, b) => a + b) / arr.length;
|
||||
|
||||
export function nanFallback(value, fallback = 0) {
|
||||
if (isNaN(Number(value))) {
|
||||
logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning');
|
||||
@@ -360,6 +363,108 @@ export function objectMap(obj, fn) {
|
||||
}
|
||||
return Object.fromEntries(Object.entries(obj).map(([k, v], i) => [k, fn(v, k, i)]));
|
||||
}
|
||||
export function cycleToSeconds(cycle, cps) {
|
||||
return cycle / cps;
|
||||
}
|
||||
|
||||
// utility for averaging two clocks together to account for drift
|
||||
export class ClockCollator {
|
||||
constructor({
|
||||
getTargetClockTime = getUnixTimeSeconds,
|
||||
weight = 16,
|
||||
offsetDelta = 0.005,
|
||||
checkAfterTime = 2,
|
||||
resetAfterTime = 8,
|
||||
}) {
|
||||
this.offsetTime;
|
||||
this.timeAtPrevOffsetSample;
|
||||
this.prevOffsetTimes = [];
|
||||
this.getTargetClockTime = getTargetClockTime;
|
||||
this.weight = weight;
|
||||
this.offsetDelta = offsetDelta;
|
||||
this.checkAfterTime = checkAfterTime;
|
||||
this.resetAfterTime = resetAfterTime;
|
||||
this.reset = () => {
|
||||
this.prevOffsetTimes = [];
|
||||
this.offsetTime = null;
|
||||
this.timeAtPrevOffsetSample = null;
|
||||
};
|
||||
}
|
||||
calculateOffset(currentTime) {
|
||||
const targetClockTime = this.getTargetClockTime();
|
||||
const diffBetweenTimeSamples = targetClockTime - this.timeAtPrevOffsetSample;
|
||||
const newOffsetTime = targetClockTime - currentTime;
|
||||
// recalcuate the diff from scratch if the clock has been paused for some time.
|
||||
if (diffBetweenTimeSamples > this.resetAfterTime) {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
if (this.offsetTime == null) {
|
||||
this.offsetTime = newOffsetTime;
|
||||
}
|
||||
this.prevOffsetTimes.push(newOffsetTime);
|
||||
if (this.prevOffsetTimes.length > this.weight) {
|
||||
this.prevOffsetTimes.shift();
|
||||
}
|
||||
|
||||
// after X time has passed, the average of the previous weight offset times is calculated and used as a stable reference
|
||||
// for calculating the timestamp
|
||||
if (this.timeAtPrevOffsetSample == null || diffBetweenTimeSamples > this.checkAfterTime) {
|
||||
this.timeAtPrevOffsetSample = targetClockTime;
|
||||
const rollingOffsetTime = averageArray(this.prevOffsetTimes);
|
||||
//when the clock offsets surpass the delta, set the new reference time
|
||||
if (Math.abs(rollingOffsetTime - this.offsetTime) > this.offsetDelta) {
|
||||
this.offsetTime = rollingOffsetTime;
|
||||
}
|
||||
}
|
||||
|
||||
return this.offsetTime;
|
||||
}
|
||||
|
||||
calculateTimestamp(currentTime, targetTime) {
|
||||
return this.calculateOffset(currentTime) + targetTime;
|
||||
}
|
||||
}
|
||||
|
||||
export function getPerformanceTimeSeconds() {
|
||||
return performance.now() * 0.001;
|
||||
}
|
||||
|
||||
function getUnixTimeSeconds() {
|
||||
return Date.now() * 0.001;
|
||||
}
|
||||
|
||||
export const keyAlias = new Map([
|
||||
['control', 'Control'],
|
||||
['ctrl', 'Control'],
|
||||
['alt', 'Alt'],
|
||||
['shift', 'Shift'],
|
||||
['down', 'ArrowDown'],
|
||||
['up', 'ArrowUp'],
|
||||
['left', 'ArrowLeft'],
|
||||
['right', 'ArrowRight'],
|
||||
]);
|
||||
let keyState;
|
||||
|
||||
export function getCurrentKeyboardState() {
|
||||
if (keyState == null) {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
keyState = {};
|
||||
// Listen for the keydown event to mark the key as pressed
|
||||
window.addEventListener('keydown', (event) => {
|
||||
keyState[event.key] = true; // Mark the key as pressed
|
||||
});
|
||||
|
||||
// Listen for the keyup event to mark the key as released
|
||||
window.addEventListener('keyup', (event) => {
|
||||
keyState[event.key] = false; // Mark the key as released
|
||||
});
|
||||
}
|
||||
|
||||
return { ...keyState }; // Return a shallow copy of the key state object
|
||||
}
|
||||
|
||||
// Floating point versions, see Fraction for rational versions
|
||||
// // greatest common divisor
|
||||
|
||||
@@ -4,6 +4,7 @@ import { resolve } from 'path';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [],
|
||||
build: {
|
||||
lib: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/csound",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.3",
|
||||
"description": "csound bindings for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
@@ -37,6 +37,6 @@
|
||||
"@strudel/webaudio": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,36 @@
|
||||
import { parseNumeral, Pattern, getEventOffsetMs } from '@strudel/core';
|
||||
import { Pattern, ClockCollator } from '@strudel/core';
|
||||
import { parseControlsFromHap } from 'node_modules/@strudel/osc/osc.mjs';
|
||||
import { Invoke } from './utils.mjs';
|
||||
|
||||
Pattern.prototype.osc = function () {
|
||||
return this.onTrigger(async (time, hap, currentTime, cps = 1, targetTime) => {
|
||||
hap.ensureObjectValue();
|
||||
const cycle = hap.wholeOrPart().begin.valueOf();
|
||||
const delta = hap.duration.valueOf();
|
||||
const controls = Object.assign({}, { cps, cycle, delta }, hap.value);
|
||||
// make sure n and note are numbers
|
||||
controls.n && (controls.n = parseNumeral(controls.n));
|
||||
controls.note && (controls.note = parseNumeral(controls.note));
|
||||
const collator = new ClockCollator({});
|
||||
|
||||
const params = [];
|
||||
export async function oscTriggerTauri(t_deprecate, hap, currentTime, cps = 1, targetTime) {
|
||||
const controls = parseControlsFromHap(hap, cps);
|
||||
const params = [];
|
||||
const timestamp = collator.calculateTimestamp(currentTime, targetTime);
|
||||
|
||||
const timestamp = Math.round(Date.now() + getEventOffsetMs(targetTime, currentTime));
|
||||
Object.keys(controls).forEach((key) => {
|
||||
const val = controls[key];
|
||||
const value = typeof val === 'number' ? val.toString() : val;
|
||||
|
||||
Object.keys(controls).forEach((key) => {
|
||||
const val = controls[key];
|
||||
const value = typeof val === 'number' ? val.toString() : val;
|
||||
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
params.push({
|
||||
name: key,
|
||||
value,
|
||||
valueisnumber: typeof val === 'number',
|
||||
});
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
params.push({
|
||||
name: key,
|
||||
value,
|
||||
valueisnumber: typeof val === 'number',
|
||||
});
|
||||
|
||||
const messagesfromjs = [];
|
||||
if (params.length) {
|
||||
messagesfromjs.push({ target: '/dirt/play', timestamp, params });
|
||||
}
|
||||
|
||||
if (messagesfromjs.length) {
|
||||
setTimeout(() => {
|
||||
Invoke('sendosc', { messagesfromjs });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (params.length === 0) {
|
||||
return;
|
||||
}
|
||||
const message = { target: '/dirt/play', timestamp, params };
|
||||
setTimeout(() => {
|
||||
Invoke('sendosc', { messagesfromjs: [message] });
|
||||
});
|
||||
}
|
||||
Pattern.prototype.osc = function () {
|
||||
return this.onTrigger(oscTriggerTauri);
|
||||
};
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
{
|
||||
"name": "@strudel/desktopbridge",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "tools/shims for communicating between the JS and Tauri (Rust) sides of the Studel desktop app",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tidalcycles",
|
||||
"strudel",
|
||||
"pattern",
|
||||
"livecoding",
|
||||
"algorave"
|
||||
],
|
||||
"author": "Jade Rowland <jaderowlanddev@gmail.com>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strudel/core": "workspace:*",
|
||||
"@tauri-apps/api": "^1.5.3"
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme"
|
||||
}
|
||||
"name": "@strudel/desktopbridge",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "tools/shims for communicating between the JS and Tauri (Rust) sides of the Studel desktop app",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tidalcycles",
|
||||
"strudel",
|
||||
"pattern",
|
||||
"livecoding",
|
||||
"algorave"
|
||||
],
|
||||
"author": "Jade Rowland <jaderowlanddev@gmail.com>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strudel/core": "workspace:*",
|
||||
"@tauri-apps/api": "^2.2.0"
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme"
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
export const Invoke = invoke;
|
||||
export const isTauri = () => window.__TAURI_IPC__ != null;
|
||||
|
||||
@@ -26,7 +26,7 @@ export const getDrawContext = (id = 'test-canvas', options) => {
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
return canvas.getContext(contextType);
|
||||
return canvas.getContext(contextType, { willReadFrequently: true });
|
||||
};
|
||||
|
||||
let animationFrames = {};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/draw",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Helpers for drawing with Strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
@@ -32,6 +32,6 @@
|
||||
"@strudel/core": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
+44
-34
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Pattern, noteToMidi, freqToMidi } from '@strudel/core';
|
||||
import { Pattern, noteToMidi, freqToMidi, isPattern } from '@strudel/core';
|
||||
import { getTheme, getDrawContext } from './draw.mjs';
|
||||
|
||||
const scale = (normalized, min, max) => normalized * (max - min) + min;
|
||||
@@ -36,35 +36,9 @@ const getValue = (e) => {
|
||||
return value;
|
||||
};
|
||||
|
||||
Pattern.prototype.pianoroll = function (options = {}) {
|
||||
let { cycles = 4, playhead = 0.5, overscan = 0, hideNegative = false, ctx = getDrawContext(), id = 1 } = options;
|
||||
|
||||
let from = -cycles * playhead;
|
||||
let to = cycles * (1 - playhead);
|
||||
const inFrame = (hap, t) => (!hideNegative || hap.whole.begin >= 0) && hap.isWithinTime(t + from, t + to);
|
||||
this.draw(
|
||||
(haps, time) => {
|
||||
pianoroll({
|
||||
...options,
|
||||
time,
|
||||
ctx,
|
||||
haps: haps.filter((hap) => inFrame(hap, time)),
|
||||
});
|
||||
},
|
||||
{
|
||||
lookbehind: from - overscan,
|
||||
lookahead: to + overscan,
|
||||
id,
|
||||
},
|
||||
);
|
||||
return this;
|
||||
};
|
||||
|
||||
// this function allows drawing a pianoroll without ties to Pattern.prototype
|
||||
// it will probably replace the above in the future
|
||||
|
||||
/**
|
||||
* Displays a midi-style piano roll
|
||||
* Visualises a pattern as a scrolling 'pianoroll', displayed in the background of the editor. To show a pianoroll for all running patterns, use `all(pianoroll)`. To have a pianoroll appear below
|
||||
* a pattern instead, prefix with `_`, e.g.: `sound("bd sd")._pianoroll()`.
|
||||
*
|
||||
* @name pianoroll
|
||||
* @synonyms punchcard
|
||||
@@ -93,15 +67,51 @@ Pattern.prototype.pianoroll = function (options = {}) {
|
||||
* @param {integer} minMidi minimum note value to display on the value axis - defaults to 10
|
||||
* @param {integer} maxMidi maximum note value to display on the value axis - defaults to 90
|
||||
* @param {boolean} autorange automatically calculate the minMidi and maxMidi parameters - 0 by default
|
||||
*
|
||||
* @see _pianoroll
|
||||
* @example
|
||||
* note("c2 a2 eb2")
|
||||
* .euclid(5,8)
|
||||
* .s('sawtooth')
|
||||
* .lpenv(4).lpf(300)
|
||||
* ._pianoroll({ labels: 1 })
|
||||
* .pianoroll({ labels: 1 })
|
||||
*/
|
||||
export function pianoroll({
|
||||
|
||||
Pattern.prototype.pianoroll = function (options = {}) {
|
||||
let { cycles = 4, playhead = 0.5, overscan = 0, hideNegative = false, ctx = getDrawContext(), id = 1 } = options;
|
||||
|
||||
let from = -cycles * playhead;
|
||||
let to = cycles * (1 - playhead);
|
||||
const inFrame = (hap, t) => (!hideNegative || hap.whole.begin >= 0) && hap.isWithinTime(t + from, t + to);
|
||||
this.draw(
|
||||
(haps, time) => {
|
||||
__pianoroll({
|
||||
...options,
|
||||
time,
|
||||
ctx,
|
||||
haps: haps.filter((hap) => inFrame(hap, time)),
|
||||
});
|
||||
},
|
||||
{
|
||||
lookbehind: from - overscan,
|
||||
lookahead: to + overscan,
|
||||
id,
|
||||
},
|
||||
);
|
||||
return this;
|
||||
};
|
||||
|
||||
export function pianoroll(arg) {
|
||||
if (isPattern(arg)) {
|
||||
// Single argument as a pattern
|
||||
// (to support `all(pianoroll)`)
|
||||
return arg.pianoroll();
|
||||
}
|
||||
// Single argument with option - return function to get the pattern
|
||||
// (to support `all(pianoroll(options))`)
|
||||
return (pat) => pat.pianoroll(arg);
|
||||
}
|
||||
|
||||
export function __pianoroll({
|
||||
time,
|
||||
haps,
|
||||
cycles = 4,
|
||||
@@ -278,7 +288,7 @@ export function getDrawOptions(drawTime, options = {}) {
|
||||
export const getPunchcardPainter =
|
||||
(options = {}) =>
|
||||
(ctx, time, haps, drawTime) =>
|
||||
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, options) });
|
||||
__pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, options) });
|
||||
|
||||
Pattern.prototype.punchcard = function (options) {
|
||||
return this.onPaint(getPunchcardPainter(options));
|
||||
@@ -302,5 +312,5 @@ Pattern.prototype.wordfall = function (options) {
|
||||
|
||||
export function drawPianoroll(options) {
|
||||
const { drawTime, ...rest } = options;
|
||||
pianoroll({ ...getDrawOptions(drawTime), ...rest });
|
||||
__pianoroll({ ...getDrawOptions(drawTime), ...rest });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# @strudel/gamepad
|
||||
|
||||
This package adds gamepad input functionality to strudel Patterns.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
npm i @strudel/gamepad --save
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```javascript
|
||||
import { gamepad } from '@strudel/gamepad';
|
||||
|
||||
// Initialize gamepad (optional index parameter, defaults to 0)
|
||||
const pad = gamepad(0);
|
||||
|
||||
// Use gamepad inputs in patterns
|
||||
const pattern = sequence([
|
||||
// Button inputs
|
||||
pad.a, // A button value (0-1)
|
||||
pad.tglA, // A button toggle (0 or 1)
|
||||
|
||||
// Analog stick inputs
|
||||
pad.x1, // Left stick X (0-1)
|
||||
pad.x1_2, // Left stick X (-1 to 1)
|
||||
]);
|
||||
```
|
||||
|
||||
## Available Controls
|
||||
|
||||
### Buttons
|
||||
- Face Buttons
|
||||
- `a`, `b`, `x`, `y` (or uppercase `A`, `B`, `X`, `Y`)
|
||||
- Toggle versions: `tglA`, `tglB`, `tglX`, `tglY`
|
||||
- Shoulder Buttons
|
||||
- `lb`, `rb`, `lt`, `rt` (or uppercase `LB`, `RB`, `LT`, `RT`)
|
||||
- Toggle versions: `tglLB`, `tglRB`, `tglLT`, `tglRT`
|
||||
- D-Pad
|
||||
- `up`, `down`, `left`, `right` (or `u`, `d`, `l`, `r` or uppercase)
|
||||
- Toggle versions: `tglUp`, `tglDown`, `tglLeft`, `tglRight`(or `tglU`, `tglD`, `tglL`, `tglR`)
|
||||
|
||||
### Analog Sticks
|
||||
- Left Stick
|
||||
- `x1`, `y1` (0 to 1 range)
|
||||
- `x1_2`, `y1_2` (-1 to 1 range)
|
||||
- Right Stick
|
||||
- `x2`, `y2` (0 to 1 range)
|
||||
- `x2_2`, `y2_2` (-1 to 1 range)
|
||||
|
||||
## Examples
|
||||
|
||||
```javascript
|
||||
// Use button values to control amplitude
|
||||
$: sequence([
|
||||
s("bd").gain(pad.X), // X button controls gain
|
||||
s("[hh oh]").gain(pad.tglY), // Y button toggles gain
|
||||
]);
|
||||
|
||||
// Use analog stick for continuous control
|
||||
$: note("c4*4".add(pad.y1_2.range(-24,24))) // Left stick Y controls pitch shift
|
||||
.pan(pad.x1_2); // Left stick X controls panning
|
||||
|
||||
// Use toggle buttons to switch patterns on/off
|
||||
|
||||
// Define button sequences
|
||||
const HADOKEN = [
|
||||
'd', // Down
|
||||
'r', // Right
|
||||
'a', // A
|
||||
];
|
||||
|
||||
const KONAMI = 'uuddlrlrba' //Konami Code ↑↑↓↓←→←→BA
|
||||
|
||||
// Add these lines to enable buttons(but why?)
|
||||
$:pad.D.segment(16).gain(0)
|
||||
$:pad.R.segment(16).gain(0)
|
||||
$:pad.A.segment(16).gain(0)
|
||||
|
||||
// Check button sequence (returns 1 when detected, 0 when not within last 1 second)
|
||||
$: sound("hadoken").gain(pad.checkSequence(HADOKEN))
|
||||
|
||||
```
|
||||
|
||||
## Multiple Gamepads
|
||||
|
||||
You can connect multiple gamepads by specifying the gamepad index:
|
||||
|
||||
```javascript
|
||||
const pad1 = gamepad(0); // First gamepad
|
||||
const pad2 = gamepad(1); // Second gamepad
|
||||
```
|
||||
@@ -0,0 +1,117 @@
|
||||
import { MiniRepl } from '../../../website/src/docs/MiniRepl';
|
||||
|
||||
# Gamepad
|
||||
|
||||
The Gamepad module allows you to integrate gamepad input functionality into your musical patterns. This can be particularly useful for live performances or interactive installations where you want to manipulate sounds using a game controller.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Initialize a gamepad by calling the gamepad() function with an optional index parameter.
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`// Initialize gamepad (optional index parameter, defaults to 0)
|
||||
const gp = gamepad(0)
|
||||
note("c a f e").mask(gp.a)`}
|
||||
/>
|
||||
|
||||
## Available Controls
|
||||
|
||||
The gamepad module provides access to buttons and analog sticks as normalized signals (0-1) that can modulate your patterns.
|
||||
|
||||
### Buttons
|
||||
|
||||
| Type | Controls |
|
||||
| ---------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| Face Buttons | `a`, `b`, `x`, `y` (or uppercase `A`, `B`, `X`, `Y`) |
|
||||
| | Toggle versions: `tglA`, `tglB`, `tglX`, `tglY` |
|
||||
| Shoulder Buttons | `lb`, `rb`, `lt`, `rt` (or uppercase `LB`, `RB`, `LT`, `RT`) |
|
||||
| | Toggle versions: `tglLB`, `tglRB`, `tglLT`, `tglRT` |
|
||||
| D-Pad | `up`, `down`, `left`, `right` (or `u`, `d`, `l`, `r` or uppercase) |
|
||||
| | Toggle versions: `tglUp`, `tglDown`, `tglLeft`, `tglRight` (or `tglU`, `tglD`, `tglL`, `tglR`) |
|
||||
|
||||
### Analog Sticks
|
||||
|
||||
| Stick | Controls |
|
||||
| ----------- | ------------------------------ |
|
||||
| Left Stick | `x1`, `y1` (0 to 1 range) |
|
||||
| | `x1_2`, `y1_2` (-1 to 1 range) |
|
||||
| Right Stick | `x2`, `y2` (0 to 1 range) |
|
||||
| | `x2_2`, `y2_2` (-1 to 1 range) |
|
||||
|
||||
### Button Sequence
|
||||
|
||||
| Stick | Controls |
|
||||
| --------------- | --------------------------------------- |
|
||||
| Button Sequence | `btnSequence()`, `btnSeq()`, `btnseq()` |
|
||||
|
||||
## Using Gamepad Inputs
|
||||
|
||||
Once initialized, you can use various gamepad inputs in your patterns. Here are some examples:
|
||||
|
||||
### Button Inputs
|
||||
|
||||
You can use button inputs to control different aspects of your music, such as gain or triggering events.
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`const gp = gamepad(0)
|
||||
// Use button values to control amplitude
|
||||
$: stack(
|
||||
s("[[hh hh] oh hh oh]/2").mask(gp.tglX).bank("RolandTR909"), // X btn for HH
|
||||
s("cr*1").mask(gp.Y).bank("RolandTR909"), // LB btn for CR
|
||||
s("bd").mask(gp.tglA).bank("RolandTR909"), // A btn for BD
|
||||
s("[ht - - mt - - lt - ]/2").mask(gp.tglB).bank("RolandTR909"), // B btn for Toms
|
||||
s("sd*4").mask(gp.RB).bank("RolandTR909"), // RB btn for SD
|
||||
).cpm(120)
|
||||
`}
|
||||
/>
|
||||
|
||||
### Analog Stick Inputs
|
||||
|
||||
Analog sticks can be used for continuous control, such as pitch shifting or panning.
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`const gp = gamepad(0)
|
||||
// Use analog stick for continuous control
|
||||
$: note("c4 d3 a3 e3").sound("sawtooth")
|
||||
.lpf(gp.x1.range(100,4000))
|
||||
.lpq(gp.y1.range(5,30))
|
||||
.decay(gp.y2.range(0.1,2))
|
||||
.lpenv(gp.x2.range(-5,5))
|
||||
.cpm(120)
|
||||
`}
|
||||
/>
|
||||
|
||||
### Button Sequences
|
||||
|
||||
You can define button sequences to trigger specific actions, like playing a sound when a sequence is detected.
|
||||
|
||||
<MiniRepl client:idle tune={`const gp = gamepad(0)
|
||||
// Define button sequences
|
||||
const HADOUKEN = [
|
||||
'd', // Down
|
||||
'r', // Right
|
||||
'a', // A
|
||||
]
|
||||
const KONAMI = 'uuddlrlrba' //Konami Code ↑↑↓↓←→←→BA
|
||||
|
||||
// Check butto-n sequence (returns 1 while detected, 0 when not within last 1 second)
|
||||
$: s("free_hadouken -").slow(2)
|
||||
.mask(gp.btnSequence(HADOUKEN)).room(1).cpm(120)
|
||||
|
||||
// hadouken.wav by Syna-Max
|
||||
//https://freesound.org/people/Syna-Max/sounds/67674/
|
||||
samples({free_hadouken: 'https://cdn.freesound.org/previews/67/67674_111920-lq.mp3'})
|
||||
`} />
|
||||
|
||||
## Multiple Gamepads
|
||||
|
||||
Strudel supports multiple gamepads. You can specify the gamepad index to connect to different devices.
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`const pad1 = gamepad(0); // First gamepad
|
||||
const pad2 = gamepad(1); // Second gamepad`}
|
||||
/>
|
||||
@@ -0,0 +1,246 @@
|
||||
// @strudel/gamepad/index.mjs
|
||||
|
||||
import { signal } from '@strudel/core';
|
||||
|
||||
// Button mapping for Logitech Dual Action (STANDARD GAMEPAD Vendor: 046d Product: c216)
|
||||
export const buttonMap = {
|
||||
a: 0,
|
||||
b: 1,
|
||||
x: 2,
|
||||
y: 3,
|
||||
lb: 4,
|
||||
rb: 5,
|
||||
lt: 6,
|
||||
rt: 7,
|
||||
back: 8,
|
||||
start: 9,
|
||||
u: 12,
|
||||
up: 12,
|
||||
d: 13,
|
||||
down: 13,
|
||||
l: 14,
|
||||
left: 14,
|
||||
r: 15,
|
||||
right: 15,
|
||||
};
|
||||
|
||||
class ButtonSequenceDetector {
|
||||
constructor(timeWindow = 1000) {
|
||||
this.sequence = [];
|
||||
this.timeWindow = timeWindow;
|
||||
this.lastInputTime = 0;
|
||||
this.buttonStates = Array(16).fill(0); // Track previous state of each button
|
||||
// Button mapping for character inputs
|
||||
}
|
||||
|
||||
addInput(buttonIndex, buttonValue) {
|
||||
const currentTime = Date.now();
|
||||
|
||||
// Only add input on button press (rising edge)
|
||||
if (buttonValue === 1 && this.buttonStates[buttonIndex] === 0) {
|
||||
// Clear sequence if too much time has passed
|
||||
if (currentTime - this.lastInputTime > this.timeWindow) {
|
||||
this.sequence = [];
|
||||
}
|
||||
|
||||
// Store the button name instead of index
|
||||
const buttonName = Object.keys(buttonMap).find((key) => buttonMap[key] === buttonIndex) || buttonIndex.toString();
|
||||
|
||||
this.sequence.push({
|
||||
input: buttonName,
|
||||
timestamp: currentTime,
|
||||
});
|
||||
|
||||
this.lastInputTime = currentTime;
|
||||
|
||||
//console.log(this.sequence);
|
||||
// Keep only inputs within the time window
|
||||
this.sequence = this.sequence.filter((entry) => currentTime - entry.timestamp <= this.timeWindow);
|
||||
}
|
||||
|
||||
// Update button state
|
||||
this.buttonStates[buttonIndex] = buttonValue;
|
||||
}
|
||||
|
||||
checkSequence(targetSequence) {
|
||||
if (!Array.isArray(targetSequence) && typeof targetSequence !== 'string') {
|
||||
console.error('ButtonSequenceDetector: targetSequence must be an array or string');
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.sequence.length < targetSequence.length) return 0;
|
||||
|
||||
// Convert string input to array if needed
|
||||
const sequence =
|
||||
typeof targetSequence === 'string'
|
||||
? targetSequence.toLowerCase().split('')
|
||||
: targetSequence.map((s) => s.toString().toLowerCase());
|
||||
|
||||
//console.log(this.sequence);
|
||||
|
||||
// Get the last n inputs where n is the target sequence length
|
||||
const lastInputs = this.sequence.slice(-targetSequence.length).map((entry) => entry.input);
|
||||
|
||||
// Compare sequences
|
||||
return lastInputs.every((input, index) => {
|
||||
const target = sequence[index];
|
||||
// Check if either the input matches directly or they refer to the same button in the map
|
||||
return (
|
||||
input === target ||
|
||||
buttonMap[input] === buttonMap[target] ||
|
||||
// Also check if the numerical index matches
|
||||
buttonMap[input] === parseInt(target)
|
||||
);
|
||||
})
|
||||
? 1
|
||||
: 0;
|
||||
}
|
||||
}
|
||||
|
||||
class GamepadHandler {
|
||||
constructor(index = 0) {
|
||||
// Add index parameter
|
||||
this._gamepads = {};
|
||||
this._activeGamepad = index; // Use provided index
|
||||
this._axes = [0, 0, 0, 0];
|
||||
this._buttons = Array(16).fill(0);
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
window.addEventListener('gamepadconnected', (e) => {
|
||||
this._gamepads[e.gamepad.index] = e.gamepad;
|
||||
if (!this._activeGamepad) {
|
||||
this._activeGamepad = e.gamepad.index;
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('gamepaddisconnected', (e) => {
|
||||
delete this._gamepads[e.gamepad.index];
|
||||
if (this._activeGamepad === e.gamepad.index) {
|
||||
this._activeGamepad = Object.keys(this._gamepads)[0] || null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
poll() {
|
||||
if (this._activeGamepad !== null) {
|
||||
const gamepad = navigator.getGamepads()[this._activeGamepad];
|
||||
if (gamepad) {
|
||||
// Update axes (normalized to 0-1 range)
|
||||
this._axes = gamepad.axes.map((axis) => (axis + 1) / 2);
|
||||
// Update buttons
|
||||
this._buttons = gamepad.buttons.map((button) => button.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getAxes() {
|
||||
return this._axes;
|
||||
}
|
||||
getButtons() {
|
||||
return this._buttons;
|
||||
}
|
||||
}
|
||||
|
||||
// Module-level state store for toggle states
|
||||
const gamepadStates = new Map();
|
||||
|
||||
export const gamepad = (index = 0) => {
|
||||
const handler = new GamepadHandler(index);
|
||||
const sequenceDetector = new ButtonSequenceDetector(2000);
|
||||
|
||||
// Base signal that polls gamepad state and handles sequence detection
|
||||
const baseSignal = signal((t) => {
|
||||
handler.poll();
|
||||
const axes = handler.getAxes();
|
||||
const buttons = handler.getButtons();
|
||||
|
||||
// Add all button inputs to sequence detector
|
||||
buttons.forEach((value, i) => {
|
||||
sequenceDetector.addInput(i, value);
|
||||
});
|
||||
|
||||
return { axes, buttons, t };
|
||||
});
|
||||
|
||||
// Create axes patterns
|
||||
const axes = {
|
||||
x1: baseSignal.fmap((state) => state.axes[0]),
|
||||
y1: baseSignal.fmap((state) => state.axes[1]),
|
||||
x2: baseSignal.fmap((state) => state.axes[2]),
|
||||
y2: baseSignal.fmap((state) => state.axes[3]),
|
||||
};
|
||||
|
||||
// Add bipolar versions
|
||||
axes.x1_2 = axes.x1.toBipolar();
|
||||
axes.y1_2 = axes.y1.toBipolar();
|
||||
axes.x2_2 = axes.x2.toBipolar();
|
||||
axes.y2_2 = axes.y2.toBipolar();
|
||||
|
||||
// Create button patterns
|
||||
const buttons = Array(16)
|
||||
.fill(null)
|
||||
.map((_, i) => {
|
||||
// Create unique key for this gamepad+button combination
|
||||
const stateKey = `gamepad${index}_btn${i}`;
|
||||
|
||||
// Initialize toggle state if it doesn't exist
|
||||
if (!gamepadStates.has(stateKey)) {
|
||||
gamepadStates.set(stateKey, {
|
||||
lastButtonState: 0,
|
||||
toggleState: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Direct button value pattern (no longer needs to call addInput)
|
||||
const btn = baseSignal.fmap((state) => state.buttons[i]);
|
||||
|
||||
// Button toggle pattern with persistent state
|
||||
const toggle = baseSignal.fmap((state) => {
|
||||
const currentState = state.buttons[i];
|
||||
const buttonState = gamepadStates.get(stateKey);
|
||||
|
||||
if (currentState === 1 && buttonState.lastButtonState === 0) {
|
||||
// Toggle the state on rising edge
|
||||
buttonState.toggleState = buttonState.toggleState === 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
buttonState.lastButtonState = currentState;
|
||||
return buttonState.toggleState;
|
||||
});
|
||||
|
||||
return { value: btn, toggle };
|
||||
});
|
||||
|
||||
// Create sequence checker pattern
|
||||
const btnSequence = (sequence) => {
|
||||
return baseSignal.fmap(() => sequenceDetector.checkSequence(sequence));
|
||||
};
|
||||
const checkSequence = btnSequence;
|
||||
const btnSeq = btnSequence;
|
||||
const btnseq = btnSeq;
|
||||
|
||||
// Return an object with all controls
|
||||
return {
|
||||
...axes,
|
||||
buttons,
|
||||
...Object.fromEntries(
|
||||
Object.entries(buttonMap).flatMap(([key, index]) => [
|
||||
[key.toLowerCase(), buttons[index].value],
|
||||
[key.toUpperCase(), buttons[index].value],
|
||||
[`tgl${key.toLowerCase()}`, buttons[index].toggle],
|
||||
[`tgl${key.toUpperCase()}`, buttons[index].toggle],
|
||||
]),
|
||||
),
|
||||
checkSequence,
|
||||
btnSequence,
|
||||
btnSeq,
|
||||
btnseq,
|
||||
raw: baseSignal,
|
||||
};
|
||||
};
|
||||
|
||||
// Optional: Export for debugging or state management
|
||||
export const getGamepadStates = () => Object.fromEntries(gamepadStates);
|
||||
export const clearGamepadStates = () => gamepadStates.clear();
|
||||
@@ -0,0 +1,3 @@
|
||||
import './gamepad.mjs';
|
||||
|
||||
export * from './gamepad.mjs';
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@strudel/gamepad",
|
||||
"version": "1.2.2",
|
||||
"description": "Gamepad Inputs for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"titdalcycles",
|
||||
"strudel",
|
||||
"pattern",
|
||||
"livecoding",
|
||||
"algorave"
|
||||
],
|
||||
"author": "Yuta Nakayama <nkymut@gmail.com>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel/core": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import { dependencies } from './package.json';
|
||||
import { resolve } from 'path';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
},
|
||||
target: 'esnext',
|
||||
},
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "hs2js",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"private": true,
|
||||
"description": "Experimental Haskell in JavaScript interpreter",
|
||||
"main": "src/index.mjs",
|
||||
"type": "module",
|
||||
@@ -28,10 +29,10 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel",
|
||||
"dependencies": {
|
||||
"web-tree-sitter": "^0.20.8"
|
||||
"web-tree-sitter": "^0.24.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tree-sitter-haskell": "^0.21.0",
|
||||
"vite": "^5.0.10"
|
||||
"tree-sitter-haskell": "^0.23.1",
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/hydra",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Hydra integration for strudel",
|
||||
"main": "hydra.mjs",
|
||||
"type": "module",
|
||||
@@ -39,6 +39,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"pkg": "^5.8.1",
|
||||
"vite": "^5.0.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,3 +7,187 @@ This package adds midi functionality to strudel Patterns.
|
||||
```sh
|
||||
npm i @strudel/midi --save
|
||||
```
|
||||
|
||||
## Available Controls
|
||||
|
||||
The following MIDI controls are available:
|
||||
|
||||
OUTPUT:
|
||||
|
||||
- `midi` - opens a midi output device.
|
||||
- `note` - Sends MIDI note messages. Can accept note names (e.g. "c4") or MIDI note numbers (0-127)
|
||||
- `midichan` - Sets the MIDI channel (1-16, defaults to 1)
|
||||
- `velocity` - Sets note velocity (0-1, defaults to 0.9)
|
||||
- `gain` - Modifies velocity by multiplying with it (0-1, defaults to 1)
|
||||
- `control` - Sets MIDI control change messages
|
||||
- `ccn` - Sets MIDI CC controller number (0-127)
|
||||
- `ccv` - Sets MIDI CC value (0-1)
|
||||
- `progNum` - Sends MIDI program change messages (0-127)
|
||||
- `sysex` - Sends MIDI System Exclusive messages (id: number 0-127 or array of bytes 0-127, data: array of bytes 0-127)
|
||||
- `sysexid` - Sets MIDI System Exclusive ID (number 0-127 or array of bytes 0-127)
|
||||
- `sysexdata` - Sets MIDI System Exclusive data (array of bytes 0-127)
|
||||
- `midibend` - Sets MIDI pitch bend (-1 - 1)
|
||||
- `miditouch` - Sets MIDI key after touch (0-1)
|
||||
- `midicmd` - Sends MIDI system real-time messages to control timing and transport on MIDI devices.
|
||||
- `nrpnn` - Sets MIDI NRPN non-registered parameter number (array of bytes 0-127)
|
||||
- `nrpv` - Sets MIDI NRPN non-registered parameter value (0-127)
|
||||
|
||||
|
||||
INPUT:
|
||||
|
||||
- `midin` - Opens a MIDI input port to receive MIDI control change messages.
|
||||
|
||||
Additional controls can be mapped using the mapping object passed to `.midi()`:
|
||||
|
||||
## Examples
|
||||
|
||||
### midi(outputName?, options?)
|
||||
|
||||
Either connect a midi device or use the IAC Driver (Mac) or Midi Through Port (Linux) for internal midi messages.
|
||||
If no outputName is given, it uses the first midi output it finds.
|
||||
|
||||
```javascript
|
||||
$: chord("<C^7 A7 Dm7 G7>").voicing().midi('IAC Driver')
|
||||
```
|
||||
|
||||
In the console, you will see a log of the available MIDI devices as soon as you run the code, e.g. `Midi connected! Using "Midi Through Port-0".`
|
||||
|
||||
### Options
|
||||
|
||||
The `.midi()` function accepts an options object with the following properties:
|
||||
|
||||
```javascript
|
||||
$: note("c a f e").midi('IAC Driver', { isController: true, midimap: 'default'})
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Available Options</summary>
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| isController | boolean | false | When true, disables sending note messages. Useful for MIDI controllers |
|
||||
| latencyMs | number | 34 | Latency in milliseconds to align MIDI with audio engine |
|
||||
| noteOffsetMs | number | 10 | Offset in milliseconds for note-off messages to prevent glitching |
|
||||
| midichannel | number | 1 | Default MIDI channel (1-16) |
|
||||
| velocity | number | 0.9 | Default note velocity (0-1) |
|
||||
| gain | number | 1 | Default gain multiplier for velocity (0-1) |
|
||||
| midimap | string | 'default' | Name of MIDI mapping to use for control changes |
|
||||
| midiport | string/number | - | MIDI device name or index |
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
|
||||
### midiport(outputName)
|
||||
|
||||
Selects the MIDI output device to use, pattern can be used to switch between devices.
|
||||
|
||||
```javascript
|
||||
$: midiport('IAC Driver')
|
||||
$: note("c a f e").midiport("<0 1 2 3>").midi()
|
||||
```
|
||||
|
||||
### midichan(number)
|
||||
|
||||
Selects the MIDI channel to use. If not used, `.midi` will use channel 1 by default.
|
||||
|
||||
### control, ccn && ccv
|
||||
|
||||
`control` sends MIDI control change messages to your MIDI device.
|
||||
|
||||
- `ccn` sets the cc number. Depends on your synths midi mapping
|
||||
- `ccv` sets the cc value. normalized from 0 to 1.
|
||||
|
||||
```javascript
|
||||
$: note("c a f e").control([74, sine.slow(4)]).midi()
|
||||
$: note("c a f e").ccn(74).ccv(sine.slow(4)).midi()
|
||||
```
|
||||
|
||||
In the above snippet, `ccn` is set to 74, which is the filter cutoff for many synths. `ccv` is controlled by a saw pattern.
|
||||
Having everything in one pattern, the `ccv` pattern will be aligned to the note pattern, because the structure comes from the left by default.
|
||||
But you can also control cc messages separately like this:
|
||||
|
||||
```javascript
|
||||
$: note("c a f e").midi()
|
||||
$: ccv(sine.segment(16).slow(4)).ccn(74).midi()
|
||||
```
|
||||
|
||||
### progNum (Program Change)
|
||||
|
||||
`progNum` control sends MIDI program change messages to switch between different presets/patches on your MIDI device.
|
||||
Program change values should be numbers between 0 and 127.
|
||||
|
||||
```javascript
|
||||
// Play notes while changing programs
|
||||
note("c3 e3 g3").progNum("<0 1 2>").midi()
|
||||
```
|
||||
|
||||
Program change messages are useful for switching between different instrument sounds or presets during a performance.
|
||||
The exact sound that each program number maps to depends on your MIDI device's configuration.
|
||||
|
||||
## sysex, sysexid && sysexdata (System Exclusive Message)
|
||||
|
||||
`sysex`, `sysexid` and `sysexdata` control sends MIDI System Exclusive (SysEx) messages to your MIDI device.
|
||||
sysEx messages are device-specific commands that allow deeper control over synthesizer parameters.
|
||||
The value should be an array of numbers between 0-255 representing the SysEx data bytes.
|
||||
|
||||
```javascript
|
||||
// Send a simple SysEx message
|
||||
let id = 0x43; //Yamaha
|
||||
//let id = "0x00:0x20:0x32"; //Behringer ID can be an array of numbers
|
||||
let data = "0x79:0x09:0x11:0x0A:0x00:0x00"; // Set NSX-39 voice to say "Aa"
|
||||
$: note("c d e f e d c").sysex(id, data).midi();
|
||||
$: note("c d e f e d c").sysexid(id).sysexdata(data).midi();
|
||||
```
|
||||
|
||||
The exact format of SysEx messages depends on your MIDI device's specification.
|
||||
Consult your device's MIDI implementation guide for details on supported SysEx messages.
|
||||
|
||||
### midibend && miditouch
|
||||
|
||||
`midibend` sets MIDI pitch bend (-1 - 1)
|
||||
`miditouch` sets MIDI key after touch (0-1)
|
||||
|
||||
```javascript
|
||||
|
||||
$: note("c d e f e d c").midibend(sine.slow(4).range(-0.4,0.4)).midi();
|
||||
$: note("c d e f e d c").miditouch(sine.slow(4).range(0,1)).midi();
|
||||
|
||||
```
|
||||
|
||||
### midicmd
|
||||
|
||||
`midicmd` sends MIDI system real-time messages to control timing and transport on MIDI devices.
|
||||
|
||||
It supports the following commands:
|
||||
|
||||
- `clock`/`midiClock` - Sends MIDI timing clock messages
|
||||
- `start` - Sends MIDI start message
|
||||
- `stop` - Sends MIDI stop message
|
||||
- `continue` - Sends MIDI continue message
|
||||
|
||||
```javascript
|
||||
// You can control the clock with a pattern and ensure it starts in sync when the repl begins.
|
||||
// Note: It might act unexpectedly if MIDI isn't set up initially.
|
||||
stack(
|
||||
midicmd("clock*48,<start stop>/2").midi('IAC Driver')
|
||||
)
|
||||
```
|
||||
|
||||
`midicmd` also supports sending control change, program change and sysex messages.
|
||||
|
||||
- `cc` - sends MIDI control change messages.
|
||||
- `progNum` - sends MIDI program change messages.
|
||||
- `sysex` - sends MIDI system exclusive messages.
|
||||
|
||||
```javascript
|
||||
stack(
|
||||
// "cc:ccn:ccv"
|
||||
midicmd("cc:74:1").midi('IAC Driver'),
|
||||
// "progNum:progNum"
|
||||
midicmd("progNum:1").midi('IAC Driver'),
|
||||
// "sysex:[sysexid]:[sysexdata]"
|
||||
midicmd("sysex:[0x43]:[0x79:0x09:0x11:0x0A:0x00:0x00]").midi('IAC Driver')
|
||||
)
|
||||
```
|
||||
+344
-34
@@ -6,8 +6,9 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
import * as _WebMidi from 'webmidi';
|
||||
import { Pattern, getEventOffsetMs, isPattern, logger, ref } from '@strudel/core';
|
||||
import { noteToMidi } from '@strudel/core';
|
||||
import { noteToMidi, getControlName } from '@strudel/core';
|
||||
import { Note } from 'webmidi';
|
||||
|
||||
// if you use WebMidi from outside of this package, make sure to import that instance:
|
||||
export const { WebMidi } = _WebMidi;
|
||||
|
||||
@@ -43,13 +44,16 @@ export function enableWebMidi(options = {}) {
|
||||
resolve(WebMidi);
|
||||
return;
|
||||
}
|
||||
WebMidi.enable((err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
onReady?.(WebMidi);
|
||||
resolve(WebMidi);
|
||||
});
|
||||
WebMidi.enable(
|
||||
(err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
onReady?.(WebMidi);
|
||||
resolve(WebMidi);
|
||||
},
|
||||
{ sysex: true },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -89,18 +93,235 @@ if (typeof window !== 'undefined') {
|
||||
});
|
||||
}
|
||||
|
||||
Pattern.prototype.midi = function (output) {
|
||||
if (isPattern(output)) {
|
||||
// registry for midi mappings, converting control names to cc messages
|
||||
export const midicontrolMap = new Map();
|
||||
|
||||
// takes midimap and converts each control key to the main control name
|
||||
function unifyMapping(mapping) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(mapping).map(([key, mapping]) => {
|
||||
if (typeof mapping === 'number') {
|
||||
mapping = { ccn: mapping };
|
||||
}
|
||||
return [getControlName(key), mapping];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function githubPath(base, subpath = '') {
|
||||
if (!base.startsWith('github:')) {
|
||||
throw new Error('expected "github:" at the start of pseudoUrl');
|
||||
}
|
||||
let [_, path] = base.split('github:');
|
||||
path = path.endsWith('/') ? path.slice(0, -1) : path;
|
||||
if (path.split('/').length === 2) {
|
||||
// assume main as default branch if none set
|
||||
path += '/main';
|
||||
}
|
||||
return `https://raw.githubusercontent.com/${path}/${subpath}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* configures the default midimap, which is used when no "midimap" port is set
|
||||
* @example
|
||||
* defaultmidimap({ lpf: 74 })
|
||||
* $: note("c a f e").midi();
|
||||
* $: lpf(sine.slow(4).segment(16)).midi();
|
||||
*/
|
||||
export function defaultmidimap(mapping) {
|
||||
midicontrolMap.set('default', unifyMapping(mapping));
|
||||
}
|
||||
|
||||
let loadCache = {};
|
||||
|
||||
/**
|
||||
* Adds midimaps to the registry. Inside each midimap, control names (e.g. lpf) are mapped to cc numbers.
|
||||
* @example
|
||||
* midimaps({ mymap: { lpf: 74 } })
|
||||
* $: note("c a f e")
|
||||
* .lpf(sine.slow(4))
|
||||
* .midimap('mymap')
|
||||
* .midi()
|
||||
* @example
|
||||
* midimaps({ mymap: {
|
||||
* lpf: { ccn: 74, min: 0, max: 20000, exp: 0.5 }
|
||||
* }})
|
||||
* $: note("c a f e")
|
||||
* .lpf(sine.slow(2).range(400,2000))
|
||||
* .midimap('mymap')
|
||||
* .midi()
|
||||
*/
|
||||
export async function midimaps(map) {
|
||||
if (typeof map === 'string') {
|
||||
if (map.startsWith('github:')) {
|
||||
map = githubPath(map, 'midimap.json');
|
||||
}
|
||||
if (!loadCache[map]) {
|
||||
loadCache[map] = fetch(map).then((res) => res.json());
|
||||
}
|
||||
map = await loadCache[map];
|
||||
}
|
||||
if (typeof map === 'object') {
|
||||
Object.entries(map).forEach(([name, mapping]) => midicontrolMap.set(name, unifyMapping(mapping)));
|
||||
}
|
||||
}
|
||||
|
||||
// registry for midi sounds, converting sound names to controls
|
||||
export const midisoundMap = new Map();
|
||||
|
||||
// normalizes the given value from the given range and exponent
|
||||
function normalize(value = 0, min = 0, max = 1, exp = 1) {
|
||||
if (min === max) {
|
||||
throw new Error('min and max cannot be the same value');
|
||||
}
|
||||
let normalized = (value - min) / (max - min);
|
||||
normalized = Math.min(1, Math.max(0, normalized));
|
||||
return Math.pow(normalized, exp);
|
||||
}
|
||||
|
||||
function mapCC(mapping, value) {
|
||||
return Object.keys(value)
|
||||
.filter((key) => !!mapping[getControlName(key)])
|
||||
.map((key) => {
|
||||
const { ccn, min = 0, max = 1, exp = 1 } = mapping[key];
|
||||
const ccv = normalize(value[key], min, max, exp);
|
||||
return { ccn, ccv };
|
||||
});
|
||||
}
|
||||
|
||||
// sends a cc message to the given device on the given channel
|
||||
function sendCC(ccn, ccv, device, midichan, timeOffsetString) {
|
||||
if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) {
|
||||
throw new Error('expected ccv to be a number between 0 and 1');
|
||||
}
|
||||
if (!['string', 'number'].includes(typeof ccn)) {
|
||||
throw new Error('expected ccn to be a number or a string');
|
||||
}
|
||||
const scaled = Math.round(ccv * 127);
|
||||
device.sendControlChange(ccn, scaled, midichan, { time: timeOffsetString });
|
||||
}
|
||||
|
||||
// sends a program change message to the given device on the given channel
|
||||
function sendProgramChange(progNum, device, midichan, timeOffsetString) {
|
||||
if (typeof progNum !== 'number' || progNum < 0 || progNum > 127) {
|
||||
throw new Error('expected progNum (program change) to be a number between 0 and 127');
|
||||
}
|
||||
device.sendProgramChange(progNum, midichan, { time: timeOffsetString });
|
||||
}
|
||||
|
||||
// sends a sysex message to the given device on the given channel
|
||||
function sendSysex(sysexid, sysexdata, device, timeOffsetString) {
|
||||
if (Array.isArray(sysexid)) {
|
||||
if (!sysexid.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
|
||||
throw new Error('all sysexid bytes must be integers between 0 and 255');
|
||||
}
|
||||
} else if (!Number.isInteger(sysexid) || sysexid < 0 || sysexid > 255) {
|
||||
throw new Error('A:sysexid must be an number between 0 and 255 or an array of such integers');
|
||||
}
|
||||
|
||||
if (!Array.isArray(sysexdata)) {
|
||||
throw new Error('expected sysex to be an array of numbers (0-255)');
|
||||
}
|
||||
if (!sysexdata.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
|
||||
throw new Error('all sysex bytes must be integers between 0 and 255');
|
||||
}
|
||||
device.sendSysex(sysexid, sysexdata, { time: timeOffsetString });
|
||||
}
|
||||
|
||||
// sends a NRPN message to the given device on the given channel
|
||||
function sendNRPN(nrpnn, nrpv, device, midichan, timeOffsetString) {
|
||||
if (Array.isArray(nrpnn)) {
|
||||
if (!nrpnn.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
|
||||
throw new Error('all nrpnn bytes must be integers between 0 and 255');
|
||||
}
|
||||
} else if (!Number.isInteger(nrpv) || nrpv < 0 || nrpv > 255) {
|
||||
throw new Error('A:sysexid must be an number between 0 and 255 or an array of such integers');
|
||||
}
|
||||
|
||||
device.sendNRPN(nrpnn, nrpv, midichan, { time: timeOffsetString });
|
||||
}
|
||||
|
||||
// sends a pitch bend message to the given device on the given channel
|
||||
function sendPitchBend(midibend, device, midichan, timeOffsetString) {
|
||||
if (typeof midibend !== 'number' || midibend < -1 || midibend > 1) {
|
||||
throw new Error('expected midibend to be a number between -1 and 1');
|
||||
}
|
||||
device.sendPitchBend(midibend, midichan, { time: timeOffsetString });
|
||||
}
|
||||
|
||||
// sends a channel aftertouch message to the given device on the given channel
|
||||
function sendAftertouch(miditouch, device, midichan, timeOffsetString) {
|
||||
if (typeof miditouch !== 'number' || miditouch < 0 || miditouch > 1) {
|
||||
throw new Error('expected miditouch to be a number between 0 and 1');
|
||||
}
|
||||
device.sendChannelAftertouch(miditouch, midichan, { time: timeOffsetString });
|
||||
}
|
||||
|
||||
// sends a note message to the given device on the given channel
|
||||
function sendNote(note, velocity, duration, device, midichan, timeOffsetString) {
|
||||
if (note == null || note === '') {
|
||||
throw new Error('note cannot be null or empty');
|
||||
}
|
||||
if (velocity != null && (typeof velocity !== 'number' || velocity < 0 || velocity > 1)) {
|
||||
throw new Error('velocity must be a number between 0 and 1');
|
||||
}
|
||||
if (duration != null && (typeof duration !== 'number' || duration < 0)) {
|
||||
throw new Error('duration must be a positive number');
|
||||
}
|
||||
|
||||
const midiNumber = typeof note === 'number' ? note : noteToMidi(note);
|
||||
const midiNote = new Note(midiNumber, { attack: velocity, duration });
|
||||
device.playNote(midiNote, midichan, {
|
||||
time: timeOffsetString,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* MIDI output: Opens a MIDI output port.
|
||||
* @param {string | number} midiport MIDI device name or index defaulting to 0
|
||||
* @param {object} options Additional MIDI configuration options
|
||||
* @example
|
||||
* note("c4").midichan(1).midi('IAC Driver Bus 1')
|
||||
* @example
|
||||
* note("c4").midichan(1).midi('IAC Driver Bus 1', { controller: true, latency: 50 })
|
||||
*/
|
||||
|
||||
Pattern.prototype.midi = function (midiport, options = {}) {
|
||||
if (isPattern(midiport)) {
|
||||
throw new Error(
|
||||
`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${
|
||||
`.midi does not accept Pattern input for midiport. Make sure to pass device name with single quotes. Example: .midi('${
|
||||
WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1'
|
||||
}')`,
|
||||
);
|
||||
}
|
||||
|
||||
// For backward compatibility
|
||||
if (typeof midiport === 'object') {
|
||||
const { port, isController = false, ...configOptions } = midiport;
|
||||
options = {
|
||||
isController,
|
||||
...configOptions,
|
||||
...options, // Keep any options passed separately
|
||||
};
|
||||
midiport = port;
|
||||
}
|
||||
|
||||
let midiConfig = {
|
||||
// Default configuration values
|
||||
isController: false, // Disable sending notes for midi controllers
|
||||
latencyMs: 34, // Default latency to get audio engine to line up in ms
|
||||
noteOffsetMs: 10, // Default note-off offset to prevent glitching in ms
|
||||
midichannel: 1, // Default MIDI channel
|
||||
velocity: 0.9, // Default velocity
|
||||
gain: 1, // Default gain
|
||||
midimap: 'default', // Default MIDI map
|
||||
midiport: midiport, // Store the port in the config
|
||||
...options, // Override defaults with provided options
|
||||
};
|
||||
|
||||
enableWebMidi({
|
||||
onEnabled: ({ outputs }) => {
|
||||
const device = getDevice(output, outputs);
|
||||
const device = getDevice(midiConfig.midiport, outputs);
|
||||
const otherOutputs = outputs.filter((o) => o.name !== device.name);
|
||||
logger(
|
||||
`Midi enabled! Using "${device.name}". ${
|
||||
@@ -114,39 +335,102 @@ Pattern.prototype.midi = function (output) {
|
||||
|
||||
return this.onTrigger((time_deprecate, hap, currentTime, cps, targetTime) => {
|
||||
if (!WebMidi.enabled) {
|
||||
console.log('not enabled');
|
||||
logger('Midi not enabled');
|
||||
return;
|
||||
}
|
||||
const device = getDevice(output, WebMidi.outputs);
|
||||
hap.ensureObjectValue();
|
||||
|
||||
//magic number to get audio engine to line up, can probably be calculated somehow
|
||||
const latencyMs = 34;
|
||||
const latencyMs = midiConfig.latencyMs;
|
||||
// passing a string with a +num into the webmidi api adds an offset to the current time https://webmidijs.org/api/classes/Output
|
||||
const timeOffsetString = `+${getEventOffsetMs(targetTime, currentTime) + latencyMs}`;
|
||||
// destructure value
|
||||
let { note, nrpnn, nrpv, ccn, ccv, midichan = 1, midicmd, gain = 1, velocity = 0.9 } = hap.value;
|
||||
|
||||
// midi event values from hap with configurable defaults
|
||||
let {
|
||||
note,
|
||||
nrpnn,
|
||||
nrpv,
|
||||
ccn,
|
||||
ccv,
|
||||
midichan = midiConfig.midichannel,
|
||||
midicmd,
|
||||
midibend,
|
||||
miditouch,
|
||||
polyTouch,
|
||||
gain = midiConfig.gain,
|
||||
velocity = midiConfig.velocity,
|
||||
progNum,
|
||||
sysexid,
|
||||
sysexdata,
|
||||
midimap = midiConfig.midimap,
|
||||
midiport = midiConfig.midiport,
|
||||
} = hap.value;
|
||||
|
||||
const device = getDevice(midiport, WebMidi.outputs);
|
||||
if (!device) {
|
||||
logger(
|
||||
`[midi] midiport "${midiport}" not found! available: ${WebMidi.outputs.map((output) => `'${output.name}'`).join(', ')}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
velocity = gain * velocity;
|
||||
|
||||
// note off messages will often a few ms arrive late, try to prevent glitching by subtracting from the duration length
|
||||
const duration = (hap.duration.valueOf() / cps) * 1000 - 10;
|
||||
if (note != null) {
|
||||
const midiNumber = typeof note === 'number' ? note : noteToMidi(note);
|
||||
const midiNote = new Note(midiNumber, { attack: velocity, duration });
|
||||
device.playNote(midiNote, midichan, {
|
||||
time: timeOffsetString,
|
||||
});
|
||||
// Handle midimap
|
||||
// if midimap is set, send a cc messages from defined controls
|
||||
if (midicontrolMap.has(midimap)) {
|
||||
const ccs = mapCC(midicontrolMap.get(midimap), hap.value);
|
||||
ccs.forEach(({ ccn, ccv }) => sendCC(ccn, ccv, device, midichan, timeOffsetString));
|
||||
} else if (midimap !== 'default') {
|
||||
// Add warning when a non-existent midimap is specified
|
||||
logger(`[midi] midimap "${midimap}" not found! Available maps: ${[...midicontrolMap.keys()].join(', ')}`);
|
||||
}
|
||||
|
||||
// Handle note
|
||||
if (note !== undefined && !midiConfig.isController) {
|
||||
// note off messages will often a few ms arrive late,
|
||||
// try to prevent glitching by subtracting noteOffsetMs from the duration length
|
||||
const duration = (hap.duration.valueOf() / cps) * 1000 - midiConfig.noteOffsetMs;
|
||||
|
||||
sendNote(note, velocity, duration, device, midichan, timeOffsetString);
|
||||
}
|
||||
|
||||
// Handle program change
|
||||
if (progNum !== undefined) {
|
||||
sendProgramChange(progNum, device, midichan, timeOffsetString);
|
||||
}
|
||||
|
||||
// Handle sysex
|
||||
// sysex data is consist of 2 arrays, first is sysexid, second is sysexdata
|
||||
// sysexid is a manufacturer id it is either a number or an array of 3 numbers.
|
||||
// list of manufacturer ids can be found here : https://midi.org/sysexidtable
|
||||
// if sysexid is an array the first byte is 0x00
|
||||
|
||||
if (sysexid !== undefined && sysexdata !== undefined) {
|
||||
sendSysex(sysexid, sysexdata, device, timeOffsetString);
|
||||
}
|
||||
|
||||
// Handle control change
|
||||
if (ccv !== undefined && ccn !== undefined) {
|
||||
if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) {
|
||||
throw new Error('expected ccv to be a number between 0 and 1');
|
||||
}
|
||||
if (!['string', 'number'].includes(typeof ccn)) {
|
||||
throw new Error('expected ccn to be a number or a string');
|
||||
}
|
||||
const scaled = Math.round(ccv * 127);
|
||||
device.sendControlChange(ccn, scaled, midichan, { time: timeOffsetString });
|
||||
sendCC(ccn, ccv, device, midichan, timeOffsetString);
|
||||
}
|
||||
|
||||
// Handle NRPN non-registered parameter number
|
||||
if (nrpnn !== undefined && nrpv !== undefined) {
|
||||
sendNRPN(nrpnn, nrpv, device, midichan, timeOffsetString);
|
||||
}
|
||||
|
||||
// Handle midibend
|
||||
if (midibend !== undefined) {
|
||||
sendPitchBend(midibend, device, midichan, timeOffsetString);
|
||||
}
|
||||
|
||||
// Handle miditouch
|
||||
if (miditouch !== undefined) {
|
||||
sendAftertouch(miditouch, device, midichan, timeOffsetString);
|
||||
}
|
||||
|
||||
// Handle midicmd
|
||||
if (hap.whole.begin + 0 === 0) {
|
||||
// we need to start here because we have the timing info
|
||||
device.sendStart({ time: timeOffsetString });
|
||||
@@ -159,6 +443,19 @@ Pattern.prototype.midi = function (output) {
|
||||
device.sendStop({ time: timeOffsetString });
|
||||
} else if (['continue'].includes(midicmd)) {
|
||||
device.sendContinue({ time: timeOffsetString });
|
||||
} else if (Array.isArray(midicmd)) {
|
||||
if (midicmd[0] === 'progNum') {
|
||||
sendProgramChange(midicmd[1], device, midichan, timeOffsetString);
|
||||
} else if (midicmd[0] === 'cc') {
|
||||
if (midicmd.length === 2) {
|
||||
sendCC(midicmd[0], midicmd[1] / 127, device, midichan, timeOffsetString);
|
||||
}
|
||||
} else if (midicmd[0] === 'sysex') {
|
||||
if (midicmd.length === 3) {
|
||||
const [_, id, data] = midicmd;
|
||||
sendSysex(id, data, device, timeOffsetString);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -166,16 +463,29 @@ Pattern.prototype.midi = function (output) {
|
||||
let listeners = {};
|
||||
const refs = {};
|
||||
|
||||
/**
|
||||
* MIDI input: Opens a MIDI input port to receive MIDI control change messages.
|
||||
* @param {string | number} input MIDI device name or index defaulting to 0
|
||||
* @returns {Function}
|
||||
* @example
|
||||
* let cc = await midin('IAC Driver Bus 1')
|
||||
* note("c a f e").lpf(cc(0).range(0, 1000)).lpq(cc(1).range(0, 10)).sound("sawtooth")
|
||||
*/
|
||||
export async function midin(input) {
|
||||
if (isPattern(input)) {
|
||||
throw new Error(
|
||||
`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${
|
||||
`midin: does not accept Pattern as input. Make sure to pass device name with single quotes. Example: midin('${
|
||||
WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1'
|
||||
}')`,
|
||||
);
|
||||
}
|
||||
const initial = await enableWebMidi(); // only returns on first init
|
||||
const device = getDevice(input, WebMidi.inputs);
|
||||
if (!device) {
|
||||
throw new Error(
|
||||
`midiin: device "${input}" not found.. connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`,
|
||||
);
|
||||
}
|
||||
if (initial) {
|
||||
const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name);
|
||||
logger(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/midi",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.3",
|
||||
"description": "Midi API for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
@@ -31,9 +31,9 @@
|
||||
"dependencies": {
|
||||
"@strudel/core": "workspace:*",
|
||||
"@strudel/webaudio": "workspace:*",
|
||||
"webmidi": "^3.1.8"
|
||||
"webmidi": "^3.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
+280
-338
File diff suppressed because one or more lines are too long
@@ -19,10 +19,10 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
this.location_ = location();
|
||||
}
|
||||
|
||||
var PatternStub = function(source, alignment, seed, tactus)
|
||||
var PatternStub = function(source, alignment, seed, _steps)
|
||||
{
|
||||
this.type_ = "pattern";
|
||||
this.arguments_ = { alignment: alignment, tactus: tactus };
|
||||
this.arguments_ = { alignment: alignment, _steps: _steps };
|
||||
if (seed !== undefined) {
|
||||
this.arguments_.seed = seed;
|
||||
}
|
||||
@@ -172,8 +172,8 @@ slice_with_ops = s:slice ops:slice_op*
|
||||
}
|
||||
|
||||
// a sequence is a combination of one or more successive slices (as an array)
|
||||
sequence = tactus:'^'? s:(slice_with_ops)+
|
||||
{ return new PatternStub(s, 'fastcat', undefined, !!tactus); }
|
||||
sequence = _steps:'^'? s:(slice_with_ops)+
|
||||
{ return new PatternStub(s, 'fastcat', undefined, !!_steps); }
|
||||
|
||||
// a stack is a series of vertically aligned sequence, separated by a comma
|
||||
stack_tail = tail:(comma @sequence)+
|
||||
|
||||
+17
-17
@@ -14,7 +14,7 @@ const applyOptions = (parent, enter) => (pat, i) => {
|
||||
const ast = parent.source_[i];
|
||||
const options = ast.options_;
|
||||
const ops = options?.ops;
|
||||
|
||||
const steps_source = pat.__steps_source;
|
||||
if (ops) {
|
||||
for (const op of ops) {
|
||||
switch (op.type_) {
|
||||
@@ -69,7 +69,7 @@ const applyOptions = (parent, enter) => (pat, i) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pat.__steps_source = pat.__steps_source || steps_source;
|
||||
return pat;
|
||||
};
|
||||
|
||||
@@ -82,20 +82,20 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
|
||||
// resolveReplications(ast);
|
||||
const children = ast.source_.map((child) => enter(child)).map(applyOptions(ast, enter));
|
||||
const alignment = ast.arguments_.alignment;
|
||||
const with_tactus = children.filter((child) => child.__tactus_source);
|
||||
const with_steps = children.filter((child) => child.__steps_source);
|
||||
let pat;
|
||||
switch (alignment) {
|
||||
case 'stack': {
|
||||
pat = strudel.stack(...children);
|
||||
if (with_tactus.length) {
|
||||
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus)));
|
||||
if (with_steps.length) {
|
||||
pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'polymeter_slowcat': {
|
||||
pat = strudel.stack(...children.map((child) => child._slow(child.__weight)));
|
||||
if (with_tactus.length) {
|
||||
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus)));
|
||||
if (with_steps.length) {
|
||||
pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -111,8 +111,8 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
|
||||
}
|
||||
case 'rand': {
|
||||
pat = strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
|
||||
if (with_tactus.length) {
|
||||
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus)));
|
||||
if (with_steps.length) {
|
||||
pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -131,21 +131,21 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
|
||||
...ast.source_.map((child, i) => [child.options_?.weight || strudel.Fraction(1), children[i]]),
|
||||
);
|
||||
pat.__weight = weightSum; // for polymeter
|
||||
pat.tactus = weightSum;
|
||||
if (with_tactus.length) {
|
||||
pat.tactus = pat.tactus.mul(lcm(...with_tactus.map((x) => Fraction(x.tactus))));
|
||||
pat._steps = weightSum;
|
||||
if (with_steps.length) {
|
||||
pat._steps = pat._steps.mul(lcm(...with_steps.map((x) => Fraction(x._steps))));
|
||||
}
|
||||
} else {
|
||||
pat = strudel.sequence(...children);
|
||||
pat.tactus = children.length;
|
||||
pat._steps = children.length;
|
||||
}
|
||||
if (ast.arguments_.tactus) {
|
||||
pat.__tactus_source = true;
|
||||
if (ast.arguments_._steps) {
|
||||
pat.__steps_source = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (with_tactus.length) {
|
||||
pat.__tactus_source = true;
|
||||
if (with_steps.length) {
|
||||
pat.__steps_source = true;
|
||||
}
|
||||
return pat;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel/mini",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.2",
|
||||
"description": "Mini notation for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
@@ -35,8 +35,8 @@
|
||||
"@strudel/core": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"peggy": "^3.0.2",
|
||||
"vite": "^5.0.10",
|
||||
"vitest": "^1.1.0"
|
||||
"peggy": "^4.2.0",
|
||||
"vite": "^6.0.11",
|
||||
"vitest": "^3.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user