From 104bc3553d401e99ad464c45236be171016e81ef Mon Sep 17 00:00:00 2001 From: Anton Duda Date: Thu, 23 Oct 2025 20:54:43 +0300 Subject: [PATCH] Add base plugin logic --- .env.example | 2 + .eslintignore | 3 + .eslintrc | 84 + .gitignore | 29 + .npmrc | 1 + .prettierrc.mjs | 15 + LICENSE | 622 ++ README.md | 3 + build_and_copy_plugin.sh | 41 + build_and_copy_plugin_prod.sh | 35 + manifest.json | 9 + package-lock.json | 6714 +++++++++++++++++ package.json | 62 + src/App.vue | 9 + src/commands/createStrudelBlock.ts | 28 + src/components/StrudelBlockView.vue | 120 + src/components/Views.vue | 13 + src/components/obsidian/Icon.vue | 92 + src/constants/keywords.ts | 1 + src/editor/StrudelHeaderWidget.ts | 58 + src/editor/StrudelHighlight.ts | 164 + src/editor/StrudelPlugin.ts | 97 + src/editor/SyntaxHighlighting.js | 1149 +++ src/entities/Strudel.ts | 35 + src/global.d.ts | 3 + src/helpers/vueUtils.ts | 116 + src/main.ts | 119 + src/services/StrudelConfig.ts | 13 + src/stores/GlobalStore.ts | 189 + src/strudel/codemirror/README.md | 3 - src/strudel/codemirror/autocomplete.mjs | 460 -- src/strudel/codemirror/basicSetup.mjs | 63 - src/strudel/codemirror/codemirror.mjs | 386 - src/strudel/codemirror/flash.mjs | 39 - src/strudel/codemirror/highlight.mjs | 138 - src/strudel/codemirror/html.mjs | 18 - src/strudel/codemirror/index.mjs | 6 - src/strudel/codemirror/keybindings.mjs | 32 - src/strudel/codemirror/package.json | 58 - src/strudel/codemirror/slider.mjs | 146 - src/strudel/codemirror/themes.mjs | 218 - src/strudel/codemirror/themes/CutiePi.mjs | 45 - src/strudel/codemirror/themes/algoboy.mjs | 61 - .../codemirror/themes/androidstudio.mjs | 43 - src/strudel/codemirror/themes/archBtw.mjs | 38 - src/strudel/codemirror/themes/atomone.mjs | 51 - src/strudel/codemirror/themes/aura.mjs | 51 - src/strudel/codemirror/themes/bbedit.mjs | 46 - src/strudel/codemirror/themes/blackscreen.mjs | 39 - src/strudel/codemirror/themes/bluescreen.mjs | 42 - .../codemirror/themes/bluescreenlight.mjs | 37 - src/strudel/codemirror/themes/darcula.mjs | 47 - src/strudel/codemirror/themes/dracula.mjs | 50 - src/strudel/codemirror/themes/duotoneDark.mjs | 42 - .../codemirror/themes/duotoneLight.mjs | 45 - src/strudel/codemirror/themes/eclipse.mjs | 46 - src/strudel/codemirror/themes/fruitDaw.mjs | 50 - src/strudel/codemirror/themes/githubDark.mjs | 42 - src/strudel/codemirror/themes/githubLight.mjs | 42 - src/strudel/codemirror/themes/green-text.mjs | 39 - src/strudel/codemirror/themes/gruvboxDark.mjs | 82 - .../codemirror/themes/gruvboxLight.mjs | 130 - .../codemirror/themes/materialDark.mjs | 77 - .../codemirror/themes/materialLight.mjs | 52 - src/strudel/codemirror/themes/monokai.mjs | 45 - src/strudel/codemirror/themes/noctisLilac.mjs | 50 - src/strudel/codemirror/themes/nord.mjs | 78 - src/strudel/codemirror/themes/red-text.mjs | 39 - .../codemirror/themes/solarizedDark.mjs | 79 - .../codemirror/themes/solarizedLight.mjs | 82 - src/strudel/codemirror/themes/sonic-pink.mjs | 39 - .../codemirror/themes/strudel-theme.mjs | 49 - src/strudel/codemirror/themes/sublime.mjs | 43 - src/strudel/codemirror/themes/teletext.mjs | 51 - src/strudel/codemirror/themes/terminal.mjs | 37 - .../codemirror/themes/theme-helper.mjs | 42 - .../codemirror/themes/tokioNightStorm.mjs | 52 - src/strudel/codemirror/themes/tokyoNight.mjs | 52 - .../codemirror/themes/tokyoNightDay.mjs | 53 - src/strudel/codemirror/themes/vscodeDark.mjs | 80 - src/strudel/codemirror/themes/vscodeLight.mjs | 81 - src/strudel/codemirror/themes/whitescreen.mjs | 39 - src/strudel/codemirror/themes/xcodeLight.mjs | 38 - src/strudel/codemirror/tooltip.mjs | 79 - src/strudel/codemirror/vite.config.js | 19 - src/strudel/codemirror/widget.mjs | 142 - src/strudel/core/clockworker.js | 174 +- src/strudel/core/controls.mjs | 665 +- src/strudel/core/cyclist.mjs | 132 +- src/strudel/core/drawLine.mjs | 52 +- src/strudel/core/euclid.mjs | 115 +- src/strudel/core/evaluate.mjs | 59 +- src/strudel/core/fraction.mjs | 117 +- src/strudel/core/hap.mjs | 107 +- src/strudel/core/index.mjs | 54 +- src/strudel/core/logger.mjs | 24 +- src/strudel/core/neocyclist.mjs | 102 +- src/strudel/core/pattern.mjs | 1980 ++--- src/strudel/core/pick.mjs | 98 +- src/strudel/core/repl.mjs | 210 +- src/strudel/core/signal.mjs | 340 +- src/strudel/core/speak.mjs | 32 +- src/strudel/core/state.mjs | 12 +- src/strudel/core/time.mjs | 8 +- src/strudel/core/timespan.mjs | 74 +- src/strudel/core/ui.mjs | 30 +- src/strudel/core/util.mjs | 360 +- src/strudel/core/value.mjs | 46 +- src/strudel/core/zyklus.mjs | 62 +- src/strudel/draw/animate.mjs | 101 +- src/strudel/draw/color.mjs | 14 +- src/strudel/draw/draw.mjs | 220 +- src/strudel/draw/index.mjs | 12 +- src/strudel/draw/pianoroll.mjs | 244 +- src/strudel/draw/pitchwheel.mjs | 146 +- src/strudel/draw/spiral.mjs | 89 +- src/strudel/init.js | 45 + src/strudel/midi/index.mjs | 4 +- src/strudel/midi/midi.mjs | 302 +- src/strudel/mini/index.mjs | 4 +- src/strudel/mini/krill-parser.js | 5255 +++++++++---- src/strudel/mini/krill.pegjs | 303 - src/strudel/mini/mini.mjs | 252 +- src/strudel/soundfonts/convert.js | 13 - src/strudel/soundfonts/fontloader.mjs | 186 +- src/strudel/soundfonts/index.mjs | 17 +- src/strudel/soundfonts/list.mjs | 262 +- src/strudel/soundfonts/sfumato.mjs | 34 +- src/strudel/superdough/README.md | 171 - src/strudel/superdough/audioContext.mjs | 16 +- src/strudel/superdough/dspworklet.mjs | 50 +- src/strudel/superdough/feedbackdelay.mjs | 34 +- src/strudel/superdough/fft.js | 588 +- src/strudel/superdough/helpers.mjs | 539 +- src/strudel/superdough/index.mjs | 18 +- src/strudel/superdough/logger.mjs | 12 +- src/strudel/superdough/noise.mjs | 78 +- src/strudel/superdough/ola-processor.js | 121 +- src/strudel/superdough/package.json | 41 - src/strudel/superdough/reverb.mjs | 56 +- src/strudel/superdough/reverbGen.mjs | 120 +- src/strudel/superdough/sampler.mjs | 337 +- src/strudel/superdough/superdough.mjs | 551 +- src/strudel/superdough/superdoughoutput.mjs | 224 +- src/strudel/superdough/synth.mjs | 468 +- src/strudel/superdough/util.mjs | 103 +- src/strudel/superdough/vite.config.js | 20 - src/strudel/superdough/vowel.mjs | 142 +- src/strudel/superdough/wavetable.mjs | 266 +- src/strudel/superdough/worklets.mjs | 1152 +-- src/strudel/superdough/zzfx.mjs | 84 +- src/strudel/superdough/zzfx_fork.mjs | 53 +- src/strudel/supradough/.gitignore | 1 - src/strudel/supradough/README.md | 3 - src/strudel/supradough/dough-export.mjs | 123 - src/strudel/supradough/dough-worklet.mjs | 32 +- src/strudel/supradough/dough.mjs | 1095 +-- src/strudel/supradough/index.mjs | 6 +- src/strudel/supradough/package.json | 37 - src/strudel/tonal/index.mjs | 14 +- src/strudel/tonal/ireal.mjs | 148 +- src/strudel/tonal/tonal.mjs | 233 +- src/strudel/tonal/tonleiter.mjs | 248 +- src/strudel/tonal/voicings.mjs | 113 +- src/strudel/transpiler/index.mjs | 8 +- src/strudel/transpiler/transpiler.mjs | 203 +- src/strudel/webaudio/index.mjs | 10 +- src/strudel/webaudio/scope.mjs | 137 +- src/strudel/webaudio/spectrum.mjs | 66 +- src/strudel/webaudio/supradough.mjs | 116 +- src/strudel/webaudio/webaudio.mjs | 38 +- src/strudel/xen/index.mjs | 6 +- src/strudel/xen/tune.mjs | 22 +- src/strudel/xen/xen.mjs | 69 +- src/styles.css | 5 + src/vue-shims.d.ts | 7 + tsconfig.json | 19 + version-bump.mjs | 14 + versions.json | 3 + vite.config.mts | 113 + watch.sh | 3 + 181 files changed, 21479 insertions(+), 12872 deletions(-) create mode 100644 .env.example create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .prettierrc.mjs create mode 100644 LICENSE create mode 100644 README.md create mode 100755 build_and_copy_plugin.sh create mode 100755 build_and_copy_plugin_prod.sh create mode 100644 manifest.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/App.vue create mode 100644 src/commands/createStrudelBlock.ts create mode 100644 src/components/StrudelBlockView.vue create mode 100644 src/components/Views.vue create mode 100644 src/components/obsidian/Icon.vue create mode 100644 src/constants/keywords.ts create mode 100644 src/editor/StrudelHeaderWidget.ts create mode 100644 src/editor/StrudelHighlight.ts create mode 100644 src/editor/StrudelPlugin.ts create mode 100644 src/editor/SyntaxHighlighting.js create mode 100644 src/entities/Strudel.ts create mode 100644 src/global.d.ts create mode 100644 src/helpers/vueUtils.ts create mode 100644 src/main.ts create mode 100644 src/services/StrudelConfig.ts create mode 100644 src/stores/GlobalStore.ts delete mode 100644 src/strudel/codemirror/README.md delete mode 100644 src/strudel/codemirror/autocomplete.mjs delete mode 100644 src/strudel/codemirror/basicSetup.mjs delete mode 100644 src/strudel/codemirror/codemirror.mjs delete mode 100644 src/strudel/codemirror/flash.mjs delete mode 100644 src/strudel/codemirror/highlight.mjs delete mode 100644 src/strudel/codemirror/html.mjs delete mode 100644 src/strudel/codemirror/index.mjs delete mode 100644 src/strudel/codemirror/keybindings.mjs delete mode 100644 src/strudel/codemirror/package.json delete mode 100644 src/strudel/codemirror/slider.mjs delete mode 100644 src/strudel/codemirror/themes.mjs delete mode 100644 src/strudel/codemirror/themes/CutiePi.mjs delete mode 100644 src/strudel/codemirror/themes/algoboy.mjs delete mode 100644 src/strudel/codemirror/themes/androidstudio.mjs delete mode 100644 src/strudel/codemirror/themes/archBtw.mjs delete mode 100644 src/strudel/codemirror/themes/atomone.mjs delete mode 100644 src/strudel/codemirror/themes/aura.mjs delete mode 100644 src/strudel/codemirror/themes/bbedit.mjs delete mode 100644 src/strudel/codemirror/themes/blackscreen.mjs delete mode 100644 src/strudel/codemirror/themes/bluescreen.mjs delete mode 100644 src/strudel/codemirror/themes/bluescreenlight.mjs delete mode 100644 src/strudel/codemirror/themes/darcula.mjs delete mode 100644 src/strudel/codemirror/themes/dracula.mjs delete mode 100644 src/strudel/codemirror/themes/duotoneDark.mjs delete mode 100644 src/strudel/codemirror/themes/duotoneLight.mjs delete mode 100644 src/strudel/codemirror/themes/eclipse.mjs delete mode 100644 src/strudel/codemirror/themes/fruitDaw.mjs delete mode 100644 src/strudel/codemirror/themes/githubDark.mjs delete mode 100644 src/strudel/codemirror/themes/githubLight.mjs delete mode 100644 src/strudel/codemirror/themes/green-text.mjs delete mode 100644 src/strudel/codemirror/themes/gruvboxDark.mjs delete mode 100644 src/strudel/codemirror/themes/gruvboxLight.mjs delete mode 100644 src/strudel/codemirror/themes/materialDark.mjs delete mode 100644 src/strudel/codemirror/themes/materialLight.mjs delete mode 100644 src/strudel/codemirror/themes/monokai.mjs delete mode 100644 src/strudel/codemirror/themes/noctisLilac.mjs delete mode 100644 src/strudel/codemirror/themes/nord.mjs delete mode 100644 src/strudel/codemirror/themes/red-text.mjs delete mode 100644 src/strudel/codemirror/themes/solarizedDark.mjs delete mode 100644 src/strudel/codemirror/themes/solarizedLight.mjs delete mode 100644 src/strudel/codemirror/themes/sonic-pink.mjs delete mode 100644 src/strudel/codemirror/themes/strudel-theme.mjs delete mode 100644 src/strudel/codemirror/themes/sublime.mjs delete mode 100644 src/strudel/codemirror/themes/teletext.mjs delete mode 100644 src/strudel/codemirror/themes/terminal.mjs delete mode 100644 src/strudel/codemirror/themes/theme-helper.mjs delete mode 100644 src/strudel/codemirror/themes/tokioNightStorm.mjs delete mode 100644 src/strudel/codemirror/themes/tokyoNight.mjs delete mode 100644 src/strudel/codemirror/themes/tokyoNightDay.mjs delete mode 100644 src/strudel/codemirror/themes/vscodeDark.mjs delete mode 100644 src/strudel/codemirror/themes/vscodeLight.mjs delete mode 100644 src/strudel/codemirror/themes/whitescreen.mjs delete mode 100644 src/strudel/codemirror/themes/xcodeLight.mjs delete mode 100644 src/strudel/codemirror/tooltip.mjs delete mode 100644 src/strudel/codemirror/vite.config.js delete mode 100644 src/strudel/codemirror/widget.mjs create mode 100644 src/strudel/init.js delete mode 100644 src/strudel/mini/krill.pegjs delete mode 100644 src/strudel/soundfonts/convert.js delete mode 100644 src/strudel/superdough/README.md delete mode 100644 src/strudel/superdough/package.json delete mode 100644 src/strudel/superdough/vite.config.js delete mode 100644 src/strudel/supradough/.gitignore delete mode 100644 src/strudel/supradough/README.md delete mode 100644 src/strudel/supradough/dough-export.mjs delete mode 100644 src/strudel/supradough/package.json create mode 100644 src/styles.css create mode 100644 src/vue-shims.d.ts create mode 100644 tsconfig.json create mode 100644 version-bump.mjs create mode 100644 versions.json create mode 100644 vite.config.mts create mode 100644 watch.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..434fd94 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +TARGET_VAULT_DIR="/path/to/plugin/dir" +TARGET_TEST_VAULT_DIR="/path/to/plugin/dir" diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..e019f3c --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules/ + +main.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..8073b90 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,84 @@ +{ + "parser": "vue-eslint-parser", + "plugins": ["@typescript-eslint/eslint-plugin", "eslint-plugin-vue", "prettier"], + "extends": ["plugin:@typescript-eslint/recommended", "plugin:vue/vue3-recommended", "plugin:prettier/recommended"], + "parserOptions": { + "parser": "@typescript-eslint/parser", + "ecmaVersion": 2018, + "sourceType": "module" + }, + "env": { + "node": true + }, + "ignorePatterns": [ + "docker-formatter.*", + "node_modules/*", + ".nuxt/*", + "dist/*", + "dist-electron/*", + "server/*", + "scripts/*", + "src/assets/*" + ], + "rules": { + "arrow-body-style": 0, + "class-methods-use-this": 0, + "comma-dangle": 0, + "func-names": 0, + "import/extensions": 0, + "import/no-dynamic-require": 0, + "import/no-extraneous-dependencies": 0, + "import/no-named-as-default-member": 0, + "import/no-unresolved": 0, + "import/prefer-default-export": 0, + "linebreak-style": 0, + "max-len": [ + "warn", + { + "code": 120, + "ignoreTemplateLiterals": true, + "ignoreComments": true, + "ignoreStrings": true + } + ], + "no-await-in-loop": 0, + "no-console": 0, + "no-loop-func": 0, + "no-mixed-operators": 0, + "no-param-reassign": 0, + "no-restricted-syntax": 0, + "no-shadow": 0, + "no-trailing-spaces": 1, + "no-underscore-dangle": 0, + "no-use-before-define": 0, + "prefer-destructuring": 0, + "semi": 0, + "space-before-function-paren": 0, + "vue/multi-word-component-names": "off", + "vue/no-dupe-keys": "warn", + "vue/no-mutating-props": ["warn", { "shallowOnly": true }], + "vue/no-unused-vars": ["warn", { "ignorePattern": "^_" }], + "vue/no-use-v-if-with-v-for": "warn", + "vue/prop-name-casing": "error", + "vue/return-in-computed-property": "off", + "vue/require-prop-types": "error", + "vue/require-toggle-inside-transition": "warn", + "vue/require-typed-object-prop": "error", + "vue/require-v-for-key": "warn", + "vue/valid-v-for": "warn", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/no-inferrable-types": ["warn", { "ignoreParameters": true }], + "@typescript-eslint/no-unsafe-declaration-merging": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } + ], + "prettier/prettier": ["warn"] + } +} + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7fa4349 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# vscode +.vscode + +# Intellij +*.iml +.idea + +# npm +node_modules + +# Don't include the compiled main.js file in the repo. +# They should be uploaded to GitHub releases instead. +main.js + +# Exclude sourcemaps +*.map + +# obsidian +data.json + +# Exclude macOS Finder (System Explorer) View States +.DS_Store + +auto-imports.d.ts +components.d.ts + +build/* + +.env diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b973752 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +tag-version-prefix="" \ No newline at end of file diff --git a/.prettierrc.mjs b/.prettierrc.mjs new file mode 100644 index 0000000..5e5ada6 --- /dev/null +++ b/.prettierrc.mjs @@ -0,0 +1,15 @@ +/** + * @type {import('prettier').Options} + */ +const config = { + trailingComma: 'es5', + tabWidth: 2, + semi: false, + singleQuote: true, + printWidth: 100, + importOrder: ['^@/(.*)$', '^[./]'], + importOrderSeparation: true, + importOrderSortSpecifiers: true, +} + +export default config diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1f7cb4b --- /dev/null +++ b/LICENSE @@ -0,0 +1,622 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c240a4 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Strudel Obsidian plugin + +TODO diff --git a/build_and_copy_plugin.sh b/build_and_copy_plugin.sh new file mode 100755 index 0000000..0132058 --- /dev/null +++ b/build_and_copy_plugin.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +source ./.env + +# Exit immediately if a command exits with a non-zero status. +set -e + +# Define plugin source and build directories +PLUGIN_DIR="." +BUILD_DIR="$PLUGIN_DIR/build" + +echo "--- Building Strudel Obsidian Plugin ---" + +# Install dependencies if node_modules doesn't exist +if [ ! -d "node_modules" ]; then + echo "Node modules not found. Installing dependencies..." + npm install +fi + +# Run the build script +echo "Running build..." +npm run build + +echo "--- Copying Plugin Files to Test Vault ---" + +# Create target directory in test vault if it doesn't exist +mkdir -p "$TARGET_TEST_VAULT_DIR" +echo "Ensured target directory exists: $TARGET_TEST_VAULT_DIR" + +# Copy built files +cp "$BUILD_DIR/main.js" "$TARGET_TEST_VAULT_DIR/main.js" +echo "Copied main.js" + +cp "$BUILD_DIR/main.css" "$TARGET_TEST_VAULT_DIR/styles.css" +echo "Copied styles.css" + +cp "$PLUGIN_DIR/manifest.json" "$TARGET_TEST_VAULT_DIR/manifest.json" +echo "Copied manifest.json" + +echo "--- Plugin Build and Copy Complete ---" +echo "Plugin files are ready in: $TARGET_TEST_VAULT_DIR" diff --git a/build_and_copy_plugin_prod.sh b/build_and_copy_plugin_prod.sh new file mode 100755 index 0000000..0f68337 --- /dev/null +++ b/build_and_copy_plugin_prod.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Exit immediately if a command exits with a non-zero status. +set -e + +# Define plugin source and build directories +PLUGIN_DIR="." +BUILD_DIR="$PLUGIN_DIR/build" + +echo "--- Building Strudel Obsidian Plugin ---" + +# Install dependencies if node_modules doesn't exist +if [ ! -d "node_modules" ]; then + echo "Node modules not found. Installing dependencies..." + npm install +fi + +# Run the build script +echo "Running build..." +npm run build + +echo "--- Copying Plugin Files to Test Vault ---" + +# Copy built files +cp -X "$BUILD_DIR/main.js" "$TARGET_VAULT_DIR/main.js" +echo "Copied main.js" + +cp -X "$BUILD_DIR/main.css" "$TARGET_VAULT_DIR/styles.css" +echo "Copied styles.css" + +cp -X "$PLUGIN_DIR/manifest.json" "$TARGET_VAULT_DIR/manifest.json" +echo "Copied manifest.json" + +echo "--- Plugin Build and Copy Complete ---" +echo "Plugin files are ready in: $TARGET_VAULT_DIR" diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..82fd3c6 --- /dev/null +++ b/manifest.json @@ -0,0 +1,9 @@ +{ + "id": "strudel-repl", + "name": "Strudel REPL", + "version": "0.0.1", + "minAppVersion": "0.15.0", + "description": "", + "author": "Anton Duda", + "isDesktopOnly": false +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d5a7183 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6714 @@ +{ + "name": "strudel-obsidian-plugin", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "strudel-obsidian-plugin", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@codemirror/language": "https://github.com/lishid/cm-language", + "@codemirror/merge": "^6.11.0", + "@internationalized/date": "^3.8.2", + "@tonaljs/tonal": "^4.10.0", + "@vueuse/core": "^13.9.0", + "acorn": "^8.15.0", + "chord-voicings": "^0.0.1", + "dayjs": "^1.11.18", + "escodegen": "^2.1.0", + "estree-walker": "^3.0.3", + "file-type": "^20.5.0", + "fraction.js": "^5.3.4", + "front-matter": "^4.0.2", + "lucide-vue-next": "^0.284.0", + "nanoid": "^5.1.6", + "nanostores": "^1.0.1", + "pinia": "^3.0.2", + "sfumato": "^0.1.2", + "soundfont2": "^0.5.0", + "uuid": "^11.1.0", + "vue": "^3.5.16", + "webmidi": "^3.1.14" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.2", + "@types/js-yaml": "^4.0.9", + "@types/luxon": "^3.6.2", + "@types/node": "^24.9.1", + "@typescript-eslint/eslint-plugin": "5.61.0", + "@typescript-eslint/parser": "5.61.0", + "@vitejs/plugin-vue": "^5.2.4", + "builtin-modules": "3.3.0", + "esbuild": "0.17.3", + "esbuild-plugin-vue3": "^0.3.2", + "eslint": "8.31.0", + "eslint-config-prettier": "9.0.0", + "eslint-plugin-prettier": "5.0.0", + "eslint-plugin-vue": "9.23.0", + "obsidian": "latest", + "prettier": "^3.5.3", + "prettier-eslint": "16.1.2", + "sass-embedded": "^1.93.2", + "tslib": "2.4.0", + "typescript": "^5.8.3", + "vite": "^6.3.5", + "vue-tsc": "^2.2.10" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", + "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.9.0.tgz", + "integrity": "sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA==", + "dev": true + }, + "node_modules/@codemirror/language": { + "version": "6.11.2", + "resolved": "git+ssh://git@github.com/lishid/cm-language.git#a9c3c7efe17dd1d24395ee2a179fe12dd6ed1e76", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/merge": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@codemirror/merge/-/merge-6.11.0.tgz", + "integrity": "sha512-Wu5Camx8u0jKA4yV3IxcWGMIoXUxuptsbWW9kTje8d/NInnnALeyQaxcVssJznp9FRnu4As3qsBhacERyB9p6w==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/highlight": "^1.0.0", + "style-mod": "^4.1.0" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz", + "integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.38.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz", + "integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.3.tgz", + "integrity": "sha512-1Mlz934GvbgdDmt26rTLmf03cAgLg5HyOgJN+ZGCeP3Q9ynYTNMn2/LQxIl7Uy+o4K6Rfi2OuLsr12JQQR8gNg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.3.tgz", + "integrity": "sha512-XvJsYo3dO3Pi4kpalkyMvfQsjxPWHYjoX4MDiB/FUM4YMfWcXa5l4VCwFWVYI1+92yxqjuqrhNg0CZg3gSouyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.3.tgz", + "integrity": "sha512-nuV2CmLS07Gqh5/GrZLuqkU9Bm6H6vcCspM+zjp9TdQlxJtIe+qqEXQChmfc7nWdyr/yz3h45Utk1tUn8Cz5+A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.3.tgz", + "integrity": "sha512-01Hxaaat6m0Xp9AXGM8mjFtqqwDjzlMP0eQq9zll9U85ttVALGCGDuEvra5Feu/NbP5AEP1MaopPwzsTcUq1cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.3.tgz", + "integrity": "sha512-Eo2gq0Q/er2muf8Z83X21UFoB7EU6/m3GNKvrhACJkjVThd0uA+8RfKpfNhuMCl1bKRfBzKOk6xaYKQZ4lZqvA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.3.tgz", + "integrity": "sha512-CN62ESxaquP61n1ZjQP/jZte8CE09M6kNn3baos2SeUfdVBkWN5n6vGp2iKyb/bm/x4JQzEvJgRHLGd5F5b81w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.3.tgz", + "integrity": "sha512-feq+K8TxIznZE+zhdVurF3WNJ/Sa35dQNYbaqM/wsCbWdzXr5lyq+AaTUSER2cUR+SXPnd/EY75EPRjf4s1SLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.3.tgz", + "integrity": "sha512-CLP3EgyNuPcg2cshbwkqYy5bbAgK+VhyfMU7oIYyn+x4Y67xb5C5ylxsNUjRmr8BX+MW3YhVNm6Lq6FKtRTWHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.3.tgz", + "integrity": "sha512-JHeZXD4auLYBnrKn6JYJ0o5nWJI9PhChA/Nt0G4MvLaMrvXuWnY93R3a7PiXeJQphpL1nYsaMcoV2QtuvRnF/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.3.tgz", + "integrity": "sha512-FyXlD2ZjZqTFh0sOQxFDiWG1uQUEOLbEh9gKN/7pFxck5Vw0qjWSDqbn6C10GAa1rXJpwsntHcmLqydY9ST9ZA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.3.tgz", + "integrity": "sha512-OrDGMvDBI2g7s04J8dh8/I7eSO+/E7nMDT2Z5IruBfUO/RiigF1OF6xoH33Dn4W/OwAWSUf1s2nXamb28ZklTA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.3.tgz", + "integrity": "sha512-DcnUpXnVCJvmv0TzuLwKBC2nsQHle8EIiAJiJ+PipEVC16wHXaPEKP0EqN8WnBe0TPvMITOUlP2aiL5YMld+CQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.3.tgz", + "integrity": "sha512-BDYf/l1WVhWE+FHAW3FzZPtVlk9QsrwsxGzABmN4g8bTjmhazsId3h127pliDRRu5674k1Y2RWejbpN46N9ZhQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.3.tgz", + "integrity": "sha512-WViAxWYMRIi+prTJTyV1wnqd2mS2cPqJlN85oscVhXdb/ZTFJdrpaqm/uDsZPGKHtbg5TuRX/ymKdOSk41YZow==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.3.tgz", + "integrity": "sha512-Iw8lkNHUC4oGP1O/KhumcVy77u2s6+KUjieUqzEU3XuWJqZ+AY7uVMrrCbAiwWTkpQHkr00BuXH5RpC6Sb/7Ug==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.3.tgz", + "integrity": "sha512-0AGkWQMzeoeAtXQRNB3s4J1/T2XbigM2/Mn2yU1tQSmQRmHIZdkGbVq2A3aDdNslPyhb9/lH0S5GMTZ4xsjBqg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.3.tgz", + "integrity": "sha512-4+rR/WHOxIVh53UIQIICryjdoKdHsFZFD4zLSonJ9RRw7bhKzVyXbnRPsWSfwybYqw9sB7ots/SYyufL1mBpEg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.3.tgz", + "integrity": "sha512-cVpWnkx9IYg99EjGxa5Gc0XmqumtAwK3aoz7O4Dii2vko+qXbkHoujWA68cqXjhh6TsLaQelfDO4MVnyr+ODeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.3.tgz", + "integrity": "sha512-RxmhKLbTCDAY2xOfrww6ieIZkZF+KBqG7S2Ako2SljKXRFi+0863PspK74QQ7JpmWwncChY25JTJSbVBYGQk2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.3.tgz", + "integrity": "sha512-0r36VeEJ4efwmofxVJRXDjVRP2jTmv877zc+i+Pc7MNsIr38NfsjkQj23AfF7l0WbB+RQ7VUb+LDiqC/KY/M/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.3.tgz", + "integrity": "sha512-wgO6rc7uGStH22nur4aLFcq7Wh86bE9cOFmfTr/yxN3BXvDEdCSXyKkO+U5JIt53eTOgC47v9k/C1bITWL/Teg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.3.tgz", + "integrity": "sha512-FdVl64OIuiKjgXBjwZaJLKp0eaEckifbhn10dXWhysMJkWblg3OEEGKSIyhiD5RSgAya8WzP3DNkngtIg3Nt7g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@internationalized/date": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz", + "integrity": "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + }, + "node_modules/@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.2.tgz", + "integrity": "sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", + "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", + "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", + "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", + "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", + "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", + "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", + "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", + "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", + "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", + "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", + "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", + "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", + "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", + "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", + "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", + "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", + "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", + "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", + "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", + "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", + "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", + "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", + "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@swc/helpers/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@tokenizer/inflate": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "dependencies": { + "debug": "^4.4.0", + "fflate": "^0.8.2", + "token-types": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, + "node_modules/@tonaljs/abc-notation": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@tonaljs/abc-notation/-/abc-notation-4.9.1.tgz", + "integrity": "sha512-2fDUdPsFDdgZgyIiZCTYGKy30QiwIQxSXCSN2thGrSMXbQKCp8iTC8haStYcrA+25MPWhWlmvC/pz3tGcTNAqQ==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch-distance": "5.0.5", + "@tonaljs/pitch-note": "6.1.0" + } + }, + "node_modules/@tonaljs/array": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/@tonaljs/array/-/array-4.8.4.tgz", + "integrity": "sha512-97HVdpZy82PqNBDMM9PRSbO2DUrnxNg3++N4xqLpfby70fKHAHhTWrMXWZK+Dzs76HDPQLd+qhd4cq28eBZzjw==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch-note": "6.1.0" + } + }, + "node_modules/@tonaljs/chord": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/@tonaljs/chord/-/chord-4.10.2.tgz", + "integrity": "sha512-Zlqtq6c6T4y+EqvwHRQp9icEjHqyniCpnIwwCFg5amgAQwXmWzarouOOSmcdJ1Is92eEvcPVuCoLiVUtajS30A==", + "license": "MIT", + "dependencies": { + "@tonaljs/chord-detect": "^4.8.1", + "@tonaljs/chord-type": "^4.8.2", + "@tonaljs/collection": "^4.8.0", + "@tonaljs/core": "^4.10.0", + "@tonaljs/pcset": "^4.8.2", + "@tonaljs/scale-type": "^4.8.2" + } + }, + "node_modules/@tonaljs/chord-detect": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@tonaljs/chord-detect/-/chord-detect-4.9.1.tgz", + "integrity": "sha512-rV/9+R7aZ9cQorQ3jdNMMMh63onosglYZM71Q0n7KKcWMAGrxF66MzxBG82xy+w1QDMJQslB3iHfDHUiS6wRjA==", + "license": "MIT", + "dependencies": { + "@tonaljs/chord-type": "5.1.1", + "@tonaljs/pcset": "4.10.1", + "@tonaljs/pitch-note": "6.1.0" + } + }, + "node_modules/@tonaljs/chord-detect/node_modules/@tonaljs/chord-type": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@tonaljs/chord-type/-/chord-type-5.1.1.tgz", + "integrity": "sha512-ti4WzRYvvjH7to0G3zlJFq7WsjHqmcqbk8Jv98aZSR5YumLdY/ua2yOPPyoPq82n6vfgjZsacnAZ3v8/SodOcw==", + "license": "MIT", + "dependencies": { + "@tonaljs/pcset": "4.10.1" + } + }, + "node_modules/@tonaljs/chord-type": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@tonaljs/chord-type/-/chord-type-4.8.2.tgz", + "integrity": "sha512-GzSTjQmZjkUdPhyesFDeJbxpW8R7L/bAE34E8ApGAh9FMcKYpRdX3Tn+gkluRsXOiRMfW07p3E0Adx4bjtyN+Q==", + "license": "MIT", + "dependencies": { + "@tonaljs/core": "^4.8.0", + "@tonaljs/pcset": "^4.8.2" + } + }, + "node_modules/@tonaljs/collection": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@tonaljs/collection/-/collection-4.9.0.tgz", + "integrity": "sha512-Mk0h7O54nT6PgNVcUYauzxa5KOB23+0AOKudWzRH7JhJIN9vhVIC7PtwZXE+/G051UTbHSFIcN/afkgF4nB/8A==", + "license": "MIT" + }, + "node_modules/@tonaljs/core": { + "version": "4.10.4", + "resolved": "https://registry.npmjs.org/@tonaljs/core/-/core-4.10.4.tgz", + "integrity": "sha512-PhGlQ2Js6rFQ6CufkSiBpEJoPS8QIIhbXSXhT4U+5ffqckli+YBZRN24vjZ4AEKuX8xoYDmCCbl+r6agauvzmw==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "5.0.1", + "@tonaljs/pitch-distance": "5.0.2", + "@tonaljs/pitch-interval": "5.0.2", + "@tonaljs/pitch-note": "5.0.3" + } + }, + "node_modules/@tonaljs/core/node_modules/@tonaljs/pitch-distance": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch-distance/-/pitch-distance-5.0.2.tgz", + "integrity": "sha512-DPxfGJCf4BvfIVRxl2v142tuCKIziM6PomOBT0/s7U5o+Z5qFAIW0tNx/MKyL83guV74p+XIf5VI0w1flmXxDA==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "5.0.1", + "@tonaljs/pitch-interval": "5.0.2", + "@tonaljs/pitch-note": "5.0.3" + } + }, + "node_modules/@tonaljs/core/node_modules/@tonaljs/pitch-note": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch-note/-/pitch-note-5.0.3.tgz", + "integrity": "sha512-JsPgqPa8VZdZtfwvgoHJz996BZqzvlnRxUF6c/Q9q8E0iq30UHBEid0Y6XldK+h6tuIENsG3a9jyvNNxRqIeYw==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "5.0.1" + } + }, + "node_modules/@tonaljs/duration-value": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@tonaljs/duration-value/-/duration-value-4.9.0.tgz", + "integrity": "sha512-Muz54HyIe0nMYKWx6wyTa4y17ma29DtpJF4/oqJphy6A124rAVDe/SKit8JGOvDYAQj71FUXqs17sXBxO/ExVw==", + "license": "MIT" + }, + "node_modules/@tonaljs/interval": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@tonaljs/interval/-/interval-4.8.2.tgz", + "integrity": "sha512-9SEuJuqFdmu9lnvMmJtxbReQcQvZ1YHXhCcxaF6Re23/w+BqiJvvkvNZhfWH+n1+g0uaSdTsuvMfamp7hAvPMw==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "^5.0.1", + "@tonaljs/pitch-distance": "^5.0.2", + "@tonaljs/pitch-interval": "^5.0.2" + } + }, + "node_modules/@tonaljs/key": { + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/@tonaljs/key/-/key-4.11.2.tgz", + "integrity": "sha512-fc1y5+NwS4S3amirzYLLB324PxJDO00oIBbLJclAddc46UBeYnu4FNz+1nZboHCXRQ/06/ftuaWD/l7HrAoulw==", + "license": "MIT", + "dependencies": { + "@tonaljs/note": "4.12.1", + "@tonaljs/pitch-note": "6.1.0", + "@tonaljs/roman-numeral": "4.9.1" + } + }, + "node_modules/@tonaljs/midi": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/@tonaljs/midi/-/midi-4.10.2.tgz", + "integrity": "sha512-MPamXhEwPL7L1udLfYMm3Ft8mLYtHr62Zi2w5zYHM2P7YwIvNoiX0+dvAN5is1Wvq4iVRa8AjFHerjOW/SZhGg==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch-note": "6.1.0" + } + }, + "node_modules/@tonaljs/mode": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@tonaljs/mode/-/mode-4.9.1.tgz", + "integrity": "sha512-h+K7eOUKpyX7kMRCwCew6cV1oN0sZYD5LLeKD7zEERNd/6wvGBfGCdMZECGqzpFUmjUyCY87IOntem6EPbKVCg==", + "license": "MIT", + "dependencies": { + "@tonaljs/collection": "4.9.0", + "@tonaljs/interval": "5.1.0", + "@tonaljs/pcset": "4.10.1", + "@tonaljs/pitch-distance": "5.0.5", + "@tonaljs/pitch-note": "6.1.0", + "@tonaljs/scale-type": "4.9.1" + } + }, + "node_modules/@tonaljs/mode/node_modules/@tonaljs/interval": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@tonaljs/interval/-/interval-5.1.0.tgz", + "integrity": "sha512-GR9dUjn0j7yhjwjRh8HQxZYXOiVl05WfY3AFyMB9rfg807K4dSJmWfPTULPQXyHJ6NiZOPXcwRs8MxMLDxgdbg==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "^5.0.2", + "@tonaljs/pitch-distance": "^5.0.4", + "@tonaljs/pitch-interval": "^6.0.0" + } + }, + "node_modules/@tonaljs/mode/node_modules/@tonaljs/pitch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch/-/pitch-5.0.2.tgz", + "integrity": "sha512-mxaXJPPe+LIJdjzpZEl8I8Wx3dEvlzkBbsr2Ltwc2dTAdnErAZ5R0TxVq2egF27lMvQN2QPQPWI9iDPPdVUmrg==", + "license": "MIT" + }, + "node_modules/@tonaljs/mode/node_modules/@tonaljs/pitch-interval": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch-interval/-/pitch-interval-6.1.0.tgz", + "integrity": "sha512-9ZMxA7V4UgySnOKPIG6HECzhDb8mbiTCIdNNIzCIfz5XpjUmohku2YZpVVWMacLHgyeQicJzNoRiPel5oSAn4A==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "5.0.2" + } + }, + "node_modules/@tonaljs/note": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@tonaljs/note/-/note-4.12.1.tgz", + "integrity": "sha512-yh6cnhu21bUb0VuIQWxObSbWBVp2cHIkJj4zZ4qsNOW4jSqn74+wHpSbOTDF8q+2hl6upz7TtBRavtqwPtLUCQ==", + "license": "MIT", + "dependencies": { + "@tonaljs/midi": "4.10.2", + "@tonaljs/pitch": "5.0.2", + "@tonaljs/pitch-distance": "5.0.5", + "@tonaljs/pitch-interval": "6.1.0", + "@tonaljs/pitch-note": "6.1.0" + } + }, + "node_modules/@tonaljs/note/node_modules/@tonaljs/pitch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch/-/pitch-5.0.2.tgz", + "integrity": "sha512-mxaXJPPe+LIJdjzpZEl8I8Wx3dEvlzkBbsr2Ltwc2dTAdnErAZ5R0TxVq2egF27lMvQN2QPQPWI9iDPPdVUmrg==", + "license": "MIT" + }, + "node_modules/@tonaljs/note/node_modules/@tonaljs/pitch-interval": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch-interval/-/pitch-interval-6.1.0.tgz", + "integrity": "sha512-9ZMxA7V4UgySnOKPIG6HECzhDb8mbiTCIdNNIzCIfz5XpjUmohku2YZpVVWMacLHgyeQicJzNoRiPel5oSAn4A==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "5.0.2" + } + }, + "node_modules/@tonaljs/pcset": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@tonaljs/pcset/-/pcset-4.10.1.tgz", + "integrity": "sha512-CZG1rpKc38yMfpEJsbDTvsTmQqsek9xxcuMgK64Tr6sP1lEiDuZJbQeKVWWRnreBF2FQ1cEGLmfOpvSO+40csA==", + "license": "MIT", + "dependencies": { + "@tonaljs/collection": "4.9.0", + "@tonaljs/pitch": "5.0.2", + "@tonaljs/pitch-distance": "5.0.5", + "@tonaljs/pitch-interval": "6.1.0", + "@tonaljs/pitch-note": "6.1.0" + } + }, + "node_modules/@tonaljs/pcset/node_modules/@tonaljs/pitch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch/-/pitch-5.0.2.tgz", + "integrity": "sha512-mxaXJPPe+LIJdjzpZEl8I8Wx3dEvlzkBbsr2Ltwc2dTAdnErAZ5R0TxVq2egF27lMvQN2QPQPWI9iDPPdVUmrg==", + "license": "MIT" + }, + "node_modules/@tonaljs/pcset/node_modules/@tonaljs/pitch-interval": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch-interval/-/pitch-interval-6.1.0.tgz", + "integrity": "sha512-9ZMxA7V4UgySnOKPIG6HECzhDb8mbiTCIdNNIzCIfz5XpjUmohku2YZpVVWMacLHgyeQicJzNoRiPel5oSAn4A==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "5.0.2" + } + }, + "node_modules/@tonaljs/pitch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch/-/pitch-5.0.1.tgz", + "integrity": "sha512-5HYkF4hGY0jS2y5V3Hf5gNFXX46kT4cAcI7JLEn+qQb9N1dU9Gz9koI9di0mD1Tbam+kviceiCsJl4WX/FqYjA==", + "license": "MIT" + }, + "node_modules/@tonaljs/pitch-distance": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch-distance/-/pitch-distance-5.0.5.tgz", + "integrity": "sha512-dTfjsU0zyrj5YmiFio5prPaD5w7sBmHp4nnmlEg70nHY+SerAH0KiO9HM9usttVgRFUaXl0Gc7OI8YMGfSFmug==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "5.0.2", + "@tonaljs/pitch-interval": "6.1.0", + "@tonaljs/pitch-note": "6.1.0" + } + }, + "node_modules/@tonaljs/pitch-distance/node_modules/@tonaljs/pitch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch/-/pitch-5.0.2.tgz", + "integrity": "sha512-mxaXJPPe+LIJdjzpZEl8I8Wx3dEvlzkBbsr2Ltwc2dTAdnErAZ5R0TxVq2egF27lMvQN2QPQPWI9iDPPdVUmrg==", + "license": "MIT" + }, + "node_modules/@tonaljs/pitch-distance/node_modules/@tonaljs/pitch-interval": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch-interval/-/pitch-interval-6.1.0.tgz", + "integrity": "sha512-9ZMxA7V4UgySnOKPIG6HECzhDb8mbiTCIdNNIzCIfz5XpjUmohku2YZpVVWMacLHgyeQicJzNoRiPel5oSAn4A==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "5.0.2" + } + }, + "node_modules/@tonaljs/pitch-interval": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch-interval/-/pitch-interval-5.0.2.tgz", + "integrity": "sha512-bQmxeenRFNuuABs9mDc+bSUp3ySGw8I49pHMoGDdCcro9n3MDN8IsSwhvKoGOYErFMx76rNn1t+7WL8ukpvX5w==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "5.0.1" + } + }, + "node_modules/@tonaljs/pitch-note": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch-note/-/pitch-note-6.1.0.tgz", + "integrity": "sha512-A4OSLo8DjM38u73862LnDmL4YInDDRBmg0fojXcvu4cyU3oOlqndyeHOra1OVoH/WW46uNIxNs1wJDZNPWL5KQ==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "5.0.2" + } + }, + "node_modules/@tonaljs/pitch-note/node_modules/@tonaljs/pitch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch/-/pitch-5.0.2.tgz", + "integrity": "sha512-mxaXJPPe+LIJdjzpZEl8I8Wx3dEvlzkBbsr2Ltwc2dTAdnErAZ5R0TxVq2egF27lMvQN2QPQPWI9iDPPdVUmrg==", + "license": "MIT" + }, + "node_modules/@tonaljs/progression": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@tonaljs/progression/-/progression-4.9.1.tgz", + "integrity": "sha512-jHdZUNlXRmjrvbZrvjoW6syW7dO9ilhgvyouB6YuVM5lGShwTN9dsSuQYsugso9jYHkwQBgYj5SaXTTY0/0nHw==", + "license": "MIT", + "dependencies": { + "@tonaljs/chord": "6.1.1", + "@tonaljs/pitch-distance": "5.0.5", + "@tonaljs/pitch-interval": "6.1.0", + "@tonaljs/pitch-note": "6.1.0", + "@tonaljs/roman-numeral": "4.9.1" + } + }, + "node_modules/@tonaljs/progression/node_modules/@tonaljs/chord": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@tonaljs/chord/-/chord-6.1.1.tgz", + "integrity": "sha512-gsLyGGkOt0g5L/uF4ICpYQengahW3WAMjckyvyzvqMYpvH7fokmtcymOfbltDQzaVuYuL0TsVmbfjbah2rKEww==", + "license": "MIT", + "dependencies": { + "@tonaljs/chord-detect": "4.9.1", + "@tonaljs/chord-type": "5.1.1", + "@tonaljs/collection": "4.9.0", + "@tonaljs/interval": "^5.1.0", + "@tonaljs/pcset": "4.10.1", + "@tonaljs/pitch-distance": "5.0.5", + "@tonaljs/pitch-note": "6.1.0", + "@tonaljs/scale-type": "4.9.1" + } + }, + "node_modules/@tonaljs/progression/node_modules/@tonaljs/chord-type": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@tonaljs/chord-type/-/chord-type-5.1.1.tgz", + "integrity": "sha512-ti4WzRYvvjH7to0G3zlJFq7WsjHqmcqbk8Jv98aZSR5YumLdY/ua2yOPPyoPq82n6vfgjZsacnAZ3v8/SodOcw==", + "license": "MIT", + "dependencies": { + "@tonaljs/pcset": "4.10.1" + } + }, + "node_modules/@tonaljs/progression/node_modules/@tonaljs/interval": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@tonaljs/interval/-/interval-5.1.0.tgz", + "integrity": "sha512-GR9dUjn0j7yhjwjRh8HQxZYXOiVl05WfY3AFyMB9rfg807K4dSJmWfPTULPQXyHJ6NiZOPXcwRs8MxMLDxgdbg==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "^5.0.2", + "@tonaljs/pitch-distance": "^5.0.4", + "@tonaljs/pitch-interval": "^6.0.0" + } + }, + "node_modules/@tonaljs/progression/node_modules/@tonaljs/pitch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch/-/pitch-5.0.2.tgz", + "integrity": "sha512-mxaXJPPe+LIJdjzpZEl8I8Wx3dEvlzkBbsr2Ltwc2dTAdnErAZ5R0TxVq2egF27lMvQN2QPQPWI9iDPPdVUmrg==", + "license": "MIT" + }, + "node_modules/@tonaljs/progression/node_modules/@tonaljs/pitch-interval": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch-interval/-/pitch-interval-6.1.0.tgz", + "integrity": "sha512-9ZMxA7V4UgySnOKPIG6HECzhDb8mbiTCIdNNIzCIfz5XpjUmohku2YZpVVWMacLHgyeQicJzNoRiPel5oSAn4A==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "5.0.2" + } + }, + "node_modules/@tonaljs/range": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/@tonaljs/range/-/range-4.9.2.tgz", + "integrity": "sha512-XhFbCJCrEEIVz3MNmdVp9QUyiJA6eqnNnQeNqto8AuT5BYuffHDoMkBmvvjPuV5Lh8zfF9D0aqglvqPbZ+neKQ==", + "license": "MIT", + "dependencies": { + "@tonaljs/collection": "4.9.0", + "@tonaljs/midi": "4.10.2" + } + }, + "node_modules/@tonaljs/roman-numeral": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@tonaljs/roman-numeral/-/roman-numeral-4.9.1.tgz", + "integrity": "sha512-dJGKBNHdPrNTE97ZDk4t6wpNmgYcpHyLkAvWkRP9I/HsDkeTFFQqNXosYIvspPWrFySlRpeqqFwcqV74MYoOag==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "5.0.2", + "@tonaljs/pitch-interval": "6.1.0", + "@tonaljs/pitch-note": "6.1.0" + } + }, + "node_modules/@tonaljs/roman-numeral/node_modules/@tonaljs/pitch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch/-/pitch-5.0.2.tgz", + "integrity": "sha512-mxaXJPPe+LIJdjzpZEl8I8Wx3dEvlzkBbsr2Ltwc2dTAdnErAZ5R0TxVq2egF27lMvQN2QPQPWI9iDPPdVUmrg==", + "license": "MIT" + }, + "node_modules/@tonaljs/roman-numeral/node_modules/@tonaljs/pitch-interval": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@tonaljs/pitch-interval/-/pitch-interval-6.1.0.tgz", + "integrity": "sha512-9ZMxA7V4UgySnOKPIG6HECzhDb8mbiTCIdNNIzCIfz5XpjUmohku2YZpVVWMacLHgyeQicJzNoRiPel5oSAn4A==", + "license": "MIT", + "dependencies": { + "@tonaljs/pitch": "5.0.2" + } + }, + "node_modules/@tonaljs/scale": { + "version": "4.13.3", + "resolved": "https://registry.npmjs.org/@tonaljs/scale/-/scale-4.13.3.tgz", + "integrity": "sha512-IcPMpOm6gMQJ3A2IXZ5dN0wOhHIUonQY8abTql4BrwcVCUaItXWQfSd9t1HII+2kA6J8JCeY2BukTr6wVwAhXQ==", + "license": "MIT", + "dependencies": { + "@tonaljs/chord-type": "5.1.1", + "@tonaljs/collection": "4.9.0", + "@tonaljs/note": "4.12.1", + "@tonaljs/pcset": "4.10.1", + "@tonaljs/pitch-distance": "5.0.5", + "@tonaljs/pitch-note": "6.1.0", + "@tonaljs/scale-type": "4.9.1" + } + }, + "node_modules/@tonaljs/scale-type": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@tonaljs/scale-type/-/scale-type-4.9.1.tgz", + "integrity": "sha512-Nz2wThzz5NmWNx0vazX7MqNbF8kT1g5BEcbZcWjgkc67zhwosfZE0LEn7W9XcVDEws71b8CqQqDPKUEEybC9Rw==", + "license": "MIT", + "dependencies": { + "@tonaljs/pcset": "4.10.1" + } + }, + "node_modules/@tonaljs/scale/node_modules/@tonaljs/chord-type": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@tonaljs/chord-type/-/chord-type-5.1.1.tgz", + "integrity": "sha512-ti4WzRYvvjH7to0G3zlJFq7WsjHqmcqbk8Jv98aZSR5YumLdY/ua2yOPPyoPq82n6vfgjZsacnAZ3v8/SodOcw==", + "license": "MIT", + "dependencies": { + "@tonaljs/pcset": "4.10.1" + } + }, + "node_modules/@tonaljs/time-signature": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@tonaljs/time-signature/-/time-signature-4.9.0.tgz", + "integrity": "sha512-zRo8CBqg/2guzTlF2vxyVIuB5gmwWQaxlknJPUSDII8CTdQ/x3a1LlNoMKkvTUnqwCihe3WrNPZ5XUfOOTthYA==", + "license": "MIT" + }, + "node_modules/@tonaljs/tonal": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@tonaljs/tonal/-/tonal-4.10.0.tgz", + "integrity": "sha512-F2T9Fiy+j0MVped89kCrX1XF68mQOLUnny4pDOHrIf+OkrjtLQOzzaSOrX1tx0o72WUwQm1knz6D1d57b9X1HA==", + "license": "MIT", + "dependencies": { + "@tonaljs/abc-notation": "^4.8.0", + "@tonaljs/array": "^4.8.0", + "@tonaljs/chord": "^4.8.0", + "@tonaljs/chord-type": "^4.8.0", + "@tonaljs/collection": "^4.8.0", + "@tonaljs/core": "^4.8.0", + "@tonaljs/duration-value": "^4.8.0", + "@tonaljs/interval": "^4.8.0", + "@tonaljs/key": "^4.9.0", + "@tonaljs/midi": "^4.8.0", + "@tonaljs/mode": "^4.8.0", + "@tonaljs/note": "^4.9.0", + "@tonaljs/pcset": "^4.8.0", + "@tonaljs/progression": "^4.8.0", + "@tonaljs/range": "^4.8.0", + "@tonaljs/roman-numeral": "^4.8.0", + "@tonaljs/scale": "^4.9.0", + "@tonaljs/scale-type": "^4.8.0", + "@tonaljs/time-signature": "^4.8.0" + } + }, + "node_modules/@types/codemirror": { + "version": "5.60.8", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", + "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", + "dev": true, + "dependencies": { + "@types/tern": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/luxon": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz", + "integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==", + "dev": true + }, + "node_modules/@types/node": { + "version": "24.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", + "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true + }, + "node_modules/@types/tern": { + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", + "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz", + "integrity": "sha512-A5l/eUAug103qtkwccSCxn8ZRwT+7RXWkFECdA4Cvl1dOlDUgTpAOfSEElZn2uSUxhdDpnCdetrf0jvU4qrL+g==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.61.0", + "@typescript-eslint/type-utils": "5.61.0", + "@typescript-eslint/utils": "5.61.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.61.0.tgz", + "integrity": "sha512-yGr4Sgyh8uO6fSi9hw3jAFXNBHbCtKKFMdX2IkT3ZqpKmtAq3lHS4ixB/COFuAIJpwl9/AqF7j72ZDWYKmIfvg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.61.0", + "@typescript-eslint/types": "5.61.0", + "@typescript-eslint/typescript-estree": "5.61.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.61.0.tgz", + "integrity": "sha512-W8VoMjoSg7f7nqAROEmTt6LoBpn81AegP7uKhhW5KzYlehs8VV0ZW0fIDVbcZRcaP3aPSW+JZFua+ysQN+m/Nw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.61.0", + "@typescript-eslint/visitor-keys": "5.61.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.61.0.tgz", + "integrity": "sha512-kk8u//r+oVK2Aj3ph/26XdH0pbAkC2RiSjUYhKD+PExemG4XSjpGFeyZ/QM8lBOa7O8aGOU+/yEbMJgQv/DnCg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.61.0", + "@typescript-eslint/utils": "5.61.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.61.0.tgz", + "integrity": "sha512-ldyueo58KjngXpzloHUog/h9REmHl59G1b3a5Sng1GfBo14BkS3ZbMEb3693gnP1k//97lh7bKsp6/V/0v1veQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.61.0.tgz", + "integrity": "sha512-Fud90PxONnnLZ36oR5ClJBLTLfU4pIWBmnvGwTbEa2cXIqj70AEDEmOmpkFComjBZ/037ueKrOdHuYmSFVD7Rw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.61.0", + "@typescript-eslint/visitor-keys": "5.61.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.61.0.tgz", + "integrity": "sha512-mV6O+6VgQmVE6+xzlA91xifndPW9ElFW8vbSF0xCT/czPXVhwDewKila1jOyRwa9AE19zKnrr7Cg5S3pJVrTWQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.61.0", + "@typescript-eslint/types": "5.61.0", + "@typescript-eslint/typescript-estree": "5.61.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.61.0.tgz", + "integrity": "sha512-50XQ5VdbWrX06mQXhy93WywSFZZGsv3EOjq+lqp6WC2t+j3mb6A9xYVdrRxafvK88vg9k9u+CT4l6D8PEatjKg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.61.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", + "dev": true, + "dependencies": { + "@volar/source-map": "2.4.15" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", + "dev": true + }, + "node_modules/@volar/typescript": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", + "dev": true, + "dependencies": { + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.22.tgz", + "integrity": "sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==", + "dependencies": { + "@babel/parser": "^7.28.4", + "@vue/shared": "3.5.22", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz", + "integrity": "sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==", + "dependencies": { + "@vue/compiler-core": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz", + "integrity": "sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==", + "dependencies": { + "@babel/parser": "^7.28.4", + "@vue/compiler-core": "3.5.22", + "@vue/compiler-dom": "3.5.22", + "@vue/compiler-ssr": "3.5.22", + "@vue/shared": "3.5.22", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.19", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz", + "integrity": "sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==", + "dependencies": { + "@vue/compiler-dom": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz", + "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==", + "dependencies": { + "@vue/devtools-kit": "^7.7.7" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz", + "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==", + "dependencies": { + "@vue/devtools-shared": "^7.7.7", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-kit/node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==" + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz", + "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", + "dev": true, + "dependencies": { + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.22.tgz", + "integrity": "sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==", + "dependencies": { + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.22.tgz", + "integrity": "sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==", + "dependencies": { + "@vue/reactivity": "3.5.22", + "@vue/shared": "3.5.22" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz", + "integrity": "sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==", + "dependencies": { + "@vue/reactivity": "3.5.22", + "@vue/runtime-core": "3.5.22", + "@vue/shared": "3.5.22", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.22.tgz", + "integrity": "sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==", + "dependencies": { + "@vue/compiler-ssr": "3.5.22", + "@vue/shared": "3.5.22" + }, + "peerDependencies": { + "vue": "3.5.22" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.22.tgz", + "integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==" + }, + "node_modules/@vueuse/core": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.9.0.tgz", + "integrity": "sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "13.9.0", + "@vueuse/shared": "13.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.9.0.tgz", + "integrity": "sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.9.0.tgz", + "integrity": "sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/birpc": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.6.1.tgz", + "integrity": "sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "optional": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chord-voicings": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/chord-voicings/-/chord-voicings-0.0.1.tgz", + "integrity": "sha512-SutgB/4ynkkuiK6qdQ/k3QvCFcH0Vj8Ch4t6LbRyRQbVzP/TOztiCk3kvXd516UZ6fqk7ijDRELEFcKN+6V8sA==", + "license": "ISC", + "dependencies": { + "@tonaljs/tonal": "^4.6.5" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "dev": true + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/djipevents": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/djipevents/-/djipevents-2.0.7.tgz", + "integrity": "sha512-KNFYaU85imxOCKOUsIR70Iz9E19r96/X7LSH+u0tSoZdpWcBdzoqtTsU+wuLhc6GMpSFob+KInkZAbfKi01Bjg==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.20.6" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz", + "integrity": "sha512-9n3AsBRe6sIyOc6kmoXg2ypCLgf3eZSraWFRpnkto+svt8cZNuKTkb1bhQcitBcvIqjNiK7K0J3KPmwGSfkA8g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.3", + "@esbuild/android-arm64": "0.17.3", + "@esbuild/android-x64": "0.17.3", + "@esbuild/darwin-arm64": "0.17.3", + "@esbuild/darwin-x64": "0.17.3", + "@esbuild/freebsd-arm64": "0.17.3", + "@esbuild/freebsd-x64": "0.17.3", + "@esbuild/linux-arm": "0.17.3", + "@esbuild/linux-arm64": "0.17.3", + "@esbuild/linux-ia32": "0.17.3", + "@esbuild/linux-loong64": "0.17.3", + "@esbuild/linux-mips64el": "0.17.3", + "@esbuild/linux-ppc64": "0.17.3", + "@esbuild/linux-riscv64": "0.17.3", + "@esbuild/linux-s390x": "0.17.3", + "@esbuild/linux-x64": "0.17.3", + "@esbuild/netbsd-x64": "0.17.3", + "@esbuild/openbsd-x64": "0.17.3", + "@esbuild/sunos-x64": "0.17.3", + "@esbuild/win32-arm64": "0.17.3", + "@esbuild/win32-ia32": "0.17.3", + "@esbuild/win32-x64": "0.17.3" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz", + "integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz", + "integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz", + "integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz", + "integrity": "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz", + "integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz", + "integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz", + "integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz", + "integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz", + "integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz", + "integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz", + "integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz", + "integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz", + "integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz", + "integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz", + "integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz", + "integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-plugin-vue3": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/esbuild-plugin-vue3/-/esbuild-plugin-vue3-0.3.2.tgz", + "integrity": "sha512-KqZUPlIUS4vJLSexV3q5hgqIlsMWzlPtIuvZ1epZQvw/wJ/4vEPzEC1HQZduoHvUYvtnG703hsP1PsdpjTJ3ug==", + "dev": true, + "dependencies": { + "@vue/compiler-core": "^3.2.26", + "@vue/compiler-sfc": "^3.2.26", + "esbuild": "^0.14.8", + "typescript": "^4.7.4" + }, + "peerDependencies": { + "cheerio": "^1.0.0-rc.10", + "html-minifier": "^4.0.0", + "pug": "^3.0.2", + "sass": "^1.35.2" + }, + "peerDependenciesMeta": { + "cheerio": { + "optional": true + }, + "html-minifier": { + "optional": true + }, + "pug": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/esbuild-plugin-vue3/node_modules/@esbuild/linux-loong64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz", + "integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-plugin-vue3/node_modules/esbuild": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz", + "integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/linux-loong64": "0.14.54", + "esbuild-android-64": "0.14.54", + "esbuild-android-arm64": "0.14.54", + "esbuild-darwin-64": "0.14.54", + "esbuild-darwin-arm64": "0.14.54", + "esbuild-freebsd-64": "0.14.54", + "esbuild-freebsd-arm64": "0.14.54", + "esbuild-linux-32": "0.14.54", + "esbuild-linux-64": "0.14.54", + "esbuild-linux-arm": "0.14.54", + "esbuild-linux-arm64": "0.14.54", + "esbuild-linux-mips64le": "0.14.54", + "esbuild-linux-ppc64le": "0.14.54", + "esbuild-linux-riscv64": "0.14.54", + "esbuild-linux-s390x": "0.14.54", + "esbuild-netbsd-64": "0.14.54", + "esbuild-openbsd-64": "0.14.54", + "esbuild-sunos-64": "0.14.54", + "esbuild-windows-32": "0.14.54", + "esbuild-windows-64": "0.14.54", + "esbuild-windows-arm64": "0.14.54" + } + }, + "node_modules/esbuild-plugin-vue3/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz", + "integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz", + "integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz", + "integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz", + "integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint": { + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", + "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", + "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", + "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.5" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.23.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.23.0.tgz", + "integrity": "sha512-Bqd/b7hGYGrlV+wP/g77tjyFmp81lh5TMw0be9093X02SyelxRRfCI6/IsGq/J7Um0YwB9s0Ry0wlFyjPdmtUw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.0", + "vue-eslint-parser": "^9.4.2", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-type": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz", + "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==", + "dependencies": { + "@tokenizer/inflate": "^0.2.6", + "strtok3": "^10.2.0", + "token-types": "^6.0.0", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/front-matter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", + "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", + "dependencies": { + "js-yaml": "^3.13.1" + } + }, + "node_modules/front-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/front-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/immutable": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", + "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jazz-midi": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/jazz-midi/-/jazz-midi-1.7.9.tgz", + "integrity": "sha512-c8c4BBgwxdsIr1iVm53nadCrtH7BUlnX3V95ciK/gbvXN/ndE5+POskBalXgqlc/r9p2XUbdLTrgrC6fou5p9w==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-sdsl": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.2.tgz", + "integrity": "sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/jzz": { + "version": "1.9.6", + "resolved": "https://registry.npmjs.org/jzz/-/jzz-1.9.6.tgz", + "integrity": "sha512-J7ENLhXwfm2BNDKRUrL8eKtPhUS/CtMBpiafxQHDBcOWSocLhearDKEdh+ylnZFcr5OXWTed0gj6l/txeQA9vg==", + "license": "MIT", + "optional": true, + "dependencies": { + "jazz-midi": "^1.7.9" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loglevel-colored-level-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz", + "integrity": "sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "loglevel": "^1.4.1" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/lucide-vue-next": { + "version": "0.284.0", + "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.284.0.tgz", + "integrity": "sha512-RLO8mV88Uckm2X+RuOJY3zGwRk0MhLsJiWwnDQ0GvwIPXnvGH36jYe+C6/lmEbkTLxdm+PqMZ3Pe36yFD1CYUA==", + "peerDependencies": { + "vue": ">=3.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true + }, + "node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/nanostores": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.0.1.tgz", + "integrity": "sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "optional": true + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/obsidian": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.10.0.tgz", + "integrity": "sha512-F7hhnmGRQD1TanDPFT//LD3iKNUVd7N8sKL7flCCHRszfTxpDJ39j3T7LHbcGpyid906i6lD5oO+cnfLBzJMKw==", + "dev": true, + "dependencies": { + "@types/codemirror": "5.60.8", + "moment": "2.29.4" + }, + "peerDependencies": { + "@codemirror/state": "6.5.0", + "@codemirror/view": "6.38.1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz", + "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==", + "dependencies": { + "@vue/devtools-api": "^7.7.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-eslint": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-16.1.2.tgz", + "integrity": "sha512-mGFGZQbAh11FSnwW3H1zngzQYR2QMmHO8vdfgnAuzOFhnDeUZHYtwpqQvOMOMT0k818Dr1X+J4a/sVE0r34RKQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/parser": "^6.7.5", + "common-tags": "^1.4.0", + "dlv": "^1.1.0", + "eslint": "^8.7.0", + "indent-string": "^4.0.0", + "lodash.merge": "^4.6.0", + "loglevel-colored-level-prefix": "^1.0.0", + "prettier": "^3.0.1", + "pretty-format": "^29.7.0", + "require-relative": "^0.8.7", + "typescript": "^5.2.2", + "vue-eslint-parser": "^9.1.0" + }, + "engines": { + "node": ">=16.10.0" + } + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/prettier-eslint/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/prettier-eslint/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/require-relative": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", + "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==", + "dev": true + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", + "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.4", + "@rollup/rollup-android-arm64": "4.52.4", + "@rollup/rollup-darwin-arm64": "4.52.4", + "@rollup/rollup-darwin-x64": "4.52.4", + "@rollup/rollup-freebsd-arm64": "4.52.4", + "@rollup/rollup-freebsd-x64": "4.52.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", + "@rollup/rollup-linux-arm-musleabihf": "4.52.4", + "@rollup/rollup-linux-arm64-gnu": "4.52.4", + "@rollup/rollup-linux-arm64-musl": "4.52.4", + "@rollup/rollup-linux-loong64-gnu": "4.52.4", + "@rollup/rollup-linux-ppc64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-musl": "4.52.4", + "@rollup/rollup-linux-s390x-gnu": "4.52.4", + "@rollup/rollup-linux-x64-gnu": "4.52.4", + "@rollup/rollup-linux-x64-musl": "4.52.4", + "@rollup/rollup-openharmony-arm64": "4.52.4", + "@rollup/rollup-win32-arm64-msvc": "4.52.4", + "@rollup/rollup-win32-ia32-msvc": "4.52.4", + "@rollup/rollup-win32-x64-gnu": "4.52.4", + "@rollup/rollup-win32-x64-msvc": "4.52.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/sass": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz", + "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", + "dev": true, + "optional": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-embedded": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.93.2.tgz", + "integrity": "sha512-FvQdkn2dZ8DGiLgi0Uf4zsj7r/BsiLImNa5QJ10eZalY6NfZyjrmWGFcuCN5jNwlDlXFJnftauv+UtvBKLvepQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "^2.5.0", + "buffer-builder": "^0.2.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-all-unknown": "1.93.2", + "sass-embedded-android-arm": "1.93.2", + "sass-embedded-android-arm64": "1.93.2", + "sass-embedded-android-riscv64": "1.93.2", + "sass-embedded-android-x64": "1.93.2", + "sass-embedded-darwin-arm64": "1.93.2", + "sass-embedded-darwin-x64": "1.93.2", + "sass-embedded-linux-arm": "1.93.2", + "sass-embedded-linux-arm64": "1.93.2", + "sass-embedded-linux-musl-arm": "1.93.2", + "sass-embedded-linux-musl-arm64": "1.93.2", + "sass-embedded-linux-musl-riscv64": "1.93.2", + "sass-embedded-linux-musl-x64": "1.93.2", + "sass-embedded-linux-riscv64": "1.93.2", + "sass-embedded-linux-x64": "1.93.2", + "sass-embedded-unknown-all": "1.93.2", + "sass-embedded-win32-arm64": "1.93.2", + "sass-embedded-win32-x64": "1.93.2" + } + }, + "node_modules/sass-embedded-all-unknown": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.93.2.tgz", + "integrity": "sha512-GdEuPXIzmhRS5J7UKAwEvtk8YyHQuFZRcpnEnkA3rwRUI27kwjyXkNeIj38XjUQ3DzrfMe8HcKFaqWGHvblS7Q==", + "cpu": [ + "!arm", + "!arm64", + "!riscv64", + "!x64" + ], + "dev": true, + "optional": true, + "dependencies": { + "sass": "1.93.2" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.93.2.tgz", + "integrity": "sha512-I8bpO8meZNo5FvFx5FIiE7DGPVOYft0WjuwcCCdeJ6duwfkl6tZdatex1GrSigvTsuz9L0m4ngDcX/Tj/8yMow==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.93.2.tgz", + "integrity": "sha512-346f4iVGAPGcNP6V6IOOFkN5qnArAoXNTPr5eA/rmNpeGwomdb7kJyQ717r9rbJXxOG8OAAUado6J0qLsjnjXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-riscv64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.93.2.tgz", + "integrity": "sha512-hSMW1s4yJf5guT9mrdkumluqrwh7BjbZ4MbBW9tmi1DRDdlw1Wh9Oy1HnnmOG8x9XcI1qkojtPL6LUuEJmsiDg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.93.2.tgz", + "integrity": "sha512-JqktiHZduvn+ldGBosE40ALgQ//tGCVNAObgcQ6UIZznEJbsHegqStqhRo8UW3x2cgOO2XYJcrInH6cc7wdKbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.93.2.tgz", + "integrity": "sha512-qI1X16qKNeBJp+M/5BNW7v/JHCDYWr1/mdoJ7+UMHmP0b5AVudIZtimtK0hnjrLnBECURifd6IkulybR+h+4UA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.93.2.tgz", + "integrity": "sha512-4KeAvlkQ0m0enKUnDGQJZwpovYw99iiMb8CTZRSsQm8Eh7halbJZVmx67f4heFY/zISgVOCcxNg19GrM5NTwtA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.93.2.tgz", + "integrity": "sha512-N3+D/ToHtzwLDO+lSH05Wo6/KRxFBPnbjVHASOlHzqJnK+g5cqex7IFAp6ozzlRStySk61Rp6d+YGrqZ6/P0PA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.93.2.tgz", + "integrity": "sha512-9ftX6nd5CsShJqJ2WRg+ptaYvUW+spqZfJ88FbcKQBNFQm6L87luj3UI1rB6cP5EWrLwHA754OKxRJyzWiaN6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.93.2.tgz", + "integrity": "sha512-XBTvx66yRenvEsp3VaJCb3HQSyqCsUh7R+pbxcN5TuzueybZi0LXvn9zneksdXcmjACMlMpIVXi6LyHPQkYc8A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.93.2.tgz", + "integrity": "sha512-+3EHuDPkMiAX5kytsjEC1bKZCawB9J6pm2eBIzzLMPWbf5xdx++vO1DpT7hD4bm4ZGn0eVHgSOKIfP6CVz6tVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.93.2.tgz", + "integrity": "sha512-0sB5kmVZDKTYzmCSlTUnjh6mzOhzmQiW/NNI5g8JS4JiHw2sDNTvt1dsFTuqFkUHyEOY3ESTsfHHBQV8Ip4bEA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.93.2.tgz", + "integrity": "sha512-t3ejQ+1LEVuHy7JHBI2tWHhoMfhedUNDjGJR2FKaLgrtJntGnyD1RyX0xb3nuqL/UXiEAtmTmZY+Uh3SLUe1Hg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.93.2.tgz", + "integrity": "sha512-e7AndEwAbFtXaLy6on4BfNGTr3wtGZQmypUgYpSNVcYDO+CWxatKVY4cxbehMPhxG9g5ru+eaMfynvhZt7fLaA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.93.2.tgz", + "integrity": "sha512-U3EIUZQL11DU0xDDHXexd4PYPHQaSQa2hzc4EzmhHqrAj+TyfYO94htjWOd+DdTPtSwmLp+9cTWwPZBODzC96w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-unknown-all": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.93.2.tgz", + "integrity": "sha512-7VnaOmyewcXohiuoFagJ3SK5ddP9yXpU0rzz+pZQmS1/+5O6vzyFCUoEt3HDRaLctH4GT3nUGoK1jg0ae62IfQ==", + "dev": true, + "optional": true, + "os": [ + "!android", + "!darwin", + "!linux", + "!win32" + ], + "dependencies": { + "sass": "1.93.2" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.93.2.tgz", + "integrity": "sha512-Y90DZDbQvtv4Bt0GTXKlcT9pn4pz8AObEjFF8eyul+/boXwyptPZ/A1EyziAeNaIEIfxyy87z78PUgCeGHsx3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.93.2.tgz", + "integrity": "sha512-BbSucRP6PVRZGIwlEBkp+6VQl2GWdkWFMN+9EuOTPrLxCJZoq+yhzmbjspd3PeM8+7WJ7AdFu/uRYdO8tor1iQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sfumato": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/sfumato/-/sfumato-0.1.2.tgz", + "integrity": "sha512-j2s5BLUS5VUNtaK1l+v+yal3XjjV7JXCQIwE5Xs4yiQ3HJ+2Fc/dd3IkkrVHn0AJO2epShSWVoP3GnE0TvPdMg==", + "license": "ISC", + "dependencies": { + "soundfont2": "^0.4.0" + } + }, + "node_modules/sfumato/node_modules/soundfont2": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/soundfont2/-/soundfont2-0.4.0.tgz", + "integrity": "sha512-537WiurDBRbDLVhJMxXLE06D6yWxJCidfPClnibZ0f8dKMDpv+0fIfwCQ8pELE0JqKX05SOJosNJgKzQobaAEA==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/soundfont2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/soundfont2/-/soundfont2-0.5.0.tgz", + "integrity": "sha512-dcmNVtHT/Y8BOOrtmjt7hn6Bk6bB1g+O2bWB9fa6emW7kfwiEiEL4VvGQfwVt8g0m58LyoqVyuQ4ZFukMLwGHQ==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strtok3": { + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", + "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" + }, + "node_modules/superjson": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "dev": true, + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz", + "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/synckit/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/token-types": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", + "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", + "dependencies": { + "@borewit/text-codec": "^0.1.0", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "dev": true + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true + }, + "node_modules/vue": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz", + "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==", + "dependencies": { + "@vue/compiler-dom": "3.5.22", + "@vue/compiler-sfc": "3.5.22", + "@vue/runtime-dom": "3.5.22", + "@vue/server-renderer": "3.5.22", + "@vue/shared": "3.5.22" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-eslint-parser": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", + "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-eslint-parser/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/vue-tsc": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", + "dev": true, + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, + "node_modules/webmidi": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/webmidi/-/webmidi-3.1.14.tgz", + "integrity": "sha512-K9GzNm0J3R/61NJWAW7ipAJGWU5D/8bEjOir3PymFjLDpbQJ+ygjvm5jx/WQ8atQ1hu23St3lvnc5g1NKbOsrw==", + "license": "Apache-2.0", + "dependencies": { + "djipevents": "^2.0.7" + }, + "engines": { + "node": ">=8.5" + }, + "optionalDependencies": { + "jzz": "^1.8.5" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8e1a598 --- /dev/null +++ b/package.json @@ -0,0 +1,62 @@ +{ + "name": "strudel-obsidian-plugin", + "version": "0.0.1", + "description": "Strudel REPL Obsidian plugin", + "main": "main.js", + "scripts": { + "dev": "vite preview", + "build": "tsc -noEmit -skipLibCheck && vite build", + "version": "node version-bump.mjs && git add manifest.json versions.json" + }, + "keywords": [], + "author": "", + "license": "MIT", + "devDependencies": { + "@rollup/plugin-replace": "^6.0.2", + "@types/js-yaml": "^4.0.9", + "@types/luxon": "^3.6.2", + "@types/node": "^24.9.1", + "@typescript-eslint/eslint-plugin": "5.61.0", + "@typescript-eslint/parser": "5.61.0", + "@vitejs/plugin-vue": "^5.2.4", + "builtin-modules": "3.3.0", + "esbuild": "0.17.3", + "esbuild-plugin-vue3": "^0.3.2", + "eslint": "8.31.0", + "eslint-config-prettier": "9.0.0", + "eslint-plugin-prettier": "5.0.0", + "eslint-plugin-vue": "9.23.0", + "obsidian": "latest", + "prettier": "^3.5.3", + "prettier-eslint": "16.1.2", + "sass-embedded": "^1.93.2", + "tslib": "2.4.0", + "typescript": "^5.8.3", + "vite": "^6.3.5", + "vue-tsc": "^2.2.10" + }, + "dependencies": { + "@codemirror/language": "https://github.com/lishid/cm-language", + "@codemirror/merge": "^6.11.0", + "@internationalized/date": "^3.8.2", + "@tonaljs/tonal": "^4.10.0", + "@vueuse/core": "^13.9.0", + "acorn": "^8.15.0", + "chord-voicings": "^0.0.1", + "dayjs": "^1.11.18", + "escodegen": "^2.1.0", + "estree-walker": "^3.0.3", + "file-type": "^20.5.0", + "fraction.js": "^5.3.4", + "front-matter": "^4.0.2", + "lucide-vue-next": "^0.284.0", + "nanoid": "^5.1.6", + "nanostores": "^1.0.1", + "pinia": "^3.0.2", + "sfumato": "^0.1.2", + "soundfont2": "^0.5.0", + "uuid": "^11.1.0", + "vue": "^3.5.16", + "webmidi": "^3.1.14" + } +} diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..85e9205 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/commands/createStrudelBlock.ts b/src/commands/createStrudelBlock.ts new file mode 100644 index 0000000..d6a16e4 --- /dev/null +++ b/src/commands/createStrudelBlock.ts @@ -0,0 +1,28 @@ +import { STRUDEL_CODEBLOCK_KEYWORD } from '@/constants/keywords' +import { Editor, Notice } from 'obsidian' + +export const createStrudelBlock = async (editor: Editor) => { + if (!editor) { + new Notice('No active markdown editor found.', 3000) + return + } + + const cursor = editor.getCursor() + const shouldCreateNewLine = cursor.ch > 0 + const selection = editor.getSelection() + + const strudelBlock = `\`\`\`${STRUDEL_CODEBLOCK_KEYWORD} + +\`\`\`` + + + if (selection) { + editor.replaceSelection(strudelBlock) + } else { + // If no selection, just insert the linkPath at the cursor position + editor.replaceRange(strudelBlock, cursor) + } + + // setting cursor to the line inside the new strudel block + editor.setSelection({ line: cursor.line + (shouldCreateNewLine ? 2 : 1), ch: 0 }) +} diff --git a/src/components/StrudelBlockView.vue b/src/components/StrudelBlockView.vue new file mode 100644 index 0000000..ea32f29 --- /dev/null +++ b/src/components/StrudelBlockView.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/src/components/Views.vue b/src/components/Views.vue new file mode 100644 index 0000000..ef64160 --- /dev/null +++ b/src/components/Views.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/components/obsidian/Icon.vue b/src/components/obsidian/Icon.vue new file mode 100644 index 0000000..014dc81 --- /dev/null +++ b/src/components/obsidian/Icon.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/constants/keywords.ts b/src/constants/keywords.ts new file mode 100644 index 0000000..954f8f5 --- /dev/null +++ b/src/constants/keywords.ts @@ -0,0 +1 @@ +export const STRUDEL_CODEBLOCK_KEYWORD = 'strudel' diff --git a/src/editor/StrudelHeaderWidget.ts b/src/editor/StrudelHeaderWidget.ts new file mode 100644 index 0000000..6e8f043 --- /dev/null +++ b/src/editor/StrudelHeaderWidget.ts @@ -0,0 +1,58 @@ +import { WidgetType } from '@codemirror/view' +import { genid } from '@/helpers/vueUtils' +import { GlobalStore } from '@/stores/GlobalStore' +import { Strudel } from '@/entities/Strudel' + +export class StrudelHeaderWidget extends WidgetType { + private instance: Strudel + + constructor(code: string, filePath: string, lineFrom: number) { + super() + + this.instance = new Strudel({ + id: genid(), + code, + filePath, + lineFrom, + }) + } + + getInstance() { + return this.instance + } + + // should be called before any DOM updates + updateInstance(newInstance: Strudel) { + this.instance = newInstance + } + + toDOM() { + const container = document.createElement('div') + container.id = this.instance.id + + container.createDiv({ attr: { 'data-strudel-id': this.instance.id } }) + + if (!GlobalStore.getInstance().strudelBlocks.value.find((t) => t.id === this.instance.id)) { + GlobalStore.getInstance().strudelBlocks.value.push(this.instance) + } + + return container + } + + destroy() { + const store = GlobalStore.getInstance() + const index = store.strudelBlocks.value.findIndex((t) => t.id === this.instance.id) + if (index !== -1) { + // store.strudelBlocks.value[index].cleanup() + store.strudelBlocks.value.splice(index, 1) + } + } + + eq(other: StrudelHeaderWidget) { + return this.instance.compare(other.instance) + } + + ignoreEvent() { + return true + } +} diff --git a/src/editor/StrudelHighlight.ts b/src/editor/StrudelHighlight.ts new file mode 100644 index 0000000..51a8930 --- /dev/null +++ b/src/editor/StrudelHighlight.ts @@ -0,0 +1,164 @@ +import { RangeSetBuilder, StateEffect, StateField, Prec } from '@codemirror/state' +import { Decoration, DecorationSet, EditorView } from '@codemirror/view' + +export const setMiniLocations = StateEffect.define<{ + locations: Array<[number, number]> + shift: number +}>() +export const showMiniLocations = StateEffect.define<{ + atTime: number + haps: any[] +}>() +export const updateMiniLocations = ( + view: EditorView, + locations: Array<[number, number]>, + shift: number +) => { + view.dispatch({ effects: setMiniLocations.of({ locations, shift }) }) +} +export const highlightMiniLocations = (view: EditorView, atTime: number, haps: any[]) => { + view.dispatch({ effects: showMiniLocations.of({ atTime, haps }) }) +} + +const miniLocations = StateField.define({ + create() { + return Decoration.none + }, + update(locations, tr) { + if (tr.docChanged) { + locations = locations.map(tr.changes) + } + + // console.log('miniLocations update', tr.effects) + + for (const e of tr.effects) { + if (e.is(setMiniLocations)) { + // this is called on eval, with the mini locations obtained from the transpiler + // codemirror will automatically remap the marks when the document is edited + // create a mark for each mini location, adding the range to the spec to find it later + const marks = e.value.locations + .filter(([from]) => from < tr.newDoc.length) + .map(([from, to]) => [from, Math.min(to, tr.newDoc.length)]) + .map( + (range) => + Decoration.mark({ + id: range.join(':'), + // this green is only to verify that the decoration moves when the document is edited + // it will be removed later, so the mark is not visible by default + // attributes: { style: `background-color: #00CA2880` }, + }).range(range[0] + e.value.shift, range[1] + e.value.shift) // -> Decoration + ) + + locations = Decoration.set(marks, true) // -> DecorationSet === RangeSet + } + } + + return locations + }, +}) + +const visibleMiniLocations = StateField.define<{ + atTime: number + haps: Map +}>({ + create() { + return { atTime: 0, haps: new Map(), shift: 0 } + }, + update(visible, tr) { + for (const e of tr.effects) { + if (e.is(showMiniLocations)) { + // this is called every frame to show the locations that are currently active + // we can NOT create new marks because the context.locations haven't changed since eval time + // this is why we need to find a way to update the existing decorations, showing the ones that have an active range + const haps = new Map() + // console.log(e.value, e.value.haps) + for (const hap of e.value.haps) { + if (!hap.context?.locations || !hap.whole) { + continue + } + for (const { start, end } of hap.context.locations) { + // start += e.value.shift + // end += e.value.shift + const id = `${start}:${end}` + if (!haps.has(id) || haps.get(id).whole.begin.lt(hap.whole.begin)) { + haps.set(id, hap) + } + } + } + visible = { atTime: e.value.atTime, haps } + } + } + + return visible + }, +}) + +// // Derive the set of decorations from the miniLocations and visibleLocations +const miniLocationHighlights = EditorView.decorations.compute( + [miniLocations, visibleMiniLocations], + (state) => { + const iterator = state.field(miniLocations).iter() + const { haps } = state.field(visibleMiniLocations) + const builder = new RangeSetBuilder() + + // console.log('highlighting mini locations', haps, shift) + + while (iterator.value) { + const { + from, + to, + value: { + spec: { id }, + }, + } = iterator + + // console.log(haps, id) + + if (haps.has(id)) { + const hap = haps.get(id) + const colorMod = hap.value?.color ? ` strudel-mark_${hap.value.color}` : '' + // const style = hap.value?.markcss || `outline: solid 2px ${color}` + const classStr = `strudel-mark${colorMod}` + // Get explicit channels for color values + /* + const swatch = document.createElement('div'); + swatch.style.color = color; + document.body.appendChild(swatch); + let channels = getComputedStyle(swatch) + .color.match(/^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*(\d*(?:\.\d+)?))?\)$/) + .slice(1) + .map((c) => parseFloat(c || 1)); + document.body.removeChild(swatch); + + // Get percentage of event + const percent = 1 - (atTime - hap.whole.begin) / hap.whole.duration; + channels[3] *= percent; + */ + + builder.add( + from, + to, + Decoration.mark({ + // attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` }, + attributes: { class: classStr }, + }) + ) + } + + iterator.next() + } + + return builder.finish() + } +) + +export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights] + +// export const isPatternHighlightingEnabled = (on, config) => { +// on && +// config && +// setTimeout(() => { +// updateMiniLocations(config.editor, config.miniLocations) +// }, 100) +// return on ? Prec.highest(highlightExtension) : [] +// } diff --git a/src/editor/StrudelPlugin.ts b/src/editor/StrudelPlugin.ts new file mode 100644 index 0000000..96bb4e3 --- /dev/null +++ b/src/editor/StrudelPlugin.ts @@ -0,0 +1,97 @@ +import { StateField, RangeSetBuilder, EditorState } from '@codemirror/state' +import { Decoration, DecorationSet, EditorView } from '@codemirror/view' +import { editorLivePreviewField, editorInfoField } from 'obsidian' +import { StrudelHeaderWidget } from './StrudelHeaderWidget' +import { STRUDEL_CODEBLOCK_KEYWORD } from '@/constants/keywords' +import { GlobalStore } from '@/stores/GlobalStore' + +function buildStrudelDecorations(state: EditorState): DecorationSet { + const builder = new RangeSetBuilder() + + if (!state.field(editorLivePreviewField)) { + return builder.finish() + } + + const currentFile = state.field(editorInfoField)?.file + if (!currentFile) { + return builder.finish() + } + + for (let i = 1; i <= state.doc.lines; i++) { + const line = state.doc.line(i) + + if (line.text.trim() === `\`\`\`${STRUDEL_CODEBLOCK_KEYWORD}`) { + const codeLines = [] + let endLineFound = false + + // find the end of the code block and collect code lines + let endLineNumber = i + 1 + while (endLineNumber <= state.doc.lines) { + const endLine = state.doc.line(endLineNumber) + if (endLine.text.trim() === '```') { + endLineFound = true + break + } + codeLines.push(endLine.text) + endLineNumber++ + } + + if (!endLineFound) { + continue // skip if no closing ``` + } + + const code = codeLines.join('\n') + + if (!code.trim()) { + continue // skip empty code blocks + } + + const widget = new StrudelHeaderWidget(code, currentFile.path, state.doc.line(i + 1).from) + + // searching for existing instance + const existingInstance = GlobalStore.getInstance().strudelBlocks.value.find((s) => + s.compare(widget.getInstance()) + ) + + if (existingInstance) { + existingInstance.code = code + existingInstance.lineFrom = state.doc.line(i + 1).from + // update instance at newly created widget + widget.updateInstance(existingInstance) + } + + // add widget at the start of the code block + builder.add( + line.from, + line.from, + Decoration.widget({ + widget, + block: true, + side: -1, + }) + ) + } + } + + return builder.finish() +} + +export const strudelStateField = StateField.define({ + create(state) { + return buildStrudelDecorations(state) + }, + update(decorations, tr) { + if ( + tr.docChanged || + tr.selection || + tr.state.field(editorLivePreviewField) !== tr.startState.field(editorLivePreviewField) + ) { + return buildStrudelDecorations(tr.state) + } + + return decorations.map(tr.changes) + }, + provide(field) { + return [EditorView.decorations.from(field)] + }, +}) diff --git a/src/editor/SyntaxHighlighting.js b/src/editor/SyntaxHighlighting.js new file mode 100644 index 0000000..2d7e130 --- /dev/null +++ b/src/editor/SyntaxHighlighting.js @@ -0,0 +1,1149 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +CodeMirror.defineMode('strudel', function (config, parserConfig) { + var indentUnit = config.indentUnit + var statementIndent = parserConfig.statementIndent + var jsonldMode = false + var jsonMode = false + var isTS = false + var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/ + + // Tokenizer + + var keywords = (function () { + function kw(type) { + return { type: type, style: 'keyword' } + } + var A = kw('keyword a'), + B = kw('keyword b'), + C = kw('keyword c'), + D = kw('keyword d') + var operator = kw('operator'), + atom = { type: 'atom', style: 'atom' } + + return { + if: kw('if'), + while: A, + with: A, + else: B, + do: B, + try: B, + finally: B, + return: D, + break: D, + continue: D, + new: kw('new'), + delete: C, + void: C, + throw: C, + debugger: kw('debugger'), + var: kw('var'), + const: kw('var'), + let: kw('var'), + function: kw('function'), + catch: kw('catch'), + for: kw('for'), + switch: kw('switch'), + case: kw('case'), + default: kw('default'), + in: operator, + typeof: operator, + instanceof: operator, + true: atom, + false: atom, + null: atom, + undefined: atom, + NaN: atom, + Infinity: atom, + this: kw('this'), + class: kw('class'), + super: kw('atom'), + yield: C, + export: kw('export'), + import: kw('import'), + extends: C, + await: C, + } + })() + + var isOperatorChar = /[+\-*&%=<>!?|~^@]/ + var isJsonldKeyword = + /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/ + + function readRegexp(stream) { + var escaped = false, + next, + inSet = false + while ((next = stream.next()) != null) { + if (!escaped) { + if (next == '/' && !inSet) return + if (next == '[') inSet = true + else if (inSet && next == ']') inSet = false + } + escaped = !escaped && next == '\\' + } + } + + // Used as scratch variables to communicate multiple values without + // consing up tons of objects. + var type, content + function ret(tp, style, cont) { + type = tp + content = cont + return style + } + function tokenBase(stream, state) { + var ch = stream.next() + if (ch == '"' || ch == "'") { + state.tokenize = tokenString(ch) + return state.tokenize(stream, state) + } else if (ch == '.' && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) { + return ret('number', 'number') + } else if (ch == '.' && stream.match('..')) { + return ret('spread', 'meta') + } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + return ret(ch) + } else if (ch == '=' && stream.eat('>')) { + return ret('=>', 'operator') + } else if (ch == '0' && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) { + return ret('number', 'number') + } else if (/\d/.test(ch)) { + stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/) + return ret('number', 'number') + } else if (ch == '/') { + if (stream.eat('*')) { + state.tokenize = tokenComment + return tokenComment(stream, state) + } else if (stream.eat('/')) { + stream.skipToEnd() + return ret('comment', 'comment') + } else if (expressionAllowed(stream, state, 1)) { + readRegexp(stream) + stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/) + return ret('regexp', 'string-2') + } else { + stream.eat('=') + return ret('operator', 'operator', stream.current()) + } + } else if (ch == '`') { + state.tokenize = tokenQuasi + return tokenQuasi(stream, state) + } else if (ch == '#' && stream.peek() == '!') { + stream.skipToEnd() + return ret('meta', 'meta') + } else if (ch == '#' && stream.eatWhile(wordRE)) { + return ret('variable', 'property') + } else if ( + (ch == '<' && stream.match('!--')) || + (ch == '-' && stream.match('->') && !/\S/.test(stream.string.slice(0, stream.start))) + ) { + stream.skipToEnd() + return ret('comment', 'comment') + } else if (isOperatorChar.test(ch)) { + if (ch != '>' || !state.lexical || state.lexical.type != '>') { + if (stream.eat('=')) { + if (ch == '!' || ch == '=') stream.eat('=') + } else if (/[<>*+\-|&?]/.test(ch)) { + stream.eat(ch) + if (ch == '>') stream.eat(ch) + } + } + if (ch == '?' && stream.eat('.')) return ret('.') + return ret('operator', 'operator', stream.current()) + } else if (wordRE.test(ch)) { + stream.eatWhile(wordRE) + var word = stream.current() + if (state.lastType != '.') { + if (keywords.propertyIsEnumerable(word)) { + var kw = keywords[word] + return ret(kw.type, kw.style, word) + } + if (word == 'async' && stream.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/, false)) + return ret('async', 'keyword', word) + } + return ret('variable', 'variable', word) + } + } + + function tokenString(quote) { + return function (stream, state) { + var escaped = false, + next + if (jsonldMode && stream.peek() == '@' && stream.match(isJsonldKeyword)) { + state.tokenize = tokenBase + return ret('jsonld-keyword', 'meta') + } + while ((next = stream.next()) != null) { + if (next == quote && !escaped) break + escaped = !escaped && next == '\\' + } + if (!escaped) state.tokenize = tokenBase + return ret('string', 'string') + } + } + + function tokenComment(stream, state) { + var maybeEnd = false, + ch + while ((ch = stream.next())) { + if (ch == '/' && maybeEnd) { + state.tokenize = tokenBase + break + } + maybeEnd = ch == '*' + } + return ret('comment', 'comment') + } + + function tokenQuasi(stream, state) { + var escaped = false, + next + while ((next = stream.next()) != null) { + if (!escaped && (next == '`' || (next == '$' && stream.eat('{')))) { + state.tokenize = tokenBase + break + } + escaped = !escaped && next == '\\' + } + return ret('quasi', 'string-2', stream.current()) + } + + var brackets = '([{}])' + // This is a crude lookahead trick to try and notice that we're + // parsing the argument patterns for a fat-arrow function before we + // actually hit the arrow token. It only works if the arrow is on + // the same line as the arguments and there's no strange noise + // (comments) in between. Fallback is to only notice when we hit the + // arrow, and not declare the arguments as locals for the arrow + // body. + function findFatArrow(stream, state) { + if (state.fatArrowAt) state.fatArrowAt = null + var arrow = stream.string.indexOf('=>', stream.start) + if (arrow < 0) return + + if (isTS) { + // Try to skip TypeScript return type declarations after the arguments + var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec( + stream.string.slice(stream.start, arrow) + ) + if (m) arrow = m.index + } + + var depth = 0, + sawSomething = false + for (var pos = arrow - 1; pos >= 0; --pos) { + var ch = stream.string.charAt(pos) + var bracket = brackets.indexOf(ch) + if (bracket >= 0 && bracket < 3) { + if (!depth) { + ++pos + break + } + if (--depth == 0) { + if (ch == '(') sawSomething = true + break + } + } else if (bracket >= 3 && bracket < 6) { + ++depth + } else if (wordRE.test(ch)) { + sawSomething = true + } else if (/["'\/`]/.test(ch)) { + for (; ; --pos) { + if (pos == 0) return + var next = stream.string.charAt(pos - 1) + if (next == ch && stream.string.charAt(pos - 2) != '\\') { + pos-- + break + } + } + } else if (sawSomething && !depth) { + ++pos + break + } + } + if (sawSomething && !depth) state.fatArrowAt = pos + } + + // Parser + + var atomicTypes = { + atom: true, + number: true, + variable: true, + string: true, + regexp: true, + this: true, + 'jsonld-keyword': true, + } + + function JSLexical(indented, column, type, align, prev, info) { + this.indented = indented + this.column = column + this.type = type + this.prev = prev + this.info = info + if (align != null) this.align = align + } + + function inScope(state, varname) { + for (var v = state.localVars; v; v = v.next) if (v.name == varname) return true + for (var cx = state.context; cx; cx = cx.prev) { + for (var v = cx.vars; v; v = v.next) if (v.name == varname) return true + } + } + + function parseJS(state, style, type, content, stream) { + var cc = state.cc + // Communicate our context to the combinators. + // (Less wasteful than consing up a hundred closures on every call.) + cx.state = state + cx.stream = stream + ;((cx.marked = null), (cx.cc = cc)) + cx.style = style + + if (!state.lexical.hasOwnProperty('align')) state.lexical.align = true + + while (true) { + var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement + if (combinator(type, content)) { + while (cc.length && cc[cc.length - 1].lex) cc.pop()() + if (cx.marked) return cx.marked + if (type == 'variable' && inScope(state, content)) return 'variable-2' + return style + } + } + } + + // Combinator utils + + var cx = { state: null, column: null, marked: null, cc: null } + function pass() { + for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]) + } + function cont() { + pass.apply(null, arguments) + return true + } + function inList(name, list) { + for (var v = list; v; v = v.next) if (v.name == name) return true + return false + } + function register(varname) { + var state = cx.state + cx.marked = 'def' + if (state.context) { + if (state.lexical.info == 'var' && state.context && state.context.block) { + // FIXME function decls are also not block scoped + var newContext = registerVarScoped(varname, state.context) + if (newContext != null) { + state.context = newContext + return + } + } else if (!inList(varname, state.localVars)) { + state.localVars = new Var(varname, state.localVars) + return + } + } + // Fall through means this is global + if (parserConfig.globalVars && !inList(varname, state.globalVars)) + state.globalVars = new Var(varname, state.globalVars) + } + function registerVarScoped(varname, context) { + if (!context) { + return null + } else if (context.block) { + var inner = registerVarScoped(varname, context.prev) + if (!inner) return null + if (inner == context.prev) return context + return new Context(inner, context.vars, true) + } else if (inList(varname, context.vars)) { + return context + } else { + return new Context(context.prev, new Var(varname, context.vars), false) + } + } + + function isModifier(name) { + return ( + name == 'public' || + name == 'private' || + name == 'protected' || + name == 'abstract' || + name == 'readonly' + ) + } + + // Combinators + + function Context(prev, vars, block) { + this.prev = prev + this.vars = vars + this.block = block + } + function Var(name, next) { + this.name = name + this.next = next + } + + var defaultVars = new Var('this', new Var('arguments', null)) + function pushcontext() { + cx.state.context = new Context(cx.state.context, cx.state.localVars, false) + cx.state.localVars = defaultVars + } + function pushblockcontext() { + cx.state.context = new Context(cx.state.context, cx.state.localVars, true) + cx.state.localVars = null + } + function popcontext() { + cx.state.localVars = cx.state.context.vars + cx.state.context = cx.state.context.prev + } + popcontext.lex = true + function pushlex(type, info) { + var result = function () { + var state = cx.state, + indent = state.indented + if (state.lexical.type == 'stat') indent = state.lexical.indented + else + for ( + var outer = state.lexical; + outer && outer.type == ')' && outer.align; + outer = outer.prev + ) + indent = outer.indented + state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info) + } + result.lex = true + return result + } + function poplex() { + var state = cx.state + if (state.lexical.prev) { + if (state.lexical.type == ')') state.indented = state.lexical.indented + state.lexical = state.lexical.prev + } + } + poplex.lex = true + + function expect(wanted) { + function exp(type) { + if (type == wanted) return cont() + else if (wanted == ';' || type == '}' || type == ')' || type == ']') return pass() + else return cont(exp) + } + return exp + } + + function statement(type, value) { + if (type == 'var') return cont(pushlex('vardef', value), vardef, expect(';'), poplex) + if (type == 'keyword a') return cont(pushlex('form'), parenExpr, statement, poplex) + if (type == 'keyword b') return cont(pushlex('form'), statement, poplex) + if (type == 'keyword d') + return cx.stream.match(/^\s*$/, false) + ? cont() + : cont(pushlex('stat'), maybeexpression, expect(';'), poplex) + if (type == 'debugger') return cont(expect(';')) + if (type == '{') return cont(pushlex('}'), pushblockcontext, block, poplex, popcontext) + if (type == ';') return cont() + if (type == 'if') { + if (cx.state.lexical.info == 'else' && cx.state.cc[cx.state.cc.length - 1] == poplex) + cx.state.cc.pop()() + return cont(pushlex('form'), parenExpr, statement, poplex, maybeelse) + } + if (type == 'function') return cont(functiondef) + if (type == 'for') return cont(pushlex('form'), forspec, statement, poplex) + if (type == 'class' || (isTS && value == 'interface')) { + cx.marked = 'keyword' + return cont(pushlex('form', type == 'class' ? type : value), className, poplex) + } + if (type == 'variable') { + if (isTS && value == 'declare') { + cx.marked = 'keyword' + return cont(statement) + } else if ( + isTS && + (value == 'module' || value == 'enum' || value == 'type') && + cx.stream.match(/^\s*\w/, false) + ) { + cx.marked = 'keyword' + if (value == 'enum') return cont(enumdef) + else if (value == 'type') return cont(typename, expect('operator'), typeexpr, expect(';')) + else return cont(pushlex('form'), pattern, expect('{'), pushlex('}'), block, poplex, poplex) + } else if (isTS && value == 'namespace') { + cx.marked = 'keyword' + return cont(pushlex('form'), expression, statement, poplex) + } else if (isTS && value == 'abstract') { + cx.marked = 'keyword' + return cont(statement) + } else { + return cont(pushlex('stat'), maybelabel) + } + } + if (type == 'switch') + return cont( + pushlex('form'), + parenExpr, + expect('{'), + pushlex('}', 'switch'), + pushblockcontext, + block, + poplex, + poplex, + popcontext + ) + if (type == 'case') return cont(expression, expect(':')) + if (type == 'default') return cont(expect(':')) + if (type == 'catch') + return cont(pushlex('form'), pushcontext, maybeCatchBinding, statement, poplex, popcontext) + if (type == 'export') return cont(pushlex('stat'), afterExport, poplex) + if (type == 'import') return cont(pushlex('stat'), afterImport, poplex) + if (type == 'async') return cont(statement) + if (value == '@') return cont(expression, statement) + return pass(pushlex('stat'), expression, expect(';'), poplex) + } + function maybeCatchBinding(type) { + if (type == '(') return cont(funarg, expect(')')) + } + function expression(type, value) { + return expressionInner(type, value, false) + } + function expressionNoComma(type, value) { + return expressionInner(type, value, true) + } + function parenExpr(type) { + if (type != '(') return pass() + return cont(pushlex(')'), maybeexpression, expect(')'), poplex) + } + function expressionInner(type, value, noComma) { + if (cx.state.fatArrowAt == cx.stream.start) { + var body = noComma ? arrowBodyNoComma : arrowBody + if (type == '(') + return cont( + pushcontext, + pushlex(')'), + commasep(funarg, ')'), + poplex, + expect('=>'), + body, + popcontext + ) + else if (type == 'variable') return pass(pushcontext, pattern, expect('=>'), body, popcontext) + } + + var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma + if (atomicTypes.hasOwnProperty(type)) return cont(maybeop) + if (type == 'function') return cont(functiondef, maybeop) + if (type == 'class' || (isTS && value == 'interface')) { + cx.marked = 'keyword' + return cont(pushlex('form'), classExpression, poplex) + } + if (type == 'keyword c' || type == 'async') + return cont(noComma ? expressionNoComma : expression) + if (type == '(') return cont(pushlex(')'), maybeexpression, expect(')'), poplex, maybeop) + if (type == 'operator' || type == 'spread') + return cont(noComma ? expressionNoComma : expression) + if (type == '[') return cont(pushlex(']'), arrayLiteral, poplex, maybeop) + if (type == '{') return contCommasep(objprop, '}', null, maybeop) + if (type == 'quasi') return pass(quasi, maybeop) + if (type == 'new') return cont(maybeTarget(noComma)) + if (type == 'import') return cont(expression) + return cont() + } + function maybeexpression(type) { + if (type.match(/[;\}\)\],]/)) return pass() + return pass(expression) + } + + function maybeoperatorComma(type, value) { + if (type == ',') return cont(maybeexpression) + return maybeoperatorNoComma(type, value, false) + } + function maybeoperatorNoComma(type, value, noComma) { + var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma + var expr = noComma == false ? expression : expressionNoComma + if (type == '=>') return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext) + if (type == 'operator') { + if (/\+\+|--/.test(value) || (isTS && value == '!')) return cont(me) + if (isTS && value == '<' && cx.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/, false)) + return cont(pushlex('>'), commasep(typeexpr, '>'), poplex, me) + if (value == '?') return cont(expression, expect(':'), expr) + return cont(expr) + } + if (type == 'quasi') { + return pass(quasi, me) + } + if (type == ';') return + if (type == '(') return contCommasep(expressionNoComma, ')', 'call', me) + if (type == '.') return cont(property, me) + if (type == '[') return cont(pushlex(']'), maybeexpression, expect(']'), poplex, me) + if (isTS && value == 'as') { + cx.marked = 'keyword' + return cont(typeexpr, me) + } + if (type == 'regexp') { + cx.state.lastType = cx.marked = 'operator' + cx.stream.backUp(cx.stream.pos - cx.stream.start - 1) + return cont(expr) + } + } + function quasi(type, value) { + if (type != 'quasi') return pass() + if (value.slice(value.length - 2) != '${') return cont(quasi) + return cont(expression, continueQuasi) + } + function continueQuasi(type) { + if (type == '}') { + cx.marked = 'string-2' + cx.state.tokenize = tokenQuasi + return cont(quasi) + } + } + function arrowBody(type) { + findFatArrow(cx.stream, cx.state) + return pass(type == '{' ? statement : expression) + } + function arrowBodyNoComma(type) { + findFatArrow(cx.stream, cx.state) + return pass(type == '{' ? statement : expressionNoComma) + } + function maybeTarget(noComma) { + return function (type) { + if (type == '.') return cont(noComma ? targetNoComma : target) + else if (type == 'variable' && isTS) + return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma) + else return pass(noComma ? expressionNoComma : expression) + } + } + function target(_, value) { + if (value == 'target') { + cx.marked = 'keyword' + return cont(maybeoperatorComma) + } + } + function targetNoComma(_, value) { + if (value == 'target') { + cx.marked = 'keyword' + return cont(maybeoperatorNoComma) + } + } + function maybelabel(type) { + if (type == ':') return cont(poplex, statement) + return pass(maybeoperatorComma, expect(';'), poplex) + } + function property(type) { + if (type == 'variable') { + cx.marked = 'property' + return cont() + } + } + function objprop(type, value) { + if (type == 'async') { + cx.marked = 'property' + return cont(objprop) + } else if (type == 'variable' || cx.style == 'keyword') { + cx.marked = 'property' + if (value == 'get' || value == 'set') return cont(getterSetter) + var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params + if ( + isTS && + cx.state.fatArrowAt == cx.stream.start && + (m = cx.stream.match(/^\s*:\s*/, false)) + ) + cx.state.fatArrowAt = cx.stream.pos + m[0].length + return cont(afterprop) + } else if (type == 'number' || type == 'string') { + cx.marked = jsonldMode ? 'property' : cx.style + ' property' + return cont(afterprop) + } else if (type == 'jsonld-keyword') { + return cont(afterprop) + } else if (isTS && isModifier(value)) { + cx.marked = 'keyword' + return cont(objprop) + } else if (type == '[') { + return cont(expression, maybetype, expect(']'), afterprop) + } else if (type == 'spread') { + return cont(expressionNoComma, afterprop) + } else if (value == '*') { + cx.marked = 'keyword' + return cont(objprop) + } else if (type == ':') { + return pass(afterprop) + } + } + function getterSetter(type) { + if (type != 'variable') return pass(afterprop) + cx.marked = 'property' + return cont(functiondef) + } + function afterprop(type) { + if (type == ':') return cont(expressionNoComma) + if (type == '(') return pass(functiondef) + } + function commasep(what, end, sep) { + function proceed(type, value) { + if (sep ? sep.indexOf(type) > -1 : type == ',') { + var lex = cx.state.lexical + if (lex.info == 'call') lex.pos = (lex.pos || 0) + 1 + return cont(function (type, value) { + if (type == end || value == end) return pass() + return pass(what) + }, proceed) + } + if (type == end || value == end) return cont() + if (sep && sep.indexOf(';') > -1) return pass(what) + return cont(expect(end)) + } + return function (type, value) { + if (type == end || value == end) return cont() + return pass(what, proceed) + } + } + function contCommasep(what, end, info) { + for (var i = 3; i < arguments.length; i++) cx.cc.push(arguments[i]) + return cont(pushlex(end, info), commasep(what, end), poplex) + } + function block(type) { + if (type == '}') return cont() + return pass(statement, block) + } + function maybetype(type, value) { + if (isTS) { + if (type == ':') return cont(typeexpr) + if (value == '?') return cont(maybetype) + } + } + function maybetypeOrIn(type, value) { + if (isTS && (type == ':' || value == 'in')) return cont(typeexpr) + } + function mayberettype(type) { + if (isTS && type == ':') { + if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr) + else return cont(typeexpr) + } + } + function isKW(_, value) { + if (value == 'is') { + cx.marked = 'keyword' + return cont() + } + } + function typeexpr(type, value) { + if (value == 'keyof' || value == 'typeof' || value == 'infer') { + cx.marked = 'keyword' + return cont(value == 'typeof' ? expressionNoComma : typeexpr) + } + if (type == 'variable' || value == 'void') { + cx.marked = 'type' + return cont(afterType) + } + if (value == '|' || value == '&') return cont(typeexpr) + if (type == 'string' || type == 'number' || type == 'atom') return cont(afterType) + if (type == '[') return cont(pushlex(']'), commasep(typeexpr, ']', ','), poplex, afterType) + if (type == '{') return cont(pushlex('}'), commasep(typeprop, '}', ',;'), poplex, afterType) + if (type == '(') return cont(commasep(typearg, ')'), maybeReturnType, afterType) + if (type == '<') return cont(commasep(typeexpr, '>'), typeexpr) + } + function maybeReturnType(type) { + if (type == '=>') return cont(typeexpr) + } + function typeprop(type, value) { + if (type == 'variable' || cx.style == 'keyword') { + cx.marked = 'property' + return cont(typeprop) + } else if (value == '?' || type == 'number' || type == 'string') { + return cont(typeprop) + } else if (type == ':') { + return cont(typeexpr) + } else if (type == '[') { + return cont(expect('variable'), maybetypeOrIn, expect(']'), typeprop) + } else if (type == '(') { + return pass(functiondecl, typeprop) + } + } + function typearg(type, value) { + if ((type == 'variable' && cx.stream.match(/^\s*[?:]/, false)) || value == '?') + return cont(typearg) + if (type == ':') return cont(typeexpr) + if (type == 'spread') return cont(typearg) + return pass(typeexpr) + } + function afterType(type, value) { + if (value == '<') return cont(pushlex('>'), commasep(typeexpr, '>'), poplex, afterType) + if (value == '|' || type == '.' || value == '&') return cont(typeexpr) + if (type == '[') return cont(typeexpr, expect(']'), afterType) + if (value == 'extends' || value == 'implements') { + cx.marked = 'keyword' + return cont(typeexpr) + } + if (value == '?') return cont(typeexpr, expect(':'), typeexpr) + } + function maybeTypeArgs(_, value) { + if (value == '<') return cont(pushlex('>'), commasep(typeexpr, '>'), poplex, afterType) + } + function typeparam() { + return pass(typeexpr, maybeTypeDefault) + } + function maybeTypeDefault(_, value) { + if (value == '=') return cont(typeexpr) + } + function vardef(_, value) { + if (value == 'enum') { + cx.marked = 'keyword' + return cont(enumdef) + } + return pass(pattern, maybetype, maybeAssign, vardefCont) + } + function pattern(type, value) { + if (isTS && isModifier(value)) { + cx.marked = 'keyword' + return cont(pattern) + } + if (type == 'variable') { + register(value) + return cont() + } + if (type == 'spread') return cont(pattern) + if (type == '[') return contCommasep(eltpattern, ']') + if (type == '{') return contCommasep(proppattern, '}') + } + function proppattern(type, value) { + if (type == 'variable' && !cx.stream.match(/^\s*:/, false)) { + register(value) + return cont(maybeAssign) + } + if (type == 'variable') cx.marked = 'property' + if (type == 'spread') return cont(pattern) + if (type == '}') return pass() + if (type == '[') return cont(expression, expect(']'), expect(':'), proppattern) + return cont(expect(':'), pattern, maybeAssign) + } + function eltpattern() { + return pass(pattern, maybeAssign) + } + function maybeAssign(_type, value) { + if (value == '=') return cont(expressionNoComma) + } + function vardefCont(type) { + if (type == ',') return cont(vardef) + } + function maybeelse(type, value) { + if (type == 'keyword b' && value == 'else') + return cont(pushlex('form', 'else'), statement, poplex) + } + function forspec(type, value) { + if (value == 'await') return cont(forspec) + if (type == '(') return cont(pushlex(')'), forspec1, poplex) + } + function forspec1(type) { + if (type == 'var') return cont(vardef, forspec2) + if (type == 'variable') return cont(forspec2) + return pass(forspec2) + } + function forspec2(type, value) { + if (type == ')') return cont() + if (type == ';') return cont(forspec2) + if (value == 'in' || value == 'of') { + cx.marked = 'keyword' + return cont(expression, forspec2) + } + return pass(expression, forspec2) + } + function functiondef(type, value) { + if (value == '*') { + cx.marked = 'keyword' + return cont(functiondef) + } + if (type == 'variable') { + register(value) + return cont(functiondef) + } + if (type == '(') + return cont( + pushcontext, + pushlex(')'), + commasep(funarg, ')'), + poplex, + mayberettype, + statement, + popcontext + ) + if (isTS && value == '<') + return cont(pushlex('>'), commasep(typeparam, '>'), poplex, functiondef) + } + function functiondecl(type, value) { + if (value == '*') { + cx.marked = 'keyword' + return cont(functiondecl) + } + if (type == 'variable') { + register(value) + return cont(functiondecl) + } + if (type == '(') + return cont( + pushcontext, + pushlex(')'), + commasep(funarg, ')'), + poplex, + mayberettype, + popcontext + ) + if (isTS && value == '<') + return cont(pushlex('>'), commasep(typeparam, '>'), poplex, functiondecl) + } + function typename(type, value) { + if (type == 'keyword' || type == 'variable') { + cx.marked = 'type' + return cont(typename) + } else if (value == '<') { + return cont(pushlex('>'), commasep(typeparam, '>'), poplex) + } + } + function funarg(type, value) { + if (value == '@') cont(expression, funarg) + if (type == 'spread') return cont(funarg) + if (isTS && isModifier(value)) { + cx.marked = 'keyword' + return cont(funarg) + } + if (isTS && type == 'this') return cont(maybetype, maybeAssign) + return pass(pattern, maybetype, maybeAssign) + } + function classExpression(type, value) { + // Class expressions may have an optional name. + if (type == 'variable') return className(type, value) + return classNameAfter(type, value) + } + function className(type, value) { + if (type == 'variable') { + register(value) + return cont(classNameAfter) + } + } + function classNameAfter(type, value) { + if (value == '<') return cont(pushlex('>'), commasep(typeparam, '>'), poplex, classNameAfter) + if (value == 'extends' || value == 'implements' || (isTS && type == ',')) { + if (value == 'implements') cx.marked = 'keyword' + return cont(isTS ? typeexpr : expression, classNameAfter) + } + if (type == '{') return cont(pushlex('}'), classBody, poplex) + } + function classBody(type, value) { + if ( + type == 'async' || + (type == 'variable' && + (value == 'static' || value == 'get' || value == 'set' || (isTS && isModifier(value))) && + cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false)) + ) { + cx.marked = 'keyword' + return cont(classBody) + } + if (type == 'variable' || cx.style == 'keyword') { + cx.marked = 'property' + return cont(classfield, classBody) + } + if (type == 'number' || type == 'string') return cont(classfield, classBody) + if (type == '[') return cont(expression, maybetype, expect(']'), classfield, classBody) + if (value == '*') { + cx.marked = 'keyword' + return cont(classBody) + } + if (isTS && type == '(') return pass(functiondecl, classBody) + if (type == ';' || type == ',') return cont(classBody) + if (type == '}') return cont() + if (value == '@') return cont(expression, classBody) + } + function classfield(type, value) { + if (value == '?') return cont(classfield) + if (type == ':') return cont(typeexpr, maybeAssign) + if (value == '=') return cont(expressionNoComma) + var context = cx.state.lexical.prev, + isInterface = context && context.info == 'interface' + return pass(isInterface ? functiondecl : functiondef) + } + function afterExport(type, value) { + if (value == '*') { + cx.marked = 'keyword' + return cont(maybeFrom, expect(';')) + } + if (value == 'default') { + cx.marked = 'keyword' + return cont(expression, expect(';')) + } + if (type == '{') return cont(commasep(exportField, '}'), maybeFrom, expect(';')) + return pass(statement) + } + function exportField(type, value) { + if (value == 'as') { + cx.marked = 'keyword' + return cont(expect('variable')) + } + if (type == 'variable') return pass(expressionNoComma, exportField) + } + function afterImport(type) { + if (type == 'string') return cont() + if (type == '(') return pass(expression) + return pass(importSpec, maybeMoreImports, maybeFrom) + } + function importSpec(type, value) { + if (type == '{') return contCommasep(importSpec, '}') + if (type == 'variable') register(value) + if (value == '*') cx.marked = 'keyword' + return cont(maybeAs) + } + function maybeMoreImports(type) { + if (type == ',') return cont(importSpec, maybeMoreImports) + } + function maybeAs(_type, value) { + if (value == 'as') { + cx.marked = 'keyword' + return cont(importSpec) + } + } + function maybeFrom(_type, value) { + if (value == 'from') { + cx.marked = 'keyword' + return cont(expression) + } + } + function arrayLiteral(type) { + if (type == ']') return cont() + return pass(commasep(expressionNoComma, ']')) + } + function enumdef() { + return pass( + pushlex('form'), + pattern, + expect('{'), + pushlex('}'), + commasep(enummember, '}'), + poplex, + poplex + ) + } + function enummember() { + return pass(pattern, maybeAssign) + } + + function isContinuedStatement(state, textAfter) { + return ( + state.lastType == 'operator' || + state.lastType == ',' || + isOperatorChar.test(textAfter.charAt(0)) || + /[,.]/.test(textAfter.charAt(0)) + ) + } + + function expressionAllowed(stream, state, backUp) { + return ( + (state.tokenize == tokenBase && + /^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test( + state.lastType + )) || + (state.lastType == 'quasi' && + /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0)))) + ) + } + + // Interface + + return { + startState: function (basecolumn) { + var state = { + tokenize: tokenBase, + lastType: 'sof', + cc: [], + lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, 'block', false), + localVars: parserConfig.localVars, + context: parserConfig.localVars && new Context(null, null, false), + indented: basecolumn || 0, + } + if (parserConfig.globalVars && typeof parserConfig.globalVars == 'object') + state.globalVars = parserConfig.globalVars + return state + }, + + token: function (stream, state) { + if (stream.sol()) { + if (!state.lexical.hasOwnProperty('align')) state.lexical.align = false + state.indented = stream.indentation() + findFatArrow(stream, state) + } + if (state.tokenize != tokenComment && stream.eatSpace()) return null + var style = state.tokenize(stream, state) + if (type == 'comment') return style + state.lastType = type == 'operator' && (content == '++' || content == '--') ? 'incdec' : type + return parseJS(state, style, type, content, stream) + }, + + indent: function (state, textAfter) { + if (state.tokenize == tokenComment || state.tokenize == tokenQuasi) return CodeMirror.Pass + if (state.tokenize != tokenBase) return 0 + var firstChar = textAfter && textAfter.charAt(0), + lexical = state.lexical, + top + // Kludge to prevent 'maybelse' from blocking lexical scope pops + if (!/^\s*else\b/.test(textAfter)) + for (var i = state.cc.length - 1; i >= 0; --i) { + var c = state.cc[i] + if (c == poplex) lexical = lexical.prev + else if (c != maybeelse) break + } + while ( + (lexical.type == 'stat' || lexical.type == 'form') && + (firstChar == '}' || + ((top = state.cc[state.cc.length - 1]) && + (top == maybeoperatorComma || top == maybeoperatorNoComma) && + !/^[,\.=+\-*:?[\(]/.test(textAfter))) + ) + lexical = lexical.prev + if (statementIndent && lexical.type == ')' && lexical.prev.type == 'stat') + lexical = lexical.prev + var type = lexical.type, + closing = firstChar == type + + if (type == 'vardef') + return ( + lexical.indented + + (state.lastType == 'operator' || state.lastType == ',' ? lexical.info.length + 1 : 0) + ) + else if (type == 'form' && firstChar == '{') return lexical.indented + else if (type == 'form') return lexical.indented + indentUnit + else if (type == 'stat') + return ( + lexical.indented + + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0) + ) + else if (lexical.info == 'switch' && !closing && parserConfig.doubleIndentSwitch != false) + return ( + lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit) + ) + else if (lexical.align) return lexical.column + (closing ? 0 : 1) + else return lexical.indented + (closing ? 0 : indentUnit) + }, + + electricInput: /^\s*(?:case .*?:|default:|\{|\})$/, + blockCommentStart: jsonMode ? null : '/*', + blockCommentEnd: jsonMode ? null : '*/', + blockCommentContinue: jsonMode ? null : ' * ', + lineComment: jsonMode ? null : '//', + fold: 'brace', + closeBrackets: '()[]{}\'\'""``', + + helperType: jsonMode ? 'json' : 'javascript', + jsonldMode: jsonldMode, + jsonMode: jsonMode, + + expressionAllowed: expressionAllowed, + + skipExpression: function (state) { + var top = state.cc[state.cc.length - 1] + if (top == expression || top == expressionNoComma) state.cc.pop() + }, + } +}) + +CodeMirror.registerHelper('wordChars', 'strudel', /[\w$]/) + +CodeMirror.defineMIME('text/strudel', 'strudel') diff --git a/src/entities/Strudel.ts b/src/entities/Strudel.ts new file mode 100644 index 0000000..76c30c7 --- /dev/null +++ b/src/entities/Strudel.ts @@ -0,0 +1,35 @@ +export class Strudel { + public id: string + public code: string + public filePath: string + public lineFrom: number + public drawContext: CanvasRenderingContext2D | null = null + + public lineFromWhenPlaybackStarts: number | null = null + + constructor(data: { id: string; code: string; filePath: string; lineFrom: number }) { + this.id = data.id + this.code = data.code + this.filePath = data.filePath + this.lineFrom = data.lineFrom + } + + compare(other: Strudel) { + return ( + (this.code === other.code || this.lineFrom === other.lineFrom) && + this.filePath === other.filePath + ) + } + + setDrawContext(ctx: CanvasRenderingContext2D) { + this.drawContext = ctx + } + + playbackStarted() { + this.lineFromWhenPlaybackStarts = this.lineFrom + } + + playbackStopped() { + this.lineFromWhenPlaybackStarts = null + } +} diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..76b8192 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,3 @@ +declare module '@/strudel/init.js' +declare module '@/strudel/codemirror/highlight.mjs' +declare module '@/strudel/draw/index.mjs' diff --git a/src/helpers/vueUtils.ts b/src/helpers/vueUtils.ts new file mode 100644 index 0000000..eb70ca5 --- /dev/null +++ b/src/helpers/vueUtils.ts @@ -0,0 +1,116 @@ +import { nanoid } from 'nanoid' +import { App } from 'obsidian' +import { createPinia } from 'pinia' +import { createApp, defineComponent, Component as VueComponent } from 'vue' + +/** + * Helper class for managing Vue components in Obsidian + */ +export class VueRenderer { + private static instance: VueRenderer + private app: App + private vueApps: Map = new Map() + + private constructor(app: App) { + this.app = app + } + + /** + * Get the singleton instance of VueRenderer + * @param app Obsidian App instance + * @returns VueRenderer instance + */ + public static getInstance(app: App): VueRenderer { + if (!VueRenderer.instance) { + VueRenderer.instance = new VueRenderer(app) + } + return VueRenderer.instance + } + + /** + * Mount a Vue component to a DOM element + * @param container The DOM element to mount the component to + * @param component The Vue component to mount + * @param props Props to pass to the component + * @returns A unique ID for the mounted component + */ + public mountComponent( + container: HTMLElement, + component: VueComponent, + props: Record = {} + ): string { + // Create a unique ID for this component instance + const id = `vue-component-${Date.now()}-${Math.floor(Math.random() * 10000)}` + + // Create a wrapper div for the Vue app + const mountPoint = document.createElement('div') + mountPoint.id = id + container.appendChild(mountPoint) + + const pinia = createPinia() + + // Create a Vue app with the component + const app = createApp(component, props) + + app.use(pinia) + + // Mount the app to the container + app.mount(`#${id}`) + + // Store the app reference for cleanup + this.vueApps.set(id, app) + + return id + } + + /** + * Unmount a Vue component by its ID + * @param id The ID of the component to unmount + */ + public unmountComponent(id: string): void { + const app = this.vueApps.get(id) + if (app) { + app.unmount() + this.vueApps.delete(id) + const mountPoint = document.getElementById(id) + if (mountPoint) { + mountPoint.remove() + } + } + } + + /** + * Create a wrapper for a Vue component that can be used with Obsidian's Component system + * @param component The Vue component to wrap + * @param props Props to pass to the component + * @returns A function that can be used to mount the component + */ + public createComponentWrapper( + component: VueComponent, + props: Record = {} + ): (container: HTMLElement) => string { + return (container: HTMLElement) => { + return this.mountComponent(container, component, props) + } + } +} + +/** + * Create a simple Vue component from a template string + * @param template The Vue template string + * @param options Additional Vue component options + * @returns A Vue component + */ +export function createVueComponent( + template: string, + options: Record = {} +): VueComponent { + return defineComponent({ + template, + ...options, + }) +} + +export function genid(): string { + return `vue-${nanoid()}` +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..7feb896 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,119 @@ +import { MarkdownPostProcessorContext, Plugin } from 'obsidian' +import './styles.css' +import { GlobalStore } from './stores/GlobalStore' +import { createApp, App as VueApp } from 'vue' +import { createPinia } from 'pinia' +import VueEntry from './App.vue' +import { StrudelConfig } from './services/StrudelConfig' +import { strudelStateField } from './editor/StrudelPlugin' +import { highlightExtension } from './editor/StrudelHighlight' +// import { STRUDEL_CODEBLOCK_KEYWORD } from './constants/keywords' +// import { StrudelRenderer } from './editor/StrudelRenderer' +// import { highlightExtension } from '@/strudel/codemirror/highlight.mjs' + +import './editor/SyntaxHighlighting.js' +import { createStrudelBlock } from './commands/createStrudelBlock' + +export interface StrudelSettings {} + +const DEFAULT_SETTINGS: StrudelSettings = {} + +interface PluginData {} + +export default class StrudelPlugin extends Plugin { + settings: StrudelSettings + strudelConfig: StrudelConfig + private data: PluginData = {} + + private vueApp: VueApp | null = null + + initializeVue() { + const rootContainer = document.createElement('div') + rootContainer.id = 'strudel-vue-root' + rootContainer.style.display = 'none' + document.body.appendChild(rootContainer) + + this.vueApp = createApp(VueEntry) + this.vueApp.use(createPinia()) + this.vueApp.mount(rootContainer) + } + + getVueApp(): VueApp { + return this.vueApp! + } + + async onload() { + await this.loadPluginData() + await this.loadSettings() + + GlobalStore.getInstance().init(this.app) + + // this.addSettingTab(new StrudelSettingTab(this.app, this)) + + console.log('Strudel REPL Plugin loaded.') + + this.initializeVue() + + this.registerEditorExtension(strudelStateField) + this.registerEditorExtension(highlightExtension) + + GlobalStore.getInstance().initStrudel() + + // this.addCommand({ + // id: 'create-strudel-block', + // name: 'Create new strudel block', + // callback: () => { + // createStrudelBlock() + // }, + // }) + } + + onunload() { + GlobalStore.getInstance().destroy() + if (this.vueApp) { + this.vueApp.unmount() + document.getElementById('strudel-vue-root')?.remove() + } + console.log('Strudel REPL plugin unloaded.') + } + + applySettings() { + this.strudelConfig = StrudelConfig.getInstance() + // this.strudelConfig.refreshDelay = this.settings.refreshDelay + } + + async loadSettings() { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()) + this.applySettings() + } + + async saveSettings() { + await this.saveData(this.settings) + this.applySettings() + } + + async loadPluginData() { + this.data = (await this.loadData()) || {} + } + + async savePluginData() { + await this.saveData(this.data) + } +} + +// class StrudelSettingTab extends PluginSettingTab { +// plugin: StrudelPlugin +// +// constructor(app: App, plugin: StrudelPlugin) { +// super(app, plugin) +// this.plugin = plugin +// } +// +// display(): void { +// const { containerEl } = this +// +// containerEl.empty() +// +// containerEl.createEl('h2', { text: 'Strudel REPL Settings' }) +// } +// } diff --git a/src/services/StrudelConfig.ts b/src/services/StrudelConfig.ts new file mode 100644 index 0000000..f83359e --- /dev/null +++ b/src/services/StrudelConfig.ts @@ -0,0 +1,13 @@ +export class StrudelConfig { + private static instance: StrudelConfig + + // eslint-disable-next-line @typescript-eslint/no-empty-function + private constructor() {} + + public static getInstance(): StrudelConfig { + if (!StrudelConfig.instance) { + StrudelConfig.instance = new StrudelConfig() + } + return StrudelConfig.instance + } +} diff --git a/src/stores/GlobalStore.ts b/src/stores/GlobalStore.ts new file mode 100644 index 0000000..df4bb61 --- /dev/null +++ b/src/stores/GlobalStore.ts @@ -0,0 +1,189 @@ +import { Strudel } from '@/entities/Strudel' +import { App } from 'obsidian' +import { ref } from 'vue' +import { EditorView } from '@codemirror/view' +import { MarkdownView, WorkspaceLeaf } from 'obsidian' +import { initStrudel as init, recalculateMiniLocations } from '@/strudel/init.js' +import { highlightMiniLocations, updateMiniLocations } from '@/editor/StrudelHighlight' +import { Drawer, drawPianoroll } from '@/strudel/draw/index.mjs' + +export class GlobalStore { + private static instance: GlobalStore + private _app: App + + public repl: any + public readonly strudelInitialized = ref(false) + + public readonly initialized = ref(false) + public readonly currentFile = ref(null) + + public readonly strudelBlocks = ref([]) + + public static getInstance(): GlobalStore { + if (!GlobalStore.instance) { + GlobalStore.instance = new GlobalStore() + } + return GlobalStore.instance + } + + public get app(): App { + return this._app + } + + public getActiveEditor() { + const activeLeaf = GlobalStore.getInstance() + .app.workspace.getLeavesOfType('markdown') + .find( + (leaf: WorkspaceLeaf) => + (leaf.view as MarkdownView).file?.path === this.currentBlock.value?.filePath + ) + + const view = activeLeaf?.view as MarkdownView + + if (activeLeaf && view.editor) { + return (view.editor as any).cm as EditorView + } + } + + public readonly currentBlock = ref(null) + public readonly isPlaying = ref(false) + + public play(strudelBlock: Strudel) { + this.currentBlock.value = strudelBlock + this.repl.evaluate(strudelBlock.code) + this.isPlaying.value = true + } + + public stop() { + this.repl.stop() + this.isPlaying.value = false + } + + public drawer: any | null = null + + // public resetMiniLocations() { + // const editor = this.getActiveEditor() + // if (this.currentBlock.value && editor) { + // updateMiniLocations(this.getActiveEditor(), []) + // } + // } + + // public recalculateMiniLocations() { + // if (this.currentBlock.value) { + // const locations = recalculateMiniLocations(this.currentBlock.value.code) + // updateMiniLocations(this.getActiveEditor(), locations || []) + // } + // } + + public async initStrudel() { + this.repl = await init({ + afterEval: (options: any) => { + // console.log('afterEval', options.meta?.miniLocations) + if (options.meta?.miniLocations) { + const locations = options.meta.miniLocations + // for (const loc of locations) { + // loc[0] = loc[0] + this.currentBlock.lineFrom + // loc[1] = loc[1] + this.currentBlock.lineFrom + // } + const editor = this.getActiveEditor() + if (this.currentBlock.value && editor) { + this.currentBlock.value.playbackStarted() + updateMiniLocations(editor, locations || [], this.currentBlock.value.lineFrom) + } + } + + const drawTime = [-2, 2] + this.drawer.setDrawTime(drawTime) + // invalidate drawer after we've set the appropriate drawTime + this.drawer.invalidate(this.repl.scheduler) + }, + onToggle: (started: boolean) => { + console.log('onToggle', started) + if (started) { + this.currentBlock.value.playbackStarted() + this.drawer.start(this.repl.scheduler) + // if (this.solo) { + // // stop other repls when this one is started + // document.dispatchEvent( + // new CustomEvent('start-repl', { + // detail: this.id, + // }) + // ) + // } + } else { + this.drawer.stop() + + const editor = this.getActiveEditor() + console.log('playback stopped') + if (editor) { + this.currentBlock.value?.playbackStopped() + console.log('clearing mini locations') + updateMiniLocations(editor, [], 0) + } + + this.currentBlock.value = null + } + }, + }) + console.log(this.repl) + this.strudelInitialized.value = true + + this.drawer = new Drawer( + (haps: any, time: any, _: any) => { + const editor = this.getActiveEditor() + if (!editor || !this.currentBlock.value) { + return + } + + // console.log(this.currentBlock.value.lineFrom) + const currentFrame = haps.filter((hap: any) => hap.isActive(time)) + highlightMiniLocations(editor, time, currentFrame) + + const rootStyles = getComputedStyle(document.getElementsByTagName('body')[0]) + const playheadColor = rootStyles.getPropertyValue('--text-accent').trim() + const inactiveColor = rootStyles + .getPropertyValue('--background-modifier-active-hover') + .trim() + const activeColor = rootStyles.getPropertyValue('--text-accent').trim() + + const drawContext = this.currentBlock.value?.drawContext + if (this.currentBlock.value?.drawContext) { + drawPianoroll({ + haps, + time, + ctx: drawContext, + drawTime: [-2, 2], + fold: 1, + // labels: true, + playheadColor, + inactive: inactiveColor, + active: activeColor, + fillActive: true, + strokeActive: false, + }) + } + }, + [-2, 2] + ) + } + + public init(app: App): void { + if (this.initialized.value) { + return + } + + this._app = app + + this.initialized.value = true + } + + public destroy(): void { + if (!this.initialized.value) { + return + } + + this.initialized.value = false + this.currentFile.value = null + this.strudelBlocks.value.splice(0, this.strudelBlocks.value.length) + } +} diff --git a/src/strudel/codemirror/README.md b/src/strudel/codemirror/README.md deleted file mode 100644 index 300e30b..0000000 --- a/src/strudel/codemirror/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @strudel/codemirror - -This package contains helpers and extensions to use codemirror6. See [vite-vanilla-repl-cm6](../core/examples/vite-vanilla-repl-cm6/main.js) as an example of using it. diff --git a/src/strudel/codemirror/autocomplete.mjs b/src/strudel/codemirror/autocomplete.mjs deleted file mode 100644 index b8a569b..0000000 --- a/src/strudel/codemirror/autocomplete.mjs +++ /dev/null @@ -1,460 +0,0 @@ -import jsdoc from '../../doc.json'; -import { autocompletion } from '@codemirror/autocomplete'; -import { h } from './html'; -import { Scale } from '@tonaljs/tonal'; -import { soundMap } from 'superdough'; -import { complex } from '@strudel/tonal'; - -const escapeHtml = (str) => { - const div = document.createElement('div'); - div.innerText = str; - return div.innerHTML; -}; - -const stripHtml = (html) => { - const div = document.createElement('div'); - div.innerHTML = html; - return div.textContent || div.innerText || ''; -}; - -const getDocLabel = (doc) => doc.name || doc.longname; - -const buildParamsList = (params) => - params?.length - ? ` -
-

Parameters

-
    - ${params - .map( - ({ name, type, description }) => ` -
  • - ${name} - ${type.names?.join(' | ')} - ${description ? `
    ${stripHtml(description)}
    ` : ''} -
  • - `, - ) - .join('')} -
-
- ` - : ''; - -const buildExamples = (examples) => - examples?.length - ? ` -
-

Examples

- ${examples - .map( - (example) => ` -
${escapeHtml(example)}
- `, - ) - .join('')} -
- ` - : ''; - -export const Autocomplete = (doc) => - h` -
-
-

${getDocLabel(doc)}

- ${doc.synonyms_text ? `
Synonyms: ${doc.synonyms_text}
` : ''} - ${doc.description ? `
${doc.description}
` : ''} - ${buildParamsList(doc.params)} - ${buildExamples(doc.examples)} -
-
-`[0]; - -const isValidDoc = (doc) => { - const label = getDocLabel(doc); - return label && !label.startsWith('_') && !['package'].includes(doc.kind); -}; - -const hasExcludedTags = (doc) => - ['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)); - -export function bankCompletions() { - const soundDict = soundMap.get(); - const banks = new Set(); - for (const key of Object.keys(soundDict)) { - const [bank, suffix] = key.split('_'); - if (suffix && bank) banks.add(bank); - } - return Array.from(banks) - .sort() - .map((name) => ({ label: name, type: 'bank' })); -} - -// Attempt to get all scale names from Tonal -let scaleCompletions = []; -try { - scaleCompletions = (Scale.names ? Scale.names() : []).map((name) => ({ label: name, type: 'scale' })); -} catch (e) { - console.warn('[autocomplete] Could not load scale names from Tonal:', e); -} - -// Valid mode values for voicing -const modeCompletions = [ - { label: 'below', type: 'mode' }, - { label: 'above', type: 'mode' }, - { label: 'duck', type: 'mode' }, - { label: 'root', type: 'mode' }, -]; - -// Valid chord symbols from ireal dictionary plus empty string for major triads -const chordSymbols = ['', ...Object.keys(complex)].sort(); -const chordSymbolCompletions = chordSymbols.map((symbol) => { - if (symbol === '') { - return { - label: 'major', - apply: '', - type: 'chord-symbol', - }; - } - return { - label: symbol, - apply: symbol, - type: 'chord-symbol', - }; -}); - -export const getSynonymDoc = (doc, synonym) => { - const synonyms = doc.synonyms || []; - const docLabel = getDocLabel(doc); - // Swap `doc.name` in for `s` in the list of synonyms - const synonymsWithDoc = [docLabel, ...synonyms].filter((x) => x && x !== synonym); - return { - ...doc, - name: synonym, - longname: synonym, - synonyms: synonymsWithDoc, - synonyms_text: synonymsWithDoc.join(', '), - }; -}; - -const jsdocCompletions = (() => { - const seen = new Set(); // avoid repetition - const completions = []; - for (const doc of jsdoc.docs) { - if (!isValidDoc(doc) || hasExcludedTags(doc)) continue; - const docLabel = getDocLabel(doc); - // Remove duplicates - const synonyms = doc.synonyms || []; - let labels = [docLabel, ...synonyms]; - for (const label of labels) { - // https://codemirror.net/docs/ref/#autocomplete.Completion - if (label && !seen.has(label)) { - seen.add(label); - completions.push({ - label, - info: () => Autocomplete(getSynonymDoc(doc, label)), - type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type - }); - } - } - } - return completions; -})(); - -// --- Handler functions for each context --- -const pitchNames = [ - 'C', - 'C#', - 'Db', - 'D', - 'D#', - 'Eb', - 'E', - 'E#', - 'Fb', - 'F', - 'F#', - 'Gb', - 'G', - 'G#', - 'Ab', - 'A', - 'A#', - 'Bb', - 'B', - 'B#', - 'Cb', -]; - -// Cached regex patterns for scaleHandler -const SCALE_NO_QUOTES_REGEX = /scale\(\s*$/; -const SCALE_AFTER_COLON_REGEX = /scale\(\s*['"][^'"]*:[^'"]*$/; -const SCALE_PRE_COLON_REGEX = /scale\(\s*['"][^'"]*$/; -const SCALE_PITCH_MATCH_REGEX = /([A-Ga-g][#b]*)?$/; -const SCALE_SPACES_TO_COLON_REGEX = /\s+/g; - -function scaleHandler(context) { - // First check for scale context without quotes - block with empty completions - let scaleNoQuotesContext = context.matchBefore(SCALE_NO_QUOTES_REGEX); - if (scaleNoQuotesContext) { - return { - from: scaleNoQuotesContext.to, - options: [], - }; - } - - // Check for after-colon context first (more specific) - let scaleAfterColonContext = context.matchBefore(SCALE_AFTER_COLON_REGEX); - if (scaleAfterColonContext) { - const text = scaleAfterColonContext.text; - const colonIdx = text.lastIndexOf(':'); - if (colonIdx !== -1) { - const fragment = text.slice(colonIdx + 1); - const filteredScales = scaleCompletions.filter((s) => s.label.startsWith(fragment)); - const options = filteredScales.map((s) => ({ - ...s, - apply: s.label.replace(SCALE_SPACES_TO_COLON_REGEX, ':'), - })); - const from = scaleAfterColonContext.from + colonIdx + 1; - return { - from, - options, - }; - } - } - - // Then check for pre-colon context - let scalePreColonContext = context.matchBefore(SCALE_PRE_COLON_REGEX); - if (scalePreColonContext) { - if (!scalePreColonContext.text.includes(':')) { - if (context.explicit) { - const text = scalePreColonContext.text; - const match = text.match(SCALE_PITCH_MATCH_REGEX); - const fragment = match ? match[0] : ''; - const filtered = pitchNames.filter((p) => p.toLowerCase().startsWith(fragment.toLowerCase())); - const from = scalePreColonContext.to - fragment.length; - const options = filtered.map((p) => ({ label: p, type: 'pitch' })); - return { from, options }; - } else { - return { from: scalePreColonContext.to, options: [] }; - } - } - } - return null; -} - -// Cached regex patterns for soundHandler -const SOUND_NO_QUOTES_REGEX = /(s|sound)\(\s*$/; -const SOUND_WITH_QUOTES_REGEX = /(s|sound)\(\s*['"][^'"]*$/; -const SOUND_FRAGMENT_MATCH_REGEX = /(?:[\s[{(<])([\w]*)$/; - -function soundHandler(context) { - // First check for sound context without quotes - block with empty completions - let soundNoQuotesContext = context.matchBefore(SOUND_NO_QUOTES_REGEX); - if (soundNoQuotesContext) { - return { - from: soundNoQuotesContext.to, - options: [], - }; - } - - // Then check for sound context with quotes - provide completions - let soundContext = context.matchBefore(SOUND_WITH_QUOTES_REGEX); - if (!soundContext) return null; - - const text = soundContext.text; - const quoteIdx = Math.max(text.lastIndexOf('"'), text.lastIndexOf("'")); - if (quoteIdx === -1) return null; - const inside = text.slice(quoteIdx + 1); - const fragMatch = inside.match(SOUND_FRAGMENT_MATCH_REGEX); - const fragment = fragMatch ? fragMatch[1] : inside; - const soundNames = Object.keys(soundMap.get()).sort(); - const filteredSounds = soundNames.filter((name) => name.includes(fragment)); - let options = filteredSounds.map((name) => ({ label: name, type: 'sound' })); - const from = soundContext.to - fragment.length; - return { - from, - options, - }; -} - -// Cached regex patterns for bankHandler -const BANK_NO_QUOTES_REGEX = /bank\(\s*$/; -const BANK_WITH_QUOTES_REGEX = /bank\(\s*['"][^'"]*$/; - -function bankHandler(context) { - // First check for bank context without quotes - block with empty completions - let bankNoQuotesContext = context.matchBefore(BANK_NO_QUOTES_REGEX); - if (bankNoQuotesContext) { - return { - from: bankNoQuotesContext.to, - options: [], - }; - } - - // Then check for bank context with quotes - provide completions - let bankMatch = context.matchBefore(BANK_WITH_QUOTES_REGEX); - if (!bankMatch) return null; - - const text = bankMatch.text; - const quoteIdx = Math.max(text.lastIndexOf('"'), text.lastIndexOf("'")); - if (quoteIdx === -1) return null; - const inside = text.slice(quoteIdx + 1); - const fragment = inside; - let banks = bankCompletions(); - const filteredBanks = banks.filter((b) => b.label.startsWith(fragment)); - const from = bankMatch.to - fragment.length; - return { - from, - options: filteredBanks, - }; -} - -// Cached regex patterns for modeHandler -const MODE_NO_QUOTES_REGEX = /mode\(\s*$/; -const MODE_AFTER_COLON_REGEX = /mode\(\s*['"][^'"]*:[^'"]*$/; -const MODE_PRE_COLON_REGEX = /mode\(\s*['"][^'"]*$/; -const MODE_FRAGMENT_MATCH_REGEX = /(?:[\s[{(<])([\w:]*)$/; - -function modeHandler(context) { - // First check for mode context without quotes - block with empty completions - let modeNoQuotesContext = context.matchBefore(MODE_NO_QUOTES_REGEX); - if (modeNoQuotesContext) { - return { - from: modeNoQuotesContext.to, - options: [], - }; - } - - // Check for after-colon context first (more specific) - let modeAfterColonContext = context.matchBefore(MODE_AFTER_COLON_REGEX); - if (modeAfterColonContext) { - const text = modeAfterColonContext.text; - const colonIdx = text.lastIndexOf(':'); - if (colonIdx !== -1) { - const fragment = text.slice(colonIdx + 1); - // For anchor after colon, we can suggest pitch names - const filtered = pitchNames.filter((p) => p.toLowerCase().startsWith(fragment.toLowerCase())); - const options = filtered.map((p) => ({ label: p, type: 'pitch' })); - const from = modeAfterColonContext.from + colonIdx + 1; - return { - from, - options, - }; - } - } - - // Then check for pre-colon context - let modeContext = context.matchBefore(MODE_PRE_COLON_REGEX); - if (!modeContext) return null; - - const text = modeContext.text; - const quoteIdx = Math.max(text.lastIndexOf('"'), text.lastIndexOf("'")); - if (quoteIdx === -1) return null; - const inside = text.slice(quoteIdx + 1); - const fragMatch = inside.match(MODE_FRAGMENT_MATCH_REGEX); - const fragment = fragMatch ? fragMatch[1] : inside; - const filteredModes = modeCompletions.filter((m) => m.label.startsWith(fragment)); - const from = modeContext.to - fragment.length; - return { - from, - options: filteredModes, - }; -} - -// Cached regex patterns for chordHandler -const CHORD_NO_QUOTES_REGEX = /chord\(\s*$/; -const CHORD_WITH_QUOTES_REGEX = /chord\(\s*['"][^'"]*$/; -const CHORD_FRAGMENT_MATCH_REGEX = /(?:[\s[{(<])([\w#b+^:-]*)$/; - -function chordHandler(context) { - // First check for chord context without quotes - block with empty completions - let chordNoQuotesContext = context.matchBefore(CHORD_NO_QUOTES_REGEX); - if (chordNoQuotesContext) { - return { - from: chordNoQuotesContext.to, - options: [], - }; - } - - // Then check for chord context with quotes - provide completions - let chordContext = context.matchBefore(CHORD_WITH_QUOTES_REGEX); - if (!chordContext) return null; - - const text = chordContext.text; - const quoteIdx = Math.max(text.lastIndexOf('"'), text.lastIndexOf("'")); - if (quoteIdx === -1) return null; - const inside = text.slice(quoteIdx + 1); - - // Use same fragment matching as sound/mode for expressions like "" - const fragMatch = inside.match(CHORD_FRAGMENT_MATCH_REGEX); - const fragment = fragMatch ? fragMatch[1] : inside; - - // Check if fragment contains any pitch name at start (for root + symbol) - let rootMatch = null; - let symbolFragment = fragment; - for (const pitch of pitchNames) { - if (fragment.toLowerCase().startsWith(pitch.toLowerCase())) { - rootMatch = pitch; - symbolFragment = fragment.slice(pitch.length); - break; - } - } - - if (rootMatch) { - // We have a root, now complete chord symbols - const filteredSymbols = chordSymbolCompletions.filter((s) => - s.label.toLowerCase().startsWith(symbolFragment.toLowerCase()), - ); - - // Create completions that replace the entire chord, not just the symbol part - const options = filteredSymbols; - - const from = chordContext.to - symbolFragment.length; - return { from, options }; - } else { - // No root yet, complete with pitch names - const filteredPitches = pitchNames.filter((p) => p.toLowerCase().startsWith(fragment.toLowerCase())); - const options = filteredPitches.map((p) => ({ label: p, type: 'pitch' })); - const from = chordContext.to - fragment.length; - return { from, options }; - } -} - -// Cached regex patterns for fallbackHandler -const FALLBACK_WORD_REGEX = /\w*/; - -function fallbackHandler(context) { - const word = context.matchBefore(FALLBACK_WORD_REGEX); - if (word && word.from === word.to && !context.explicit) return null; - if (word) { - return { - from: word.from, - options: jsdocCompletions, - }; - } - return null; -} - -const handlers = [ - soundHandler, - bankHandler, - chordHandler, - scaleHandler, - modeHandler, - // this handler *must* be last - fallbackHandler, -]; - -export const strudelAutocomplete = (context) => { - for (const handler of handlers) { - const result = handler(context); - if (result) { - return result; - } - } - return null; -}; - -export const isAutoCompletionEnabled = (enabled) => - enabled ? [autocompletion({ override: [strudelAutocomplete], closeOnBlur: false })] : []; diff --git a/src/strudel/codemirror/basicSetup.mjs b/src/strudel/codemirror/basicSetup.mjs deleted file mode 100644 index 02294b9..0000000 --- a/src/strudel/codemirror/basicSetup.mjs +++ /dev/null @@ -1,63 +0,0 @@ -import { - keymap, - highlightSpecialChars, - drawSelection, - highlightActiveLine, - dropCursor, - rectangularSelection, - crosshairCursor, - lineNumbers, - highlightActiveLineGutter, -} from '@codemirror/view'; -import { - defaultHighlightStyle, - syntaxHighlighting, - bracketMatching, - foldGutter, - foldKeymap, -} from '@codemirror/language'; -import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'; -import { searchKeymap, highlightSelectionMatches } from '@codemirror/search'; -import { completionKeymap, closeBracketsKeymap } from '@codemirror/autocomplete'; - -// Taken + slightly modified from https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts - -export const basicSetup = (() => [ - // lineNumbers(), - // highlightActiveLineGutter(), - highlightSpecialChars(), - history(), - // foldGutter(), - // drawSelection(), - dropCursor(), - // EditorState.allowMultipleSelections.of(true), - // indentOnInput(), - // syntaxHighlighting(defaultHighlightStyle, { fallback: true }), - // autocompletion(), - rectangularSelection(), - crosshairCursor(), - // highlightActiveLine(), - // highlightSelectionMatches(), - keymap.of([ - ...closeBracketsKeymap, - ...defaultKeymap, - // ...searchKeymap, - ...historyKeymap, - // ...foldKeymap, - // ...completionKeymap, - ]), -])(); - -/// A minimal set of extensions to create a functional editor. Only -/// includes [the default keymap](#commands.defaultKeymap), [undo -/// history](#commands.history), [special character -/// highlighting](#view.highlightSpecialChars), [custom selection -/// drawing](#view.drawSelection), and [default highlight -/// style](#language.defaultHighlightStyle). -export const minimalSetup = (() => [ - highlightSpecialChars(), - history(), - drawSelection(), - syntaxHighlighting(defaultHighlightStyle, { fallback: true }), - keymap.of([...defaultKeymap, ...historyKeymap]), -])(); diff --git a/src/strudel/codemirror/codemirror.mjs b/src/strudel/codemirror/codemirror.mjs deleted file mode 100644 index 4dc2399..0000000 --- a/src/strudel/codemirror/codemirror.mjs +++ /dev/null @@ -1,386 +0,0 @@ -import { closeBrackets } from '@codemirror/autocomplete'; -export { toggleComment, toggleBlockComment, toggleLineComment, toggleBlockCommentByLine } from '@codemirror/commands'; -// import { search, highlightSelectionMatches } from '@codemirror/search'; -import { indentWithTab } from '@codemirror/commands'; -import { javascript, javascriptLanguage } from '@codemirror/lang-javascript'; -import { defaultHighlightStyle, syntaxHighlighting, bracketMatching } from '@codemirror/language'; -import { Compartment, EditorState, Prec } from '@codemirror/state'; -import { - EditorView, - highlightActiveLineGutter, - highlightActiveLine, - keymap, - lineNumbers, - drawSelection, -} from '@codemirror/view'; -import { repl, registerControl } from '@strudel/core'; -import { Drawer, cleanupDraw } from '@strudel/draw'; -import { isAutoCompletionEnabled } from './autocomplete.mjs'; -import { isTooltipEnabled } from './tooltip.mjs'; -import { flash, isFlashEnabled } from './flash.mjs'; -import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs'; -import { keybindings } from './keybindings.mjs'; -import { initTheme, activateTheme, theme } from './themes.mjs'; -import { sliderPlugin, updateSliderWidgets } from './slider.mjs'; -import { widgetPlugin, updateWidgets } from './widget.mjs'; -import { persistentAtom } from '@nanostores/persistent'; -import { basicSetup } from './basicSetup.mjs'; - -const extensions = { - isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []), - isBracketMatchingEnabled: (on) => (on ? bracketMatching({ brackets: '()[]{}<>' }) : []), - isBracketClosingEnabled: (on) => (on ? closeBrackets() : []), - isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []), - theme, - isAutoCompletionEnabled, - isTooltipEnabled, - isPatternHighlightingEnabled, - isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []), - isFlashEnabled, - keybindings, - isTabIndentationEnabled: (on) => (on ? keymap.of([indentWithTab]) : []), - isMultiCursorEnabled: (on) => - on - ? [ - EditorState.allowMultipleSelections.of(true), - EditorView.clickAddsSelectionRange.of((ev) => ev.metaKey || ev.ctrlKey), - ] - : [], -}; -const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()])); - -export const defaultSettings = { - keybindings: 'codemirror', - isBracketMatchingEnabled: false, - isBracketClosingEnabled: true, - isLineNumbersDisplayed: true, - isActiveLineHighlighted: false, - isAutoCompletionEnabled: false, - isPatternHighlightingEnabled: true, - isFlashEnabled: true, - isTooltipEnabled: false, - isLineWrappingEnabled: false, - isTabIndentationEnabled: false, - isMultiCursorEnabled: false, - theme: 'strudelTheme', - fontFamily: 'monospace', - fontSize: 18, -}; - -export const codemirrorSettings = persistentAtom('codemirror-settings', defaultSettings, { - encode: JSON.stringify, - decode: JSON.parse, -}); - -// https://codemirror.net/docs/guide/ -export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, root, mondo }) { - const settings = codemirrorSettings.get(); - const initialSettings = Object.keys(compartments).map((key) => - compartments[key].of(extensions[key](parseBooleans(settings[key]))), - ); - - initTheme(settings.theme); - let state = EditorState.create({ - doc: initialCode, - extensions: [ - /* search(), - highlightSelectionMatches(), */ - ...initialSettings, - basicSetup, - mondo ? [] : javascript(), - javascriptLanguage.data.of({ - closeBrackets: { brackets: ['(', '[', '{', "'", '"', '<'] }, - bracketMatching: { brackets: ['(', '[', '{', "'", '"', '<'] }, - }), - sliderPlugin, - widgetPlugin, - // indentOnInput(), // works without. already brought with javascript extension? - // bracketMatching(), // does not do anything - syntaxHighlighting(defaultHighlightStyle), - EditorView.updateListener.of((v) => onChange(v)), - drawSelection({ cursorBlinkRate: 0 }), - Prec.highest( - keymap.of([ - { - key: 'Ctrl-Enter', - run: () => onEvaluate?.(), - }, - { - key: 'Alt-Enter', - run: () => onEvaluate?.(), - }, - { - key: 'Ctrl-.', - run: () => onStop?.(), - }, - { - key: 'Alt-.', - preventDefault: true, - run: () => onStop?.(), - }, - /* { - key: 'Ctrl-Shift-.', - run: () => (onPanic ? onPanic() : onStop?.()), - }, - { - key: 'Ctrl-Shift-Enter', - run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()), - }, */ - ]), - ), - ], - }); - - return new EditorView({ - state, - parent: root, - }); -} - -export class StrudelMirror { - constructor(options) { - const { - root, - id, - initialCode = '', - onDraw, - drawContext, - drawTime = [0, 0], - autodraw, - prebake, - bgFill = true, - solo = true, - ...replOptions - } = options; - this.code = initialCode; - this.root = root; - this.miniLocations = []; - this.widgets = []; - this.drawTime = drawTime; - this.drawContext = drawContext; - this.onDraw = onDraw || this.draw; - this.id = id || s4(); - this.solo = solo; - - this.drawer = new Drawer((haps, time, _, painters) => { - const currentFrame = haps.filter((hap) => hap.isActive(time)); - this.highlight(currentFrame, time); - this.onDraw(haps, time, painters); - }, drawTime); - - this.prebaked = prebake(); - autodraw && this.drawFirstFrame(); - this.repl = repl({ - ...replOptions, - id, - onToggle: (started) => { - replOptions?.onToggle?.(started); - if (started) { - this.drawer.start(this.repl.scheduler); - if (this.solo) { - // stop other repls when this one is started - document.dispatchEvent( - new CustomEvent('start-repl', { - detail: this.id, - }), - ); - } - } else { - this.drawer.stop(); - updateMiniLocations(this.editor, []); - cleanupDraw(true, id); - } - }, - beforeEval: async () => { - cleanupDraw(true, id); - await this.prebaked; - await replOptions?.beforeEval?.(); - }, - afterEval: (options) => { - // remember for when highlighting is toggled on - this.miniLocations = options.meta?.miniLocations; - this.widgets = options.meta?.widgets; - const sliders = this.widgets.filter((w) => w.type === 'slider'); - updateSliderWidgets(this.editor, sliders); - const widgets = this.widgets.filter((w) => w.type !== 'slider'); - updateWidgets(this.editor, widgets); - updateMiniLocations(this.editor, this.miniLocations); - replOptions?.afterEval?.(options); - // if no painters are set (.onPaint was not called), then we only need the present moment (for highlighting) - const drawTime = options.pattern.getPainters().length ? this.drawTime : [0, 0]; - this.drawer.setDrawTime(drawTime); - // invalidate drawer after we've set the appropriate drawTime - this.drawer.invalidate(this.repl.scheduler); - }, - }); - this.editor = initEditor({ - root, - initialCode, - onChange: (v) => { - if (v.docChanged) { - this.code = v.state.doc.toString(); - this.repl.setCode?.(this.code); - } - }, - onEvaluate: () => this.evaluate(), - onStop: () => this.stop(), - mondo: replOptions.mondo, - }); - const cmEditor = this.root.querySelector('.cm-editor'); - if (cmEditor) { - this.root.style.display = 'block'; - if (bgFill) { - this.root.style.backgroundColor = 'var(--background)'; - } - cmEditor.style.backgroundColor = 'transparent'; - } - const settings = codemirrorSettings.get(); - this.setFontSize(settings.fontSize); - this.setFontFamily(settings.fontFamily); - - // stop this repl when another repl is started - this.onStartRepl = (e) => { - if (this.solo && e.detail !== this.id) { - this.stop(); - } - }; - document.addEventListener('start-repl', this.onStartRepl); - } - draw(haps, time, painters) { - painters?.forEach((painter) => painter(this.drawContext, time, haps, this.drawTime)); - } - async drawFirstFrame() { - if (!this.onDraw) { - return; - } - // draw first frame instantly - await this.prebaked; - try { - await this.repl.evaluate(this.code, false); - this.drawer.invalidate(this.repl.scheduler, -0.001); - // draw at -0.001 to avoid haps at 0 to be visualized as active - this.onDraw?.(this.drawer.visibleHaps, -0.001, this.drawer.painters); - } catch (err) { - console.warn('first frame could not be painted'); - } - } - async evaluate() { - this.flash(); - await this.repl.evaluate(this.code); - } - async stop() { - this.repl.scheduler.stop(); - } - async toggle() { - if (this.repl.scheduler.started) { - this.repl.stop(); - } else { - this.evaluate(); - } - } - flash(ms) { - flash(this.editor, ms); - } - highlight(haps, time) { - highlightMiniLocations(this.editor, time, haps); - } - setFontSize(size) { - this.root.style.fontSize = size + 'px'; - } - setFontFamily(family) { - this.root.style.fontFamily = family; - const scroller = this.root.querySelector('.cm-scroller'); - if (scroller) { - scroller.style.fontFamily = family; - } - } - reconfigureExtension(key, value) { - if (!extensions[key]) { - console.warn(`extension ${key} is not known`); - return; - } - value = parseBooleans(value); - const newValue = extensions[key](value, this); - this.editor.dispatch({ - effects: compartments[key].reconfigure(newValue), - }); - if (key === 'theme') { - activateTheme(value); - } - } - setLineWrappingEnabled(enabled) { - this.reconfigureExtension('isLineWrappingEnabled', enabled); - } - setBracketMatchingEnabled(enabled) { - this.reconfigureExtension('isBracketMatchingEnabled', enabled); - } - setLineNumbersDisplayed(enabled) { - this.reconfigureExtension('isLineNumbersDisplayed', enabled); - } - setBracketClosingEnabled(enabled) { - this.reconfigureExtension('isBracketClosingEnabled', enabled); - } - setTheme(theme) { - this.reconfigureExtension('theme', theme); - } - setAutocompletionEnabled(enabled) { - this.reconfigureExtension('isAutoCompletionEnabled', enabled); - } - updateSettings(settings) { - this.setFontSize(settings.fontSize); - this.setFontFamily(settings.fontFamily); - for (let key in extensions) { - this.reconfigureExtension(key, settings[key]); - } - const updated = { ...codemirrorSettings.get(), ...settings }; - codemirrorSettings.set(updated); - } - changeSetting(key, value) { - if (extensions[key]) { - this.reconfigureExtension(key, value); - return; - } else if (key === 'fontFamily') { - this.setFontFamily(value); - } else if (key === 'fontSize') { - this.setFontSize(value); - } - } - setCode(code) { - const changes = { from: 0, to: this.editor.state.doc.length, insert: code }; - this.editor.dispatch({ changes }); - } - clear() { - this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl); - } - getCursorLocation() { - return this.editor.state.selection.main.head; - } - setCursorLocation(col) { - return this.editor.dispatch({ selection: { anchor: col } }); - } - appendCode(code) { - const cursor = this.getCursorLocation(); - this.setCode(this.code + code); - this.setCursorLocation(cursor); - } -} - -function parseBooleans(value) { - return { true: true, false: false }[value] ?? value; -} - -// helper function to generate repl ids -function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); -} - -/** - * Overrides the css of highlighted events. Make sure to use single quotes! - * @name markcss - * @example - * note("c a f e") - * .markcss('text-decoration:underline') - */ -export const markcss = registerControl('markcss'); diff --git a/src/strudel/codemirror/flash.mjs b/src/strudel/codemirror/flash.mjs deleted file mode 100644 index 243500b..0000000 --- a/src/strudel/codemirror/flash.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import { StateEffect, StateField } from '@codemirror/state'; -import { Decoration, EditorView } from '@codemirror/view'; - -export const setFlash = StateEffect.define(); -export const flashField = StateField.define({ - create() { - return Decoration.none; - }, - update(flash, tr) { - try { - for (let e of tr.effects) { - if (e.is(setFlash)) { - if (e.value && tr.newDoc.length > 0) { - const mark = Decoration.mark({ - attributes: { style: `background-color: rgba(255,255,255, .4); filter: invert(10%)` }, - }); - flash = Decoration.set([mark.range(0, tr.newDoc.length)]); - } else { - flash = Decoration.set([]); - } - } - } - return flash; - } catch (err) { - console.warn('flash error', err); - return flash; - } - }, - provide: (f) => EditorView.decorations.from(f), -}); - -export const flash = (view, ms = 200) => { - view.dispatch({ effects: setFlash.of(true) }); - setTimeout(() => { - view.dispatch({ effects: setFlash.of(false) }); - }, ms); -}; - -export const isFlashEnabled = (on) => (on ? flashField : []); diff --git a/src/strudel/codemirror/highlight.mjs b/src/strudel/codemirror/highlight.mjs deleted file mode 100644 index f9f977c..0000000 --- a/src/strudel/codemirror/highlight.mjs +++ /dev/null @@ -1,138 +0,0 @@ -import { RangeSetBuilder, StateEffect, StateField, Prec } from '@codemirror/state'; -import { Decoration, EditorView } from '@codemirror/view'; - -export const setMiniLocations = StateEffect.define(); -export const showMiniLocations = StateEffect.define(); -export const updateMiniLocations = (view, locations) => { - view.dispatch({ effects: setMiniLocations.of(locations) }); -}; -export const highlightMiniLocations = (view, atTime, haps) => { - view.dispatch({ effects: showMiniLocations.of({ atTime, haps }) }); -}; - -const miniLocations = StateField.define({ - create() { - return Decoration.none; - }, - update(locations, tr) { - if (tr.docChanged) { - locations = locations.map(tr.changes); - } - - for (let e of tr.effects) { - if (e.is(setMiniLocations)) { - // this is called on eval, with the mini locations obtained from the transpiler - // codemirror will automatically remap the marks when the document is edited - // create a mark for each mini location, adding the range to the spec to find it later - const marks = e.value - .filter(([from]) => from < tr.newDoc.length) - .map(([from, to]) => [from, Math.min(to, tr.newDoc.length)]) - .map( - (range) => - Decoration.mark({ - id: range.join(':'), - // this green is only to verify that the decoration moves when the document is edited - // it will be removed later, so the mark is not visible by default - attributes: { style: `background-color: #00CA2880` }, - }).range(...range), // -> Decoration - ); - - locations = Decoration.set(marks, true); // -> DecorationSet === RangeSet - } - } - - return locations; - }, -}); - -const visibleMiniLocations = StateField.define({ - create() { - return { atTime: 0, haps: new Map() }; - }, - update(visible, tr) { - for (let e of tr.effects) { - if (e.is(showMiniLocations)) { - // this is called every frame to show the locations that are currently active - // we can NOT create new marks because the context.locations haven't changed since eval time - // this is why we need to find a way to update the existing decorations, showing the ones that have an active range - const haps = new Map(); - for (let hap of e.value.haps) { - if (!hap.context?.locations || !hap.whole) { - continue; - } - for (let { start, end } of hap.context.locations) { - let id = `${start}:${end}`; - if (!haps.has(id) || haps.get(id).whole.begin.lt(hap.whole.begin)) { - haps.set(id, hap); - } - } - } - visible = { atTime: e.value.atTime, haps }; - } - } - - return visible; - }, -}); - -// // Derive the set of decorations from the miniLocations and visibleLocations -const miniLocationHighlights = EditorView.decorations.compute([miniLocations, visibleMiniLocations], (state) => { - const iterator = state.field(miniLocations).iter(); - const { haps } = state.field(visibleMiniLocations); - const builder = new RangeSetBuilder(); - - while (iterator.value) { - const { - from, - to, - value: { - spec: { id }, - }, - } = iterator; - - 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'); - swatch.style.color = color; - document.body.appendChild(swatch); - let channels = getComputedStyle(swatch) - .color.match(/^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*(\d*(?:\.\d+)?))?\)$/) - .slice(1) - .map((c) => parseFloat(c || 1)); - document.body.removeChild(swatch); - - // Get percentage of event - const percent = 1 - (atTime - hap.whole.begin) / hap.whole.duration; - channels[3] *= percent; - */ - - builder.add( - from, - to, - Decoration.mark({ - // attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` }, - attributes: { style }, - }), - ); - } - - iterator.next(); - } - - return builder.finish(); -}); - -export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights]; - -export const isPatternHighlightingEnabled = (on, config) => { - on && - config && - setTimeout(() => { - updateMiniLocations(config.editor, config.miniLocations); - }, 100); - return on ? Prec.highest(highlightExtension) : []; -}; diff --git a/src/strudel/codemirror/html.mjs b/src/strudel/codemirror/html.mjs deleted file mode 100644 index f240059..0000000 --- a/src/strudel/codemirror/html.mjs +++ /dev/null @@ -1,18 +0,0 @@ -export let html = (string) => { - const template = document.createElement('template'); - template.innerHTML = string.trim(); - return template.content.childNodes; -}; -let parseChunk = (chunk) => { - if (Array.isArray(chunk)) return chunk.flat().join(''); - if (chunk === undefined) return ''; - return chunk; -}; -export let h = (strings, ...vars) => { - let string = ''; - for (let i in strings) { - string += parseChunk(strings[i]); - string += parseChunk(vars[i]); - } - return html(string); -}; diff --git a/src/strudel/codemirror/index.mjs b/src/strudel/codemirror/index.mjs deleted file mode 100644 index 3a5f2a2..0000000 --- a/src/strudel/codemirror/index.mjs +++ /dev/null @@ -1,6 +0,0 @@ -export * from './codemirror.mjs'; -export * from './highlight.mjs'; -export * from './flash.mjs'; -export * from './slider.mjs'; -export * from './themes.mjs'; -export * from './widget.mjs'; diff --git a/src/strudel/codemirror/keybindings.mjs b/src/strudel/codemirror/keybindings.mjs deleted file mode 100644 index ca5f34f..0000000 --- a/src/strudel/codemirror/keybindings.mjs +++ /dev/null @@ -1,32 +0,0 @@ -import { Prec } from '@codemirror/state'; -import { keymap, ViewPlugin } from '@codemirror/view'; -// import { searchKeymap } from '@codemirror/search'; -import { emacs } from '@replit/codemirror-emacs'; -import { vim } from '@replit/codemirror-vim'; -// import { vim } from './vim_test.mjs'; -import { vscodeKeymap } from '@replit/codemirror-vscode-keymap'; -import { defaultKeymap } from '@codemirror/commands'; - -const vscodePlugin = ViewPlugin.fromClass( - class { - constructor() {} - }, - { - provide: () => { - return Prec.highest(keymap.of([...vscodeKeymap])); - }, - }, -); -const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []); - -const keymaps = { - vim, - emacs, - codemirror: () => keymap.of(defaultKeymap), - vscode: vscodeExtension, -}; - -export function keybindings(name) { - const active = keymaps[name]; - return [active ? Prec.high(active()) : []]; -} diff --git a/src/strudel/codemirror/package.json b/src/strudel/codemirror/package.json deleted file mode 100644 index b4e7d27..0000000 --- a/src/strudel/codemirror/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "@strudel/codemirror", - "version": "1.2.5", - "description": "Codemirror Extensions for Strudel", - "main": "index.mjs", - "publishConfig": { - "main": "dist/index.mjs" - }, - "scripts": { - "build": "vite build", - "prepublishOnly": "npm run build" - }, - "type": "module", - "repository": { - "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git" - }, - "keywords": [ - "tidalcycles", - "strudel", - "pattern", - "livecoding", - "algorave" - ], - "author": "Felix Roos ", - "contributors": [ - "Alex McLean " - ], - "license": "AGPL-3.0-or-later", - "bugs": { - "url": "https://codeberg.org/uzu/strudel/issues" - }, - "homepage": "https://codeberg.org/uzu/strudel#readme", - "dependencies": { - "@codemirror/autocomplete": "^6.18.4", - "@codemirror/commands": "^6.8.0", - "@codemirror/lang-javascript": "^6.2.2", - "@codemirror/language": "^6.10.8", - "@codemirror/search": "^6.5.8", - "@codemirror/state": "^6.5.1", - "@codemirror/view": "^6.36.2", - "@lezer/highlight": "^1.2.1", - "@nanostores/persistent": "^0.10.2", - "@replit/codemirror-emacs": "^6.1.0", - "@replit/codemirror-vim": "^6.3.0", - "@replit/codemirror-vscode-keymap": "^6.0.2", - "@strudel/core": "workspace:*", - "@strudel/draw": "workspace:*", - "@strudel/tonal": "workspace:*", - "@strudel/transpiler": "workspace:*", - "@tonaljs/tonal": "^4.10.0", - "superdough": "workspace:*", - "nanostores": "^0.11.3" - }, - "devDependencies": { - "vite": "^6.0.11" - } -} diff --git a/src/strudel/codemirror/slider.mjs b/src/strudel/codemirror/slider.mjs deleted file mode 100644 index 72f9512..0000000 --- a/src/strudel/codemirror/slider.mjs +++ /dev/null @@ -1,146 +0,0 @@ -import { ref, pure } from '@strudel/core'; -import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view'; -import { StateEffect } from '@codemirror/state'; - -export let sliderValues = {}; -const getSliderID = (from) => `slider_${from}`; - -export class SliderWidget extends WidgetType { - constructor(value, min, max, from, to, step, view) { - super(); - this.value = value; - this.min = min; - this.max = max; - this.from = from; - this.originalFrom = from; - this.to = to; - this.step = step; - this.view = view; - } - - eq() { - return false; - } - - toDOM() { - let wrap = document.createElement('span'); - wrap.setAttribute('aria-hidden', 'true'); - wrap.className = 'cm-slider'; // inline-flex items-center - let slider = wrap.appendChild(document.createElement('input')); - slider.type = 'range'; - slider.min = this.min; - slider.max = this.max; - slider.step = this.step ?? (this.max - this.min) / 1000; - slider.originalValue = this.value; - // to make sure the code stays in sync, let's save the original value - // becuase .value automatically clamps values so it'll desync with the code - slider.value = slider.originalValue; - slider.from = this.from; - slider.originalFrom = this.originalFrom; - slider.to = this.to; - slider.style = 'width:64px;margin-right:4px;transform:translateY(4px)'; - this.slider = slider; - slider.addEventListener('input', (e) => { - const next = e.target.value; - let insert = next; - //let insert = next.toFixed(2); - const to = slider.from + slider.originalValue.length; - let change = { from: slider.from, to, insert }; - slider.originalValue = insert; - slider.value = insert; - this.view.dispatch({ changes: change }); - const id = getSliderID(slider.originalFrom); // matches id generated in transpiler - window.postMessage({ type: 'cm-slider', value: Number(next), id }); - }); - return wrap; - } - - ignoreEvent(e) { - return true; - } -} - -export const setSliderWidgets = StateEffect.define(); - -export const updateSliderWidgets = (view, widgets) => { - view.dispatch({ effects: setSliderWidgets.of(widgets) }); -}; - -function getSliders(widgetConfigs, view) { - return widgetConfigs - .filter((w) => w.type === 'slider') - .map(({ from, to, value, min, max, step }) => { - return Decoration.widget({ - widget: new SliderWidget(value, min, max, from, to, step, view), - side: 0, - }).range(from /* , to */); - }); -} - -export const sliderPlugin = ViewPlugin.fromClass( - class { - decorations; //: DecorationSet - - constructor(view /* : EditorView */) { - this.decorations = Decoration.set([]); - } - - update(update /* : ViewUpdate */) { - update.transactions.forEach((tr) => { - if (tr.docChanged) { - this.decorations = this.decorations.map(tr.changes); - const iterator = this.decorations.iter(); - while (iterator.value) { - // when the widgets are moved, we need to tell the dom node the current position - // this is important because the updateSliderValue function has to work with the dom node - if (iterator.value?.widget?.slider) { - iterator.value.widget.slider.from = iterator.from; - iterator.value.widget.slider.to = iterator.to; - } - iterator.next(); - } - } - for (let e of tr.effects) { - if (e.is(setSliderWidgets)) { - this.decorations = Decoration.set(getSliders(e.value, update.view)); - } - } - }); - } - }, - { - decorations: (v) => v.decorations, - }, -); - -/** - * Displays a slider widget to allow the user manipulate a value - * - * @name slider - * @param {number} value Initial value - * @param {number} min Minimum value - optional, defaults to 0 - * @param {number} max Maximum value - optional, defaults to 1 - * @param {number} step Step size - optional - */ -export let slider = (value) => { - console.warn('slider will only work when the transpiler is used... passing value as is'); - return pure(value); -}; -// function transpiled from slider = (value, min, max) -export let sliderWithID = (id, value, min, max) => { - sliderValues[id] = value; // sync state at eval time (code -> state) - return ref(() => sliderValues[id]); // use state at query time -}; -// update state when sliders are moved -if (typeof window !== 'undefined') { - window.addEventListener('message', (e) => { - if (e.data.type === 'cm-slider') { - if (sliderValues[e.data.id] !== undefined) { - // update state when slider is moved - sliderValues[e.data.id] = e.data.value; - } else { - console.warn(`slider with id "${e.data.id}" is not registered. Only ${Object.keys(sliderValues)}`); - } - } - }); -} diff --git a/src/strudel/codemirror/themes.mjs b/src/strudel/codemirror/themes.mjs deleted file mode 100644 index 1a523b3..0000000 --- a/src/strudel/codemirror/themes.mjs +++ /dev/null @@ -1,218 +0,0 @@ -import strudelTheme, { settings as strudelThemeSettings } from './themes/strudel-theme.mjs'; -import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen.mjs'; -import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen.mjs'; -import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen.mjs'; -import teletext, { settings as teletextSettings } from './themes/teletext.mjs'; -import algoboy, { settings as algoboySettings } from './themes/algoboy.mjs'; -import CutiePi, { settings as CutiePiSettings } from './themes/CutiePi.mjs'; -import sonicPink, { settings as sonicPinkSettings } from './themes/sonic-pink.mjs'; -import redText, { settings as redTextSettings } from './themes/red-text.mjs'; -import greenText, { settings as greenTextSettings } from './themes/green-text.mjs'; -import archBtw, { settings as archBtwSettings } from './themes/archBtw.mjs'; -import fruitDaw, { settings as fruitDawSettings } from './themes/fruitDaw.mjs'; - -import bluescreenlight, { settings as bluescreenlightsettings } from './themes/bluescreenlight.mjs'; - -import androidstudio, { settings as androidstudioSettings } from './themes/androidstudio.mjs'; -import atomone, { settings as atomOneSettings } from './themes/atomone.mjs'; -import aura, { settings as auraSettings } from './themes/aura.mjs'; -import darcula, { settings as darculaSettings } from './themes/darcula.mjs'; -import dracula, { settings as draculaSettings } from './themes/dracula.mjs'; -import duotoneDark, { settings as duotoneDarkSettings } from './themes/duotoneDark.mjs'; -import eclipse, { settings as eclipseSettings } from './themes/eclipse.mjs'; -import githubDark, { settings as githubDarkSettings } from './themes/githubDark.mjs'; -import githubLight, { settings as githubLightSettings } from './themes/githubLight.mjs'; -import gruvboxDark, { settings as gruvboxDarkSettings } from './themes/gruvboxDark.mjs'; -import gruvboxLight, { settings as gruvboxLightSettings } from './themes/gruvboxLight.mjs'; -import materialDark, { settings as materialDarkSettings } from './themes/materialDark.mjs'; -import materialLight, { settings as materialLightSettings } from './themes/materialLight.mjs'; -import nord, { settings as nordSettings } from './themes/nord.mjs'; -import monokai, { settings as monokaiSettings } from './themes/monokai.mjs'; -import solarizedDark, { settings as solarizedDarkSettings } from './themes/solarizedDark.mjs'; -import solarizedLight, { settings as solarizedLightSettings } from './themes/solarizedLight.mjs'; -import sublime, { settings as sublimeSettings } from './themes/sublime.mjs'; -import tokyoNight, { settings as tokyoNightSettings } from './themes/tokyoNight.mjs'; -import tokyoNightStorm, { settings as tokyoNightStormSettings } from './themes/tokioNightStorm.mjs'; -import tokyoNightDay, { settings as tokyoNightDaySettings } from './themes/tokyoNightDay.mjs'; -import vscodeDark, { settings as vscodeDarkSettings } from './themes/vscodeDark.mjs'; -import vscodeLight, { settings as vscodeLightSettings } from './themes/vscodeLight.mjs'; -import xcodeLight, { settings as xcodeLightSettings } from './themes/xcodeLight.mjs'; -import bbedit, { settings as bbeditSettings } from './themes/bbedit.mjs'; -import noctisLilac, { settings as noctisLilacSettings } from './themes/noctisLilac.mjs'; - -import { setTheme } from '@strudel/draw'; -export const themes = { - strudelTheme, - algoboy, - archBtw, - androidstudio, - atomone, - aura, - bbedit, - blackscreen, - bluescreen, - bluescreenlight, - CutiePi, - darcula, - dracula, - duotoneDark, - eclipse, - fruitDaw, - githubDark, - githubLight, - greenText, - gruvboxDark, - gruvboxLight, - sonicPink, - materialDark, - materialLight, - monokai, - noctisLilac, - nord, - redText, - solarizedDark, - solarizedLight, - sublime, - teletext, - tokyoNight, - tokyoNightDay, - tokyoNightStorm, - vscodeDark, - vscodeLight, - whitescreen, - xcodeLight, -}; - -export const settings = { - strudelTheme: strudelThemeSettings, - bluescreen: bluescreenSettings, - bluescreenlight: bluescreenlightsettings, - blackscreen: blackscreenSettings, - whitescreen: whitescreenSettings, - teletext: teletextSettings, - algoboy: algoboySettings, - archBtw: archBtwSettings, - androidstudio: androidstudioSettings, - atomone: atomOneSettings, - aura: auraSettings, - bbedit: bbeditSettings, - darcula: darculaSettings, - dracula: draculaSettings, - duotoneDark: duotoneDarkSettings, - eclipse: eclipseSettings, - CutiePi: CutiePiSettings, - sonicPink: sonicPinkSettings, - fruitDaw: fruitDawSettings, - githubLight: githubLightSettings, - githubDark: githubDarkSettings, - greenText: greenTextSettings, - - gruvboxDark: gruvboxDarkSettings, - gruvboxLight: gruvboxLightSettings, - materialDark: materialDarkSettings, - materialLight: materialLightSettings, - noctisLilac: noctisLilacSettings, - nord: nordSettings, - monokai: monokaiSettings, - redText: redTextSettings, - solarizedLight: solarizedLightSettings, - solarizedDark: solarizedDarkSettings, - sublime: sublimeSettings, - tokyoNight: tokyoNightSettings, - tokyoNightStorm: tokyoNightStormSettings, - vscodeDark: vscodeDarkSettings, - vscodeLight: vscodeLightSettings, - xcodeLight: xcodeLightSettings, - tokyoNightDay: tokyoNightDaySettings, -}; - -function getColors(str) { - const colorRegex = /#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})/g; - const colors = []; - - let match; - while ((match = colorRegex.exec(str)) !== null) { - const color = match[0]; - if (!colors.includes(color)) { - colors.push(color); - } - } - - return colors; -} - -// TODO: remove -export function themeColors(theme) { - return getColors(stringifySafe(theme)); -} - -function getCircularReplacer() { - const seen = new WeakSet(); - return (key, value) => { - if (typeof value === 'object' && value !== null) { - if (seen.has(value)) { - return; - } - seen.add(value); - } - return value; - }; -} - -function stringifySafe(json) { - return JSON.stringify(json, getCircularReplacer()); -} - -export const theme = (theme) => themes[theme] || themes.strudelTheme; - -// css style injection helpers -export function injectStyle(rule) { - const newStyle = document.createElement('style'); - document.head.appendChild(newStyle); - const styleSheet = newStyle.sheet; - const ruleIndex = styleSheet.insertRule(rule, 0); - return () => styleSheet.deleteRule(ruleIndex); -} - -let currentTheme, - resetThemeStyle, - themeStyle, - styleID = 'strudel-theme-vars'; -export function initTheme(theme) { - if (!document.getElementById(styleID)) { - themeStyle = document.createElement('style'); - themeStyle.id = styleID; - document.head.append(themeStyle); - } - activateTheme(theme); -} - -export function activateTheme(name) { - if (currentTheme === name) { - return; - } - currentTheme = name; - if (!settings[name]) { - console.warn('theme', name, 'has no settings.. defaulting to strudelTheme settings'); - } - const themeSettings = settings[name] || settings.strudelTheme; - // set css variables - themeStyle.innerHTML = `:root { - ${Object.entries(themeSettings) - // important to override fallback - .map(([key, value]) => `--${key}: ${value} !important;`) - .join('\n')} - }`; - setTheme(themeSettings); - // tailwind dark mode - if (themeSettings.light) { - document.documentElement.classList.remove('dark'); - } else { - document.documentElement.classList.add('dark'); - } - resetThemeStyle?.(); - resetThemeStyle = undefined; - if (themeSettings.customStyle) { - resetThemeStyle = injectStyle(themeSettings.customStyle); - } -} diff --git a/src/strudel/codemirror/themes/CutiePi.mjs b/src/strudel/codemirror/themes/CutiePi.mjs deleted file mode 100644 index 64b38ea..0000000 --- a/src/strudel/codemirror/themes/CutiePi.mjs +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Cutie Pi - * by Switch Angel - */ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; -const deepPurple = '#5c019a'; -const yellowPink = '#fbeffc'; -const grey = '#272C35'; -const pinkAccent = '#fee1ff'; -const lightGrey = '#465063'; -const bratGreen = '#9acd3f'; -const lighterGrey = '#97a1b7'; -const pink = '#f6a6fd'; - -export const settings = { - background: 'white', - lineBackground: 'transparent', - foreground: deepPurple, - caret: '#797977', - selection: yellowPink, - selectionMatch: '#2B323D', - gutterBackground: grey, - gutterForeground: lightGrey, - gutterBorder: 'transparent', - lineHighlight: pinkAccent, -}; - -export default createTheme({ - theme: 'light', - settings, - styles: [ - { - tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction], - color: deepPurple, - }, - { tag: [t.tagName, t.heading], color: settings.foreground }, - { tag: t.comment, color: lighterGrey }, - { tag: [t.variableName, t.propertyName, t.labelName], color: pink }, - { tag: [t.attributeName, t.number], color: '#d19a66' }, - { tag: t.className, color: grey }, - { tag: t.keyword, color: deepPurple }, - { tag: [t.string, t.regexp, t.special(t.propertyName)], color: bratGreen }, - ], -}); diff --git a/src/strudel/codemirror/themes/algoboy.mjs b/src/strudel/codemirror/themes/algoboy.mjs deleted file mode 100644 index 049ed59..0000000 --- a/src/strudel/codemirror/themes/algoboy.mjs +++ /dev/null @@ -1,61 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -const palettes = { - // https://www.deviantart.com/advancedfan2020/art/Game-Boy-Palette-Set-Color-HEX-Part-09-920495662 - 'Central Florida A': ['#FFF630', '#B3AC22', '#666213', '#191905'], - 'Central Florida B': ['#38CEBA', '#279082', '#16524A', '#061513'], - 'Central Florida C': ['#FF8836', '#B35F26', '#663616', '#190E05'], - 'Central Florida D': ['#E07070', '#9D4E4E', '#5A2D2D', '#160B0B'], - 'Central Florida E': ['#7AA4CB', '#55738E', '#314251', '#0C1014'], - 'Feminine Energy A': ['#DC5686', '#9A415E', '#582536', '#16090D'], - 'Feminine Energy B': ['#D0463C', '#92312A', '#531c18', '#150706'], - 'Feminine Energy C': ['#D86918', '#974A11', '#562A0A', '#160A02'], - 'Feminine Energy D': ['#EFC54F', '#A78A36', '#604F20', '#181408'], - 'Feminine Energy E': ['#866399', '#5e456b', '#36283d', '#0d0a0f'], - 'Sour Watermelon A': ['#993366', '#6B2447', '#3D1429', '#0F050A'], - 'Sour Watermelon B': ['#996666', '#6B4747', '#3D2929', '#0F0A0A'], - 'Sour Watermelon C': ['#999966', '#686B47', '#3d3d29', '#0f0f0A'], - 'Sour Watermelon D': ['#99cc66', '#6b8f47', '#3d5229', '#0f140a'], - 'Sour Watermelon E': ['#99ff66', '#6bb347', '#3d6629', '#0f190a'], - //https://www.deviantart.com/advancedfan2020/art/Game-Boy-Palette-Set-Color-HEX-Part-02-920073260 - 'Peri Peaceful A': ['#909BE9', '#656DA3', '#3A3E5D', '#0e0f17'], - 'Peri Peaceful B': ['#68628d', '#494563', '#2a2738', '#0a0a0e'], // pretty dim - 'Peri Peaceful E': ['#b5a0a9', '#7f7076', '#484044', '#121011'], - 'Hichem Palette B': ['#4fa3a5', '#377273', '#204142', '#081010'], - 'Hichem Palette C': ['#Fe6f9b', '#b24e6d', '#662c3e', '#190b0f'], - 'Hichem Palette D': ['#ffbb5a', '#b3833f', '#664b24', '#191309'], - 'JSR2 A': ['#E0EFC0', '#9da786', '#5a604d', '#161813'], -}; -const palette = palettes['Sour Watermelon B']; -export const settings = { - background: palette[3], - foreground: palette[1], - caret: palette[0], - selection: palette[0], - selectionMatch: palette[1], - lineHighlight: palette[3], - lineBackground: palette[3] + '90', - //lineBackground: 'transparent', - gutterBackground: 'transparent', - gutterForeground: palette[0], - light: false, - // customStyle: '.cm-line { line-height: 1 }', -}; -export default createTheme({ - theme: 'dark', - settings, - styles: [ - { tag: t.comment, color: palette[2] }, - { tag: t.string, color: palette[1] }, - { tag: [t.atom, t.number], color: palette[1] }, - { tag: [t.meta, t.labelName, t.variableName], color: palette[0] }, - { - tag: [t.keyword, t.tagName, t.arithmeticOperator], - color: palette[1], - }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: palette[0] }, - { tag: [t.function(t.variableName), t.propertyName], color: palette[0] }, - { tag: t.atom, color: palette[1] }, - ], -}); diff --git a/src/strudel/codemirror/themes/androidstudio.mjs b/src/strudel/codemirror/themes/androidstudio.mjs deleted file mode 100644 index 77c16c3..0000000 --- a/src/strudel/codemirror/themes/androidstudio.mjs +++ /dev/null @@ -1,43 +0,0 @@ -/* - * androidstudio - */ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - background: '#282b2e', - lineBackground: '#282b2e99', - foreground: '#a9b7c6', - caret: '#00FF00', - selection: '#343739', - selectionMatch: '#343739', - lineHighlight: '#343739', -}; - -export default createTheme({ - theme: 'dark', - settings: { - background: '#282b2e', - foreground: '#a9b7c6', - caret: '#00FF00', - selection: '#4e5254', - selectionMatch: '#4e5254', - gutterForeground: '#cccccc50', - lineHighlight: '#7f85891f', - }, - styles: [ - { tag: t.labelName, color: 'inherit' }, - { tag: [t.keyword, t.deleted, t.className], color: '#a9b7c6' }, - { tag: [t.number, t.literal], color: '#6897bb' }, - //{ tag: [t.link, t.variableName], color: '#629755' }, - { tag: [t.link, t.variableName], color: '#a9b7c6' }, - { tag: [t.comment, t.quote], color: 'grey' }, - { tag: [t.meta, t.documentMeta], color: '#bbb529' }, - //{ tag: [t.string, t.propertyName, t.attributeValue], color: '#6a8759' }, - { tag: [t.propertyName, t.attributeValue], color: '#a9b7c6' }, - { tag: [t.string], color: '#6a8759' }, - { tag: [t.heading, t.typeName], color: '#ffc66d' }, - { tag: [t.attributeName], color: '#a9b7c6' }, - { tag: [t.emphasis], fontStyle: 'italic' }, - ], -}); diff --git a/src/strudel/codemirror/themes/archBtw.mjs b/src/strudel/codemirror/themes/archBtw.mjs deleted file mode 100644 index 8754656..0000000 --- a/src/strudel/codemirror/themes/archBtw.mjs +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Arch Btw - * Modern terminal inspired theme - * made by Jade - */ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -const hex = ['rgb(0, 0, 0)', 'rgb(82, 208, 250)', 'rgba(113, 208, 250, .4)', 'rgba(113, 208, 250, .15)']; - -export const settings = { - background: hex[0], - lineBackground: 'transparent', - foreground: hex[1], - selection: hex[2], - selectionMatch: hex[0], - gutterBackground: hex[0], - gutterForeground: hex[2], - gutterBorder: 'transparent', - lineHighlight: hex[0], -}; - -export default createTheme({ - theme: 'dark', - settings, - styles: [ - { - tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction], - color: hex[1], - }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[1] }, - { tag: [t.comment, t.brace, t.bracket], color: hex[2] }, - { tag: [t.variableName, t.propertyName, t.labelName], color: hex[1] }, - { tag: [t.attributeName, t.number], color: hex[1] }, - { tag: t.keyword, color: hex[1] }, - { tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[1] }, - ], -}); diff --git a/src/strudel/codemirror/themes/atomone.mjs b/src/strudel/codemirror/themes/atomone.mjs deleted file mode 100644 index ab9cd36..0000000 --- a/src/strudel/codemirror/themes/atomone.mjs +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Atom One - * Atom One dark syntax theme - * - * https://github.com/atom/one-dark-syntax - */ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - background: '#272C35', - lineBackground: '#272C3599', - foreground: 'hsl(220, 14%, 71%)', - caret: '#797977', - selection: '#ffffff30', - selectionMatch: '#2B323D', - gutterBackground: '#272C35', - gutterForeground: '#465063', - gutterBorder: 'transparent', - lineHighlight: '#2B323D', -}; - -export default createTheme({ - theme: 'dark', - settings: { - background: '#272C35', - foreground: '#9d9b97', - caret: '#797977', - selection: '#3d4c64', - selectionMatch: '#3d4c64', - gutterBackground: '#272C35', - gutterForeground: '#465063', - gutterBorder: 'transparent', - lineHighlight: '#2e3f5940', - }, - styles: [ - { - tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction], - color: 'hsl(207, 82%, 66%)', - }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: 'hsl( 29, 54%, 61%)' }, - { tag: [t.tagName, t.heading], color: '#e06c75' }, - { tag: t.comment, color: '#54636D' }, - { tag: [t.variableName, t.propertyName, t.labelName], color: 'hsl(220, 14%, 71%)' }, - { tag: [t.attributeName, t.number], color: 'hsl( 29, 54%, 61%)' }, - { tag: t.className, color: 'hsl( 39, 67%, 69%)' }, - { tag: t.keyword, color: 'hsl(286, 60%, 67%)' }, - - { tag: [t.string, t.regexp, t.special(t.propertyName)], color: '#98c379' }, - ], -}); diff --git a/src/strudel/codemirror/themes/aura.mjs b/src/strudel/codemirror/themes/aura.mjs deleted file mode 100644 index b42a583..0000000 --- a/src/strudel/codemirror/themes/aura.mjs +++ /dev/null @@ -1,51 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - background: '#21202e', - lineBackground: '#21202e99', - foreground: '#edecee', - caret: '#a277ff', - selection: '#3d375e7f', - selectionMatch: '#3d375e7f', - gutterBackground: '#21202e', - gutterForeground: '#edecee', - gutterBorder: 'transparent', - lineHighlight: '#a394f033', -}; -export default createTheme({ - theme: 'dark', - settings: { - background: '#21202e', - foreground: '#edecee', - caret: '#a277ff', - selection: '#5a51898f', - selectionMatch: '#5a51898f', - gutterBackground: '#21202e', - gutterForeground: '#edecee', - gutterBorder: 'transparent', - lineHighlight: '#a394f033', - }, - styles: [ - { tag: t.keyword, color: '#a277ff' }, - { tag: [t.name, t.deleted, t.character, t.macroName], color: '#edecee' }, - { tag: [t.propertyName], color: '#ffca85' }, - { tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#61ffca' }, - { tag: [t.function(t.variableName), t.labelName], color: '#ffca85' }, - { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#61ffca' }, - { tag: [t.definition(t.name), t.separator], color: '#edecee' }, - { tag: [t.className], color: '#82e2ff' }, - { tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#61ffca' }, - { tag: [t.typeName], color: '#82e2ff' }, - { tag: [t.operator, t.operatorKeyword], color: '#a277ff' }, - { tag: [t.url, t.escape, t.regexp, t.link], color: '#61ffca' }, - { tag: [t.meta, t.comment], color: '#6d6d6d' }, - { tag: t.strong, fontWeight: 'bold' }, - { tag: t.emphasis, fontStyle: 'italic' }, - { tag: t.link, textDecoration: 'underline' }, - { tag: t.heading, fontWeight: 'bold', color: '#a277ff' }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: '#edecee' }, - { tag: t.invalid, color: '#ff6767' }, - { tag: t.strikethrough, textDecoration: 'line-through' }, - ], -}); diff --git a/src/strudel/codemirror/themes/bbedit.mjs b/src/strudel/codemirror/themes/bbedit.mjs deleted file mode 100644 index 535bf9e..0000000 --- a/src/strudel/codemirror/themes/bbedit.mjs +++ /dev/null @@ -1,46 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - light: true, - background: '#FFFFFF', - lineBackground: '#FFFFFF99', - foreground: '#000000', - caret: '#FBAC52', - selection: '#FFD420', - selectionMatch: '#FFD420', - gutterBackground: '#f5f5f5', - gutterForeground: '#4D4D4C', - gutterBorder: 'transparent', - lineHighlight: '#00000012', -}; - -export default createTheme({ - theme: 'light', - settings: { - background: '#FFFFFF', - foreground: '#000000', - caret: '#FBAC52', - selection: '#FFD420', - selectionMatch: '#FFD420', - gutterBackground: '#f5f5f5', - gutterForeground: '#4D4D4C', - gutterBorder: 'transparent', - lineHighlight: '#00000012', - }, - styles: [ - { tag: [t.meta, t.comment], color: '#804000' }, - { tag: [t.keyword, t.strong], color: '#0000FF' }, - { tag: [t.number], color: '#FF0080' }, - { tag: [t.string], color: '#FF0080' }, - { tag: [t.variableName], color: '#006600' }, - { tag: [t.escape], color: '#33CC33' }, - { tag: [t.tagName], color: '#1C02FF' }, - { tag: [t.heading], color: '#0C07FF' }, - { tag: [t.quote], color: '#000000' }, - { tag: [t.list], color: '#B90690' }, - { tag: [t.documentMeta], color: '#888888' }, - { tag: [t.function(t.variableName)], color: '#0000A2' }, - { tag: [t.definition(t.typeName), t.typeName], color: '#6D79DE' }, - ], -}); diff --git a/src/strudel/codemirror/themes/blackscreen.mjs b/src/strudel/codemirror/themes/blackscreen.mjs deleted file mode 100644 index 2c45df1..0000000 --- a/src/strudel/codemirror/themes/blackscreen.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; -export const settings = { - background: 'black', - foreground: 'white', // whats that? - caret: 'white', - selection: '#ffffff20', - selectionMatch: '#036dd626', - lineHighlight: '#ffffff10', - lineBackground: '#00000050', - gutterBackground: 'transparent', - gutterForeground: '#8a919966', -}; -export default createTheme({ - theme: 'dark', - settings, - styles: [ - { tag: t.labelName, color: 'inherit' }, - { tag: t.keyword, color: 'inherit' }, - { tag: t.operator, color: 'inherit' }, - { tag: t.special(t.variableName), color: 'inherit' }, - { tag: t.typeName, color: 'inherit' }, - { tag: t.atom, color: 'inherit' }, - { tag: t.number, color: 'inherit' }, - { tag: t.definition(t.variableName), color: 'inherit' }, - { tag: t.string, color: 'inherit' }, - { tag: t.special(t.string), color: 'inherit' }, - { tag: t.comment, color: 'inherit' }, - { tag: t.variableName, color: 'inherit' }, - { tag: t.tagName, color: 'inherit' }, - { tag: t.bracket, color: 'inherit' }, - { tag: t.meta, color: 'inherit' }, - { tag: t.attributeName, color: 'inherit' }, - { tag: t.propertyName, color: 'inherit' }, - { tag: t.className, color: 'inherit' }, - { tag: t.invalid, color: 'inherit' }, - { tag: [t.unit, t.punctuation], color: 'inherit' }, - ], -}); diff --git a/src/strudel/codemirror/themes/bluescreen.mjs b/src/strudel/codemirror/themes/bluescreen.mjs deleted file mode 100644 index 97d165c..0000000 --- a/src/strudel/codemirror/themes/bluescreen.mjs +++ /dev/null @@ -1,42 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; -export const settings = { - background: '#051DB5', - lineBackground: '#051DB550', - foreground: 'white', // whats that? - caret: 'white', - selection: 'rgba(128, 203, 196, 0.5)', - selectionMatch: '#036dd626', - // lineHighlight: '#8a91991a', // original - lineHighlight: '#00000050', - gutterBackground: 'transparent', - // gutterForeground: '#8a919966', - gutterForeground: '#8a919966', -}; - -export default createTheme({ - theme: 'dark', - settings, - styles: [ - { tag: t.labelName, color: 'inherit' }, - { tag: t.keyword, color: 'inherit' }, - { tag: t.operator, color: 'inherit' }, - { tag: t.special(t.variableName), color: 'inherit' }, - { tag: t.typeName, color: 'inherit' }, - { tag: t.atom, color: 'inherit' }, - { tag: t.number, color: 'inherit' }, - { tag: t.definition(t.variableName), color: 'inherit' }, - { tag: t.string, color: 'inherit' }, - { tag: t.special(t.string), color: 'inherit' }, - { tag: t.comment, color: 'inherit' }, - { tag: t.variableName, color: 'inherit' }, - { tag: t.tagName, color: 'inherit' }, - { tag: t.bracket, color: 'inherit' }, - { tag: t.meta, color: 'inherit' }, - { tag: t.attributeName, color: 'inherit' }, - { tag: t.propertyName, color: 'inherit' }, - { tag: t.className, color: 'inherit' }, - { tag: t.invalid, color: 'inherit' }, - { tag: [t.unit, t.punctuation], color: 'inherit' }, - ], -}); diff --git a/src/strudel/codemirror/themes/bluescreenlight.mjs b/src/strudel/codemirror/themes/bluescreenlight.mjs deleted file mode 100644 index 031a3da..0000000 --- a/src/strudel/codemirror/themes/bluescreenlight.mjs +++ /dev/null @@ -1,37 +0,0 @@ -/* - * A lighter blue screen theme - * made by Jade - */ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -const hex = ['rgb(75, 130, 247)', 'rgb(47, 108, 246)', 'rgb(255, 255, 255)', 'rgba(255, 255, 255,.3)']; - -export const settings = { - background: hex[0], - lineBackground: 'transparent', - foreground: hex[2], - selection: hex[3], - selectionMatch: hex[0], - gutterBackground: hex[0], - gutterForeground: hex[2], - gutterBorder: 'transparent', - lineHighlight: hex[1], -}; - -export default createTheme({ - theme: 'dark', - settings, - styles: [ - { - tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction], - color: hex[2], - }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[2] }, - { tag: [t.comment, t.bracket, t.brace, t.compareOperator], color: hex[3] }, - { tag: [t.variableName, t.propertyName, t.labelName], color: hex[2] }, - { tag: [t.attributeName, t.number], color: hex[2] }, - { tag: t.keyword, color: hex[2] }, - { tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[2] }, - ], -}); diff --git a/src/strudel/codemirror/themes/darcula.mjs b/src/strudel/codemirror/themes/darcula.mjs deleted file mode 100644 index ead66e6..0000000 --- a/src/strudel/codemirror/themes/darcula.mjs +++ /dev/null @@ -1,47 +0,0 @@ -/* - * darcula - * Name: IntelliJ IDEA darcula theme - * From IntelliJ IDEA by JetBrains - */ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; -export const settings = { - background: '#242424', - lineBackground: '#24242499', - foreground: '#f8f8f2', - caret: '#FFFFFF', - selection: 'rgba(255, 255, 255, 0.1)', - selectionMatch: 'rgba(255, 255, 255, 0.2)', - gutterBackground: 'rgba(255, 255, 255, 0.1)', - gutterForeground: '#999', - gutterBorder: 'transparent', - lineHighlight: 'rgba(255, 255, 255, 0.1)', -}; - -export default createTheme({ - theme: 'dark', - settings: { - background: '#242424', - foreground: '#f8f8f2', - caret: '#FFFFFF', - selection: 'rgba(255, 255, 255, 0.1)', - selectionMatch: 'rgba(255, 255, 255, 0.2)', - gutterBackground: 'transparent', - gutterForeground: '#999', - gutterBorder: 'transparent', - lineHighlight: 'rgba(255, 255, 255, 0.1)', - }, - styles: [ - { tag: t.labelName, color: '#CCCCCC' }, - { tag: [t.atom, t.number], color: '#7A9EC2' }, - { tag: [t.comment], color: '#707070' }, - { tag: [t.string], color: '#6A8759' }, - { tag: [t.variableName, t.operator], color: '#CCCCCC' }, - { tag: [t.function(t.variableName), t.propertyName], color: '#FFC66D' }, - { tag: [t.meta, t.className], color: '#FFC66D' }, - { tag: [t.propertyName], color: '#FFC66D' }, - { tag: [t.keyword], color: '#CC7832' }, - { tag: [t.tagName], color: '#ff79c6' }, - { tag: [t.typeName], color: '#ffb86c' }, - ], -}); diff --git a/src/strudel/codemirror/themes/dracula.mjs b/src/strudel/codemirror/themes/dracula.mjs deleted file mode 100644 index bdd9617..0000000 --- a/src/strudel/codemirror/themes/dracula.mjs +++ /dev/null @@ -1,50 +0,0 @@ -/* - * @name dracula - * Michael Kaminsky (http://github.com/mkaminsky11) - * Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme) - */ -// this is different from https://thememirror.net/dracula -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - background: '#282a36', - lineBackground: '#282a3699', - foreground: '#f8f8f2', - caret: '#f8f8f0', - selection: 'rgba(255, 255, 255, 0.1)', - selectionMatch: 'rgba(255, 255, 255, 0.2)', - gutterBackground: '#282a36', - gutterForeground: '#6272a4', - gutterBorder: 'transparent', - lineHighlight: 'rgba(255, 255, 255, 0.1)', -}; - -const purple = '#BD93F9'; - -export default createTheme({ - theme: 'dark', - settings: { - background: '#282a36', - foreground: '#f8f8f2', - caret: '#f8f8f0', - selection: 'rgba(255, 255, 255, 0.1)', - selectionMatch: 'rgba(255, 255, 255, 0.2)', - gutterBackground: '#282a36', - gutterForeground: '#6272a4', - gutterBorder: 'transparent', - lineHighlight: 'rgba(255, 255, 255, 0.1)', - }, - styles: [ - { tag: t.comment, color: '#6272a4' }, - { tag: t.string, color: '#f1fa8c' }, - { tag: [t.atom, t.number], color: purple }, - { tag: [t.meta, t.labelName, t.variableName], color: '#f8f8f2' }, - { - tag: [t.keyword, t.tagName, t.arithmeticOperator], - color: '#ff79c6', - }, - { tag: [t.function(t.variableName), t.propertyName], color: '#50fa7b' }, - { tag: t.atom, color: '#bd93f9' }, - ], -}); diff --git a/src/strudel/codemirror/themes/duotoneDark.mjs b/src/strudel/codemirror/themes/duotoneDark.mjs deleted file mode 100644 index f54973e..0000000 --- a/src/strudel/codemirror/themes/duotoneDark.mjs +++ /dev/null @@ -1,42 +0,0 @@ -/* - * duotone - * author Bram de Haan - * by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes) - */ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - background: '#2a2734', - lineBackground: '#2a273499', - foreground: '#eeebff', - caret: '#ffad5c', - selection: 'rgba(255, 255, 255, 0.1)', - gutterBackground: '#2a2734', - gutterForeground: '#545167', - lineHighlight: '#36334280', -}; - -export default createTheme({ - theme: 'dark', - settings: { - background: '#2a2734', - foreground: '#6c6783', - caret: '#ffad5c', - selection: '#9a86fd', - selectionMatch: '#9a86fd', - gutterBackground: '#2a2734', - gutterForeground: '#545167', - lineHighlight: '#36334280', - }, - styles: [ - { tag: [t.comment, t.bracket, t.operator], color: '#6c6783' }, - { tag: [t.atom, t.number, t.keyword, t.link, t.attributeName, t.quote], color: '#ffcc99' }, - { tag: [t.emphasis, t.heading, t.tagName, t.propertyName, t.className, t.variableName], color: '#eeebff' }, - { tag: [t.typeName, t.url], color: '#eeebff' }, - { tag: t.string, color: '#ffb870' }, - /* { tag: [t.propertyName], color: '#9a86fd' }, */ - { tag: [t.propertyName], color: '#eeebff' }, - { tag: t.labelName, color: '#eeebff' }, - ], -}); diff --git a/src/strudel/codemirror/themes/duotoneLight.mjs b/src/strudel/codemirror/themes/duotoneLight.mjs deleted file mode 100644 index 81aa348..0000000 --- a/src/strudel/codemirror/themes/duotoneLight.mjs +++ /dev/null @@ -1,45 +0,0 @@ -/* - * duotone - * author Bram de Haan - * by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes) - */ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - light: true, - background: '#faf8f5', - lineBackground: '#faf8f599', - foreground: '#b29762', - caret: '#93abdc', - selection: '#e3dcce', - selectionMatch: '#e3dcce', - gutterBackground: '#faf8f5', - gutterForeground: '#cdc4b1', - gutterBorder: 'transparent', - lineHighlight: '#EFEFEF', -}; - -export default createTheme({ - theme: 'light', - settings: { - background: '#faf8f5', - foreground: '#b29762', - caret: '#93abdc', - selection: '#e3dcce', - selectionMatch: '#e3dcce', - gutterBackground: '#faf8f5', - gutterForeground: '#cdc4b1', - gutterBorder: 'transparent', - lineHighlight: '#ddceb154', - }, - styles: [ - { tag: [t.comment, t.bracket], color: '#b6ad9a' }, - { tag: [t.atom, t.number, t.keyword, t.link, t.attributeName, t.quote], color: '#063289' }, - { tag: [t.emphasis, t.heading, t.tagName, t.propertyName, t.variableName], color: '#2d2006' }, - { tag: [t.typeName, t.url, t.string], color: '#896724' }, - { tag: [t.operator, t.string], color: '#1659df' }, - { tag: [t.propertyName], color: '#b29762' }, - { tag: [t.unit, t.punctuation], color: '#063289' }, - ], -}); diff --git a/src/strudel/codemirror/themes/eclipse.mjs b/src/strudel/codemirror/themes/eclipse.mjs deleted file mode 100644 index 8082b59..0000000 --- a/src/strudel/codemirror/themes/eclipse.mjs +++ /dev/null @@ -1,46 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - light: true, - background: '#fff', - lineBackground: '#ffffff99', - foreground: '#000', - caret: '#FFFFFF', - selection: '#d7d4f0', - selectionMatch: '#d7d4f0', - gutterBackground: '#f7f7f7', - gutterForeground: '#999', - lineHighlight: '#e8f2ff', - gutterBorder: 'transparent', -}; - -export default createTheme({ - theme: 'light', - settings: { - background: '#fff', - foreground: '#000', - caret: '#FFFFFF', - selection: '#d7d4f0', - selectionMatch: '#d7d4f0', - gutterBackground: '#f7f7f7', - gutterForeground: '#999', - lineHighlight: '#006fff1c', - gutterBorder: 'transparent', - }, - styles: [ - { tag: [t.comment], color: '#3F7F5F' }, - { tag: [t.documentMeta], color: '#FF1717' }, - { tag: t.keyword, color: '#7F0055', fontWeight: 'bold' }, - { tag: t.atom, color: '#00f' }, - { tag: t.number, color: '#164' }, - { tag: t.propertyName, color: '#164' }, - { tag: [t.variableName, t.definition(t.variableName)], color: '#0000C0' }, - { tag: t.function(t.variableName), color: '#0000C0' }, - { tag: t.string, color: '#2A00FF' }, - { tag: t.operator, color: 'black' }, - { tag: t.tagName, color: '#170' }, - { tag: t.attributeName, color: '#00c' }, - { tag: t.link, color: '#219' }, - ], -}); diff --git a/src/strudel/codemirror/themes/fruitDaw.mjs b/src/strudel/codemirror/themes/fruitDaw.mjs deleted file mode 100644 index c9e5577..0000000 --- a/src/strudel/codemirror/themes/fruitDaw.mjs +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Fruit Daw - * made by Jade - */ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -const hex = [ - 'rgb(84, 93, 98)', - 'rgb(255, 255, 255)', - 'rgba(255, 255, 255, .25)', - 'rgb(67, 76, 81)', - 'rgb(186, 230, 115)', - 'rgb(252, 184, 67)', - 'rgb(124, 206, 254)', - 'rgb(83, 101, 102)', - 'rgba(46, 62, 72,.5)', - 'rgb(94, 100, 108)', - 'rgb(167, 216, 177)', -]; - -export const settings = { - background: hex[0], - lineBackground: 'transparent', - foreground: hex[10], - selection: hex[8], - selectionMatch: hex[0], - gutterBackground: hex[3], - gutterForeground: hex[2], - gutterBorder: 'transparent', - lineHighlight: hex[3], -}; - -export default createTheme({ - theme: 'dark', - settings, - styles: [ - { - tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction], - color: hex[1], - }, - { tag: [t.bool, t.special(t.variableName)], color: hex[1] }, - { tag: [t.comment, t.brace, t.bracket], color: hex[2] }, - { tag: [t.variableName], color: hex[1] }, - { tag: [t.labelName, t.propertyName, t.self, t.atom], color: hex[5] }, - { tag: [t.attributeName, t.number], color: hex[6] }, - { tag: t.keyword, color: hex[5] }, - { tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[4] }, - ], -}); diff --git a/src/strudel/codemirror/themes/githubDark.mjs b/src/strudel/codemirror/themes/githubDark.mjs deleted file mode 100644 index 477e7d7..0000000 --- a/src/strudel/codemirror/themes/githubDark.mjs +++ /dev/null @@ -1,42 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - background: '#0d1117', - lineBackground: '#0d111799', - foreground: '#c9d1d9', - caret: '#c9d1d9', - selection: '#003d73', - selectionMatch: '#003d73', - lineHighlight: '#36334280', -}; - -export default createTheme({ - theme: 'dark', - settings: { - background: '#0d1117', - foreground: '#c9d1d9', - caret: '#c9d1d9', - selection: '#003d73', - selectionMatch: '#003d73', - lineHighlight: '#36334280', - }, - styles: [ - { tag: t.labelName, color: '#d2a8ff' }, - { tag: [t.standard(t.tagName), t.tagName], color: '#7ee787' }, - { tag: [t.comment, t.bracket], color: '#8b949e' }, - { tag: [t.className, t.propertyName], color: '#d2a8ff' }, - { tag: [t.variableName, t.attributeName], color: '#d2a8ff' }, - { tag: [t.number, t.operator], color: '#79c0ff' }, - { tag: [t.keyword, t.typeName, t.typeOperator, t.typeName], color: '#ff7b72' }, - { tag: [t.string, t.meta, t.regexp], color: '#a5d6ff' }, - { tag: [t.name, t.quote], color: '#7ee787' }, - { tag: [t.heading, t.strong], color: '#d2a8ff', fontWeight: 'bold' }, - { tag: [t.emphasis], color: '#d2a8ff', fontStyle: 'italic' }, - { tag: [t.deleted], color: '#ffdcd7', backgroundColor: 'ffeef0' }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: '#ffab70' }, - { tag: t.link, textDecoration: 'underline' }, - { tag: t.strikethrough, textDecoration: 'line-through' }, - { tag: t.invalid, color: '#f97583' }, - ], -}); diff --git a/src/strudel/codemirror/themes/githubLight.mjs b/src/strudel/codemirror/themes/githubLight.mjs deleted file mode 100644 index 2ccec93..0000000 --- a/src/strudel/codemirror/themes/githubLight.mjs +++ /dev/null @@ -1,42 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - light: true, - background: '#fff', - lineBackground: '#ffffff99', - foreground: '#24292e', - selection: '#BBDFFF', - selectionMatch: '#BBDFFF', - gutterBackground: '#fff', - gutterForeground: '#6e7781', -}; - -export default createTheme({ - theme: 'light', - settings: { - background: '#fff', - foreground: '#24292e', - selection: '#BBDFFF', - selectionMatch: '#BBDFFF', - gutterBackground: '#fff', - gutterForeground: '#6e7781', - }, - styles: [ - { tag: [t.standard(t.tagName), t.tagName], color: '#116329' }, - { tag: [t.comment, t.bracket], color: '#6a737d' }, - { tag: [t.className, t.propertyName], color: '#6f42c1' }, - { tag: [t.variableName, t.attributeName, t.number, t.operator], color: '#005cc5' }, - { tag: [t.keyword, t.typeName, t.typeOperator, t.typeName], color: '#d73a49' }, - { tag: [t.string, t.meta, t.regexp], color: '#032f62' }, - { tag: [t.name, t.quote], color: '#22863a' }, - { tag: [t.heading, t.strong], color: '#24292e', fontWeight: 'bold' }, - { tag: [t.emphasis], color: '#24292e', fontStyle: 'italic' }, - { tag: [t.deleted], color: '#b31d28', backgroundColor: 'ffeef0' }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: '#e36209' }, - { tag: [t.url, t.escape, t.regexp, t.link], color: '#032f62' }, - { tag: t.link, textDecoration: 'underline' }, - { tag: t.strikethrough, textDecoration: 'line-through' }, - { tag: t.invalid, color: '#cb2431' }, - ], -}); diff --git a/src/strudel/codemirror/themes/green-text.mjs b/src/strudel/codemirror/themes/green-text.mjs deleted file mode 100644 index 08c7a4b..0000000 --- a/src/strudel/codemirror/themes/green-text.mjs +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Atom One - * Atom One dark syntax theme - * - * https://github.com/atom/one-dark-syntax - */ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -const hex = ['#000000', '#8ed675', '#56bd2a', '#54636D', '#171717']; - -export const settings = { - background: hex[0], - lineBackground: 'transparent', - foreground: hex[2], - selection: hex[4], - selectionMatch: hex[0], - gutterBackground: hex[0], - gutterForeground: hex[3], - gutterBorder: 'transparent', - lineHighlight: hex[0], -}; - -export default createTheme({ - theme: 'dark', - settings, - styles: [ - { - tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction], - color: hex[2], - }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[1] }, - { tag: t.comment, color: hex[3] }, - { tag: [t.variableName, t.propertyName, t.labelName], color: hex[2] }, - { tag: [t.attributeName, t.number], color: hex[1] }, - { tag: t.keyword, color: hex[2] }, - { tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[1] }, - ], -}); diff --git a/src/strudel/codemirror/themes/gruvboxDark.mjs b/src/strudel/codemirror/themes/gruvboxDark.mjs deleted file mode 100644 index 67d8bff..0000000 --- a/src/strudel/codemirror/themes/gruvboxDark.mjs +++ /dev/null @@ -1,82 +0,0 @@ -/* - * gruvbox-dark - * author morhetz - * From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-dark.css - */ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - background: '#282828', - lineBackground: '#28282899', - foreground: '#ebdbb2', - caret: '#ebdbb2', - selection: '#bdae93', - selectionMatch: '#bdae93', - lineHighlight: '#3c3836', - gutterBackground: '#282828', - gutterForeground: '#7c6f64', -}; - -export default createTheme({ - theme: 'dark', - settings: { - background: '#282828', - foreground: '#ebdbb2', - caret: '#ebdbb2', - selection: '#b99d555c', - selectionMatch: '#b99d555c', - lineHighlight: '#baa1602b', - gutterBackground: '#282828', - gutterForeground: '#7c6f64', - }, - styles: [ - { tag: t.keyword, color: '#fb4934' }, - { tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: '#8ec07c' }, - { tag: [t.variableName], color: '#83a598' }, - { tag: [t.function(t.variableName)], color: '#8ec07c', fontStyle: 'bold' }, - { tag: [t.labelName], color: '#ebdbb2' }, - { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#d3869b' }, - { tag: [t.definition(t.name), t.separator], color: '#ebdbb2' }, - { tag: [t.brace], color: '#ebdbb2' }, - { tag: [t.annotation], color: '#fb4934d' }, - { tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#d3869b' }, - { tag: [t.typeName, t.className], color: '#fabd2f' }, - { tag: [t.operatorKeyword], color: '#fb4934' }, - { - tag: [t.tagName], - color: '#8ec07c', - fontStyle: 'bold', - }, - { tag: [t.squareBracket], color: '#fe8019' }, - { tag: [t.angleBracket], color: '#83a598' }, - { tag: [t.attributeName], color: '#8ec07c' }, - { tag: [t.regexp], color: '#8ec07c' }, - { tag: [t.quote], color: '#928374' }, - { tag: [t.string], color: '#ebdbb2' }, - { - tag: t.link, - color: '#a89984', - textDecoration: 'underline', - textUnderlinePosition: 'under', - }, - { tag: [t.url, t.escape, t.special(t.string)], color: '#d3869b' }, - { tag: [t.meta], color: '#fabd2f' }, - { tag: [t.comment], color: '#928374', fontStyle: 'italic' }, - { tag: t.strong, fontWeight: 'bold', color: '#fe8019' }, - { tag: t.emphasis, fontStyle: 'italic', color: '#b8bb26' }, - { tag: t.strikethrough, textDecoration: 'line-through' }, - { tag: t.heading, fontWeight: 'bold', color: '#b8bb26' }, - { tag: [t.heading1, t.heading2], fontWeight: 'bold', color: '#b8bb26' }, - { - tag: [t.heading3, t.heading4], - fontWeight: 'bold', - color: '#fabd2f', - }, - { tag: [t.heading5, t.heading6], color: '#fabd2f' }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: '#d3869b' }, - { tag: [t.processingInstruction, t.inserted], color: '#83a598' }, - { tag: [t.contentSeparator], color: '#fb4934' }, - { tag: t.invalid, color: '#fe8019', borderBottom: `1px dotted #fb4934d` }, - ], -}); diff --git a/src/strudel/codemirror/themes/gruvboxLight.mjs b/src/strudel/codemirror/themes/gruvboxLight.mjs deleted file mode 100644 index 5d4b4ec..0000000 --- a/src/strudel/codemirror/themes/gruvboxLight.mjs +++ /dev/null @@ -1,130 +0,0 @@ -/* - * gruvbox-light - * author morhetz - * From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-light.css - */ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - light: true, - background: '#fbf1c7', - lineBackground: '#fbf1c799', - foreground: '#3c3836', - caret: '#af3a03', - selection: '#ebdbb2', - selectionMatch: '#bdae93', - lineHighlight: '#ebdbb2', - gutterBackground: '#ebdbb2', - gutterForeground: '#665c54', - gutterBorder: 'transparent', -}; - -export default createTheme({ - theme: 'light', - settings: { - background: '#fbf1c7', - foreground: '#3c3836', - caret: '#af3a03', - selection: '#bdae9391', - selectionMatch: '#bdae9391', - lineHighlight: '#a37f2238', - gutterBackground: '#ebdbb2', - gutterForeground: '#665c54', - gutterBorder: 'transparent', - }, - styles: [ - { tag: t.keyword, color: '#9d0006' }, - { - tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], - color: '#427b58', - }, - { tag: [t.variableName], color: '#076678' }, - { tag: [t.function(t.variableName)], color: '#79740e', fontStyle: 'bold' }, - { tag: [t.labelName], color: '#3c3836' }, - { - tag: [t.color, t.constant(t.name), t.standard(t.name)], - color: '#8f3f71', - }, - { tag: [t.definition(t.name), t.separator], color: '#3c3836' }, - { tag: [t.brace], color: '#3c3836' }, - { - tag: [t.annotation], - color: '#9d0006', - }, - { - tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], - color: '#8f3f71', - }, - { - tag: [t.typeName, t.className], - color: '#b57614', - }, - { - tag: [t.operator, t.operatorKeyword], - color: '#9d0006', - }, - { - tag: [t.tagName], - color: '#427b58', - fontStyle: 'bold', - }, - { - tag: [t.squareBracket], - color: '#af3a03', - }, - { - tag: [t.angleBracket], - color: '#076678', - }, - { - tag: [t.attributeName], - color: '#427b58', - }, - { - tag: [t.regexp], - color: '#427b58', - }, - { - tag: [t.quote], - color: '#928374', - }, - { tag: [t.string], color: '#3c3836' }, - { - tag: t.link, - color: '#7c6f64', - textDecoration: 'underline', - textUnderlinePosition: 'under', - }, - { - tag: [t.url, t.escape, t.special(t.string)], - color: '#8f3f71', - }, - { tag: [t.meta], color: '#b57614' }, - { tag: [t.comment], color: '#928374', fontStyle: 'italic' }, - { tag: t.strong, fontWeight: 'bold', color: '#af3a03' }, - { tag: t.emphasis, fontStyle: 'italic', color: '#79740e' }, - { tag: t.strikethrough, textDecoration: 'line-through' }, - { tag: t.heading, fontWeight: 'bold', color: '#79740e' }, - { tag: [t.heading1, t.heading2], fontWeight: 'bold', color: '#79740e' }, - { - tag: [t.heading3, t.heading4], - fontWeight: 'bold', - color: '#b57614', - }, - { - tag: [t.heading5, t.heading6], - color: '#b57614', - }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: '#8f3f71' }, - { - tag: [t.processingInstruction, t.inserted], - color: '#076678', - }, - { - tag: [t.contentSeparator], - color: '#9d0006', - }, - { tag: t.invalid, color: '#af3a03', borderBottom: `1px dotted #9d0006` }, - ], -}); diff --git a/src/strudel/codemirror/themes/materialDark.mjs b/src/strudel/codemirror/themes/materialDark.mjs deleted file mode 100644 index f134128..0000000 --- a/src/strudel/codemirror/themes/materialDark.mjs +++ /dev/null @@ -1,77 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - background: '#212121', - lineBackground: '#21212199', - foreground: '#bdbdbd', - caret: '#a0a4ae', - selection: '#d7d4f0', - selectionMatch: '#d7d4f0', - gutterBackground: '#212121', - gutterForeground: '#999', - gutterActiveForeground: '#4f5b66', - lineHighlight: '#111111', -}; - -export default createTheme({ - theme: 'dark', - settings: { - background: '#212121', - foreground: '#bdbdbd', - caret: '#a0a4ae', - selection: '#d7d4f063', - selectionMatch: '#d7d4f063', - gutterBackground: '#212121', - gutterForeground: '#999', - gutterActiveForeground: '#4f5b66', - lineHighlight: '#333333', - }, - styles: [ - { tag: t.keyword, color: '#cf6edf' }, - { tag: [t.name, t.deleted, t.character, t.macroName], color: '#56c8d8' }, - { tag: [t.propertyName], color: '#82AAFF' }, - { tag: [t.variableName], color: '#bdbdbd' }, - { tag: [t.function(t.variableName)], color: '#82AAFF' }, - { tag: [t.labelName], color: '#cf6edf' }, - { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#facf4e' }, - { tag: [t.definition(t.name), t.separator], color: '#56c8d8' }, - { tag: [t.brace], color: '#cf6edf' }, - { tag: [t.annotation], color: '#f07178' }, - { tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#f07178' }, - { tag: [t.typeName, t.className], color: '#f07178' }, - { tag: [t.operator, t.operatorKeyword], color: '#82AAFF' }, - { tag: [t.tagName], color: '#99d066' }, - { tag: [t.squareBracket], color: '#f07178' }, - { tag: [t.angleBracket], color: '#606f7a' }, - { tag: [t.attributeName], color: '#bdbdbd' }, - { tag: [t.regexp], color: '#f07178' }, - { tag: [t.quote], color: '#6abf69' }, - { tag: [t.string], color: '#99d066' }, - { - tag: t.link, - color: '#56c8d8', - textDecoration: 'underline', - textUnderlinePosition: 'under', - }, - { tag: [t.url, t.escape, t.special(t.string)], color: '#facf4e' }, - { tag: [t.meta], color: '#707d8b' }, - { tag: [t.comment], color: '#707d8b', fontStyle: 'italic' }, - { tag: t.monospace, color: '#bdbdbd' }, - { tag: t.strong, fontWeight: 'bold', color: '#f07178' }, - { tag: t.emphasis, fontStyle: 'italic', color: '#99d066' }, - { tag: t.strikethrough, textDecoration: 'line-through' }, - { tag: t.heading, fontWeight: 'bold', color: '#facf4e' }, - { tag: t.heading1, fontWeight: 'bold', color: '#facf4e' }, - { - tag: [t.heading2, t.heading3, t.heading4], - fontWeight: 'bold', - color: '#facf4e', - }, - { tag: [t.heading5, t.heading6], color: '#facf4e' }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: '#56c8d8' }, - { tag: [t.processingInstruction, t.inserted], color: '#f07178' }, - { tag: [t.contentSeparator], color: '#56c8d8' }, - { tag: t.invalid, color: '#606f7a', borderBottom: `1px dotted #f07178` }, - ], -}); diff --git a/src/strudel/codemirror/themes/materialLight.mjs b/src/strudel/codemirror/themes/materialLight.mjs deleted file mode 100644 index d2598c6..0000000 --- a/src/strudel/codemirror/themes/materialLight.mjs +++ /dev/null @@ -1,52 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - light: true, - background: '#FAFAFA', - lineBackground: '#FAFAFA99', - foreground: '#90A4AE', - caret: '#272727', - selection: '#80CBC440', - selectionMatch: '#FAFAFA', - gutterBackground: '#FAFAFA', - gutterForeground: '#90A4AE', - gutterBorder: 'transparent', - lineHighlight: '#CCD7DA50', -}; -export default createTheme({ - theme: 'light', - settings: { - background: '#FAFAFA', - foreground: '#90A4AE', - caret: '#272727', - selection: '#80CBC440', - selectionMatch: '#80CBC440', - gutterBackground: '#FAFAFA', - gutterForeground: '#90A4AE', - gutterBorder: 'transparent', - lineHighlight: '#CCD7DA50', - }, - styles: [ - { tag: t.keyword, color: '#39ADB5' }, - { tag: [t.name, t.deleted, t.character, t.macroName], color: '#90A4AE' }, - { tag: [t.propertyName], color: '#6182B8' }, - { tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#91B859' }, - { tag: [t.function(t.variableName), t.labelName], color: '#6182B8' }, - { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#39ADB5' }, - { tag: [t.definition(t.name), t.separator], color: '#90A4AE' }, - { tag: [t.className], color: '#E2931D' }, - { tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#F76D47' }, - { tag: [t.typeName], color: '#E2931D', fontStyle: '#E2931D' }, - { tag: [t.operator, t.operatorKeyword], color: '#39ADB5' }, - { tag: [t.url, t.escape, t.regexp, t.link], color: '#91B859' }, - { tag: [t.meta, t.comment], color: '#90A4AE' }, - { tag: t.strong, fontWeight: 'bold' }, - { tag: t.emphasis, fontStyle: 'italic' }, - { tag: t.link, textDecoration: 'underline' }, - { tag: t.heading, fontWeight: 'bold', color: '#39ADB5' }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: '#90A4AE' }, - { tag: t.invalid, color: '#E5393570' }, - { tag: t.strikethrough, textDecoration: 'line-through' }, - ], -}); diff --git a/src/strudel/codemirror/themes/monokai.mjs b/src/strudel/codemirror/themes/monokai.mjs deleted file mode 100644 index 294b946..0000000 --- a/src/strudel/codemirror/themes/monokai.mjs +++ /dev/null @@ -1,45 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - background: '#272822', - lineBackground: '#27282299', - foreground: '#FFFFFF', - caret: '#FFFFFF', - selection: '#49483E', - selectionMatch: '#49483E', - gutterBackground: '#272822', - gutterForeground: '#FFFFFF70', - lineHighlight: '#00000059', -}; - -export default createTheme({ - theme: 'dark', - settings: { - background: '#272822', - foreground: '#FFFFFF', - caret: '#FFFFFF', - selection: '#49483E', - selectionMatch: '#49483E', - gutterBackground: '#272822', - gutterForeground: '#FFFFFF70', - lineHighlight: '#0000003b', - }, - styles: [ - { tag: t.labelName, color: '#bababa' }, - { tag: [t.comment, t.documentMeta], color: '#8292a2' }, - { tag: [t.number, t.bool, t.null, t.atom], color: '#ae81ff' }, - { tag: [t.attributeValue, t.className, t.name], color: '#e6db74' }, - { tag: [t.propertyName, t.attributeName], color: '#a6e22e' }, - { tag: [t.variableName], color: '#9effff' }, - { tag: [t.squareBracket], color: '#bababa' }, - { tag: [t.string, t.special(t.brace)], color: '#e6db74' }, - { tag: [t.regexp, t.className, t.typeName, t.definition(t.typeName)], color: '#66d9ef' }, - { - tag: [t.definition(t.variableName), t.definition(t.propertyName), t.function(t.variableName)], - color: '#a6e22e', - }, - // { tag: t.keyword, color: '#f92672' }, - { tag: [t.keyword, t.definitionKeyword, t.modifier, t.tagName, t.angleBracket], color: '#f92672' }, - ], -}); diff --git a/src/strudel/codemirror/themes/noctisLilac.mjs b/src/strudel/codemirror/themes/noctisLilac.mjs deleted file mode 100644 index b11c6b4..0000000 --- a/src/strudel/codemirror/themes/noctisLilac.mjs +++ /dev/null @@ -1,50 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - light: true, - background: '#f2f1f8', - lineBackground: '#f2f1f899', - foreground: '#0c006b', - caret: '#5c49e9', - selection: '#d5d1f2', - selectionMatch: '#d5d1f2', - gutterBackground: '#f2f1f8', - gutterForeground: '#0c006b70', - lineHighlight: '#e1def3', -}; - -export default createTheme({ - theme: 'light', - settings: { - background: '#f2f1f8', - foreground: '#0c006b', - caret: '#5c49e9', - selection: '#d5d1f2', - selectionMatch: '#d5d1f2', - gutterBackground: '#f2f1f8', - gutterForeground: '#0c006b70', - lineHighlight: '#16067911', - }, - styles: [ - { tag: t.comment, color: '#9995b7' }, - { - tag: t.keyword, - color: '#ff5792', - fontWeight: 'bold', - }, - { tag: [t.definitionKeyword, t.modifier], color: '#ff5792' }, - { tag: [t.className, t.tagName, t.definition(t.typeName)], color: '#0094f0' }, - { tag: [t.number, t.bool, t.null, t.special(t.brace)], color: '#5842ff' }, - { tag: [t.definition(t.propertyName), t.function(t.variableName)], color: '#0095a8' }, - { tag: t.typeName, color: '#b3694d' }, - { tag: [t.propertyName, t.variableName], color: '#fa8900' }, - { tag: t.operator, color: '#ff5792' }, - { tag: t.self, color: '#e64100' }, - { tag: [t.string, t.regexp], color: '#00b368' }, - { tag: [t.paren, t.bracket], color: '#0431fa' }, - { tag: t.labelName, color: '#00bdd6' }, - { tag: t.attributeName, color: '#e64100' }, - { tag: t.angleBracket, color: '#9995b7' }, - ], -}); diff --git a/src/strudel/codemirror/themes/nord.mjs b/src/strudel/codemirror/themes/nord.mjs deleted file mode 100644 index ce96bca..0000000 --- a/src/strudel/codemirror/themes/nord.mjs +++ /dev/null @@ -1,78 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - background: '#2e3440', - lineBackground: '#2e344099', - foreground: '#FFFFFF', - caret: '#FFFFFF', - selection: '#3b4252', - selectionMatch: '#e5e9f0', - gutterBackground: '#2e3440', - gutterForeground: '#4c566a', - gutterActiveForeground: '#d8dee9', - lineHighlight: '#4c566a', -}; - -// Colors from https://www.nordtheme.com/docs/colors-and-palettes -export default createTheme({ - theme: 'dark', - settings: { - background: '#2e3440', - foreground: '#FFFFFF', - caret: '#FFFFFF', - selection: '#00000073', - selectionMatch: '#00000073', - gutterBackground: '#2e3440', - gutterForeground: '#4c566a', - gutterActiveForeground: '#d8dee9', - lineHighlight: '#4c566a29', - }, - styles: [ - { tag: t.keyword, color: '#5e81ac' }, - { tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: '#88c0d0' }, - { tag: [t.variableName], color: '#8fbcbb' }, - { tag: [t.function(t.variableName)], color: '#8fbcbb' }, - { tag: [t.labelName], color: '#81a1c1' }, - { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#5e81ac' }, - { tag: [t.definition(t.name), t.separator], color: '#a3be8c' }, - { tag: [t.brace], color: '#8fbcbb' }, - { tag: [t.annotation], color: '#d30102' }, - { tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#b48ead' }, - { tag: [t.typeName, t.className], color: '#ebcb8b' }, - { tag: [t.operator, t.operatorKeyword], color: '#a3be8c' }, - { tag: [t.tagName], color: '#b48ead' }, - { tag: [t.squareBracket], color: '#bf616a' }, - { tag: [t.angleBracket], color: '#d08770' }, - { tag: [t.attributeName], color: '#ebcb8b' }, - { tag: [t.regexp], color: '#5e81ac' }, - { tag: [t.quote], color: '#b48ead' }, - { tag: [t.string], color: '#a3be8c' }, - { - tag: t.link, - color: '#a3be8c', - textDecoration: 'underline', - textUnderlinePosition: 'under', - }, - { tag: [t.url, t.escape, t.special(t.string)], color: '#8fbcbb' }, - { tag: [t.meta], color: '#88c0d0' }, - { tag: [t.monospace], color: '#d8dee9', fontStyle: 'italic' }, - { tag: [t.comment], color: '#4c566a', fontStyle: 'italic' }, - { tag: t.strong, fontWeight: 'bold', color: '#5e81ac' }, - { tag: t.emphasis, fontStyle: 'italic', color: '#5e81ac' }, - { tag: t.strikethrough, textDecoration: 'line-through' }, - { tag: t.heading, fontWeight: 'bold', color: '#5e81ac' }, - { tag: t.special(t.heading1), fontWeight: 'bold', color: '#5e81ac' }, - { tag: t.heading1, fontWeight: 'bold', color: '#5e81ac' }, - { - tag: [t.heading2, t.heading3, t.heading4], - fontWeight: 'bold', - color: '#5e81ac', - }, - { tag: [t.heading5, t.heading6], color: '#5e81ac' }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: '#d08770' }, - { tag: [t.processingInstruction, t.inserted], color: '#8fbcbb' }, - { tag: [t.contentSeparator], color: '#ebcb8b' }, - { tag: t.invalid, color: '#434c5e', borderBottom: `1px dotted #d30102` }, - ], -}); diff --git a/src/strudel/codemirror/themes/red-text.mjs b/src/strudel/codemirror/themes/red-text.mjs deleted file mode 100644 index 44dda5d..0000000 --- a/src/strudel/codemirror/themes/red-text.mjs +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Atom One - * Atom One dark syntax theme - * - * https://github.com/atom/one-dark-syntax - */ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -const hex = ['#000000', '#ff5356', '#bd312a', '#54636D', '#171717']; - -export const settings = { - background: hex[0], - lineBackground: 'transparent', - foreground: hex[2], - selection: hex[4], - selectionMatch: hex[0], - gutterBackground: hex[0], - gutterForeground: hex[3], - gutterBorder: 'transparent', - lineHighlight: hex[0], -}; - -export default createTheme({ - theme: 'dark', - settings, - styles: [ - { - tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction], - color: hex[2], - }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[1] }, - { tag: t.comment, color: hex[3] }, - { tag: [t.variableName, t.propertyName, t.labelName], color: hex[2] }, - { tag: [t.attributeName, t.number], color: hex[1] }, - { tag: t.keyword, color: hex[2] }, - { tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[1] }, - ], -}); diff --git a/src/strudel/codemirror/themes/solarizedDark.mjs b/src/strudel/codemirror/themes/solarizedDark.mjs deleted file mode 100644 index c6b645f..0000000 --- a/src/strudel/codemirror/themes/solarizedDark.mjs +++ /dev/null @@ -1,79 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - background: '#002b36', - lineBackground: '#002b3699', - foreground: '#93a1a1', - caret: '#839496', - selection: '#173541', - selectionMatch: '#aafe661a', - gutterBackground: '#00252f', - gutterForeground: '#839496', - lineHighlight: '#173541', -}; - -const c = { - background: '#002B36', - foreground: '#839496', - selection: '#004454AA', - selectionMatch: '#005A6FAA', - cursor: '#D30102', - dropdownBackground: '#00212B', - dropdownBorder: '#2AA19899', - activeLine: '#00cafe11', - matchingBracket: '#073642', - keyword: '#859900', - storage: '#93A1A1', - variable: '#268BD2', - parameter: '#268BD2', - function: '#268BD2', - string: '#2AA198', - constant: '#CB4B16', - type: '#859900', - class: '#268BD2', - number: '#D33682', - comment: '#586E75', - heading: '#268BD2', - invalid: '#DC322F', - regexp: '#DC322F', - tag: '#268BD2', -}; - -export default createTheme({ - theme: 'dark', - settings: { - background: c.background, - foreground: c.foreground, - caret: c.cursor, - selection: c.selection, - selectionMatch: c.selection, - gutterBackground: c.background, - gutterForeground: c.foreground, - gutterBorder: 'transparent', - lineHighlight: c.activeLine, - }, - styles: [ - { tag: t.keyword, color: c.keyword }, - { tag: [t.name, t.deleted, t.character, t.macroName], color: c.variable }, - { tag: [t.propertyName], color: c.function }, - { tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: c.string }, - { tag: [t.function(t.variableName), t.labelName], color: c.function }, - { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: c.constant }, - { tag: [t.definition(t.name), t.separator], color: c.variable }, - { tag: [t.className], color: c.class }, - { tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: c.number }, - { tag: [t.typeName], color: c.type, fontStyle: c.type }, - { tag: [t.operator, t.operatorKeyword], color: c.keyword }, - { tag: [t.url, t.escape, t.regexp, t.link], color: c.regexp }, - { tag: [t.meta, t.comment], color: c.comment }, - { tag: t.tagName, color: c.tag }, - { tag: t.strong, fontWeight: 'bold' }, - { tag: t.emphasis, fontStyle: 'italic' }, - { tag: t.link, textDecoration: 'underline' }, - { tag: t.heading, fontWeight: 'bold', color: c.heading }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: c.variable }, - { tag: t.invalid, color: c.invalid }, - { tag: t.strikethrough, textDecoration: 'line-through' }, - ], -}); diff --git a/src/strudel/codemirror/themes/solarizedLight.mjs b/src/strudel/codemirror/themes/solarizedLight.mjs deleted file mode 100644 index b6828ff..0000000 --- a/src/strudel/codemirror/themes/solarizedLight.mjs +++ /dev/null @@ -1,82 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -// this is slightly different from https://thememirror.net/solarized-light - -export const settings = { - light: true, - background: '#fdf6e3', - lineBackground: '#fdf6e399', - foreground: '#657b83', - caret: '#586e75', - selection: '#dfd9c8', - selectionMatch: '#dfd9c8', - gutterBackground: '#00000010', - gutterForeground: '#657b83', - lineHighlight: '#dfd9c8', -}; - -const c = { - background: '#FDF6E3', - foreground: '#657B83', - selection: '#EEE8D5', - selectionMatch: '#EEE8D5', - cursor: '#657B83', - dropdownBackground: '#EEE8D5', - dropdownBorder: '#D3AF86', - activeLine: '#3d392d11', - matchingBracket: '#EEE8D5', - keyword: '#859900', - storage: '#586E75', - variable: '#268BD2', - parameter: '#268BD2', - function: '#268BD2', - string: '#2AA198', - constant: '#CB4B16', - type: '#859900', - class: '#268BD2', - number: '#D33682', - comment: '#93A1A1', - heading: '#268BD2', - invalid: '#DC322F', - regexp: '#DC322F', - tag: '#268BD2', -}; - -export default createTheme({ - theme: 'light', - settings: { - background: c.background, - foreground: c.foreground, - caret: c.cursor, - selection: c.selection, - selectionMatch: c.selectionMatch, - gutterBackground: c.background, - gutterForeground: c.foreground, - gutterBorder: 'transparent', - lineHighlight: c.activeLine, - }, - styles: [ - { tag: t.keyword, color: c.keyword }, - { tag: [t.name, t.deleted, t.character, t.macroName], color: c.variable }, - { tag: [t.propertyName], color: c.function }, - { tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: c.string }, - { tag: [t.function(t.variableName), t.labelName], color: c.function }, - { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: c.constant }, - { tag: [t.definition(t.name), t.separator], color: c.variable }, - { tag: [t.className], color: c.class }, - { tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: c.number }, - { tag: [t.typeName], color: c.type, fontStyle: c.type }, - { tag: [t.operator, t.operatorKeyword], color: c.keyword }, - { tag: [t.url, t.escape, t.regexp, t.link], color: c.regexp }, - { tag: [t.meta, t.comment], color: c.comment }, - { tag: t.tagName, color: c.tag }, - { tag: t.strong, fontWeight: 'bold' }, - { tag: t.emphasis, fontStyle: 'italic' }, - { tag: t.link, textDecoration: 'underline' }, - { tag: t.heading, fontWeight: 'bold', color: c.heading }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: c.variable }, - { tag: t.invalid, color: c.invalid }, - { tag: t.strikethrough, textDecoration: 'line-through' }, - ], -}); diff --git a/src/strudel/codemirror/themes/sonic-pink.mjs b/src/strudel/codemirror/themes/sonic-pink.mjs deleted file mode 100644 index 5a19555..0000000 --- a/src/strudel/codemirror/themes/sonic-pink.mjs +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Atom One - * Atom One dark syntax theme - * - * https://github.com/atom/one-dark-syntax - */ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -const hex = ['#1e1e1e', '#fbde2d', '#ff1493', '#4c83ff', '#ededed', '#cccccc', '#ffffff30', '#dc2f8c']; - -export const settings = { - background: '#000000', - lineBackground: 'transparent', - foreground: hex[4], - selection: hex[6], - gutterBackground: hex[0], - gutterForeground: hex[5], - gutterBorder: 'transparent', - lineHighlight: hex[0], -}; - -export default createTheme({ - theme: 'dark', - settings, - styles: [ - { - tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction], - color: hex[4], - }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[3] }, - - { tag: t.comment, color: '#54636D' }, - { tag: [t.variableName, t.propertyName, t.labelName], color: hex[4] }, - { tag: [t.attributeName, t.number], color: hex[3] }, - { tag: t.keyword, color: hex[1] }, - { tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[2] }, - ], -}); diff --git a/src/strudel/codemirror/themes/strudel-theme.mjs b/src/strudel/codemirror/themes/strudel-theme.mjs deleted file mode 100644 index 7c4a3a9..0000000 --- a/src/strudel/codemirror/themes/strudel-theme.mjs +++ /dev/null @@ -1,49 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -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, - 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' }, - { tag: t.special(t.variableName), color: '#eeffff' }, - // { tag: t.typeName, color: '#f07178' }, // original - { tag: t.typeName, color: '#c3e88d' }, - { tag: t.atom, color: '#f78c6c' }, - // { tag: t.number, color: '#ff5370' }, // original - { tag: t.number, color: '#c3e88d' }, - { tag: t.definition(t.variableName), color: '#82aaff' }, - { tag: t.string, color: '#c3e88d' }, - // { tag: t.special(t.string), color: '#f07178' }, // original - { tag: t.special(t.string), color: '#c3e88d' }, - { tag: t.comment, color: '#7d8799' }, - // { tag: t.variableName, color: '#f07178' }, // original - { tag: t.variableName, color: '#c792ea' }, - // { tag: t.tagName, color: '#ff5370' }, // original - { tag: t.tagName, color: '#c3e88d' }, - { tag: t.bracket, color: '#525154' }, - // { tag: t.bracket, color: '#a2a1a4' }, // original - { tag: t.meta, color: '#ffcb6b' }, - { tag: t.attributeName, color: '#c792ea' }, - { tag: t.propertyName, color: '#c792ea' }, - - { tag: t.className, color: '#decb6b' }, - { tag: t.invalid, color: '#ffffff' }, - { tag: [t.unit, t.punctuation], color: '#82aaff' }, - ], -}); diff --git a/src/strudel/codemirror/themes/sublime.mjs b/src/strudel/codemirror/themes/sublime.mjs deleted file mode 100644 index 068b9ba..0000000 --- a/src/strudel/codemirror/themes/sublime.mjs +++ /dev/null @@ -1,43 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - background: '#303841', - lineBackground: '#30384199', - foreground: '#FFFFFF', - caret: '#FBAC52', - selection: '#4C5964', - selectionMatch: '#3A546E', - gutterBackground: '#303841', - gutterForeground: '#FFFFFF70', - lineHighlight: '#00000059', -}; - -export default createTheme({ - theme: 'dark', - settings: { - background: '#303841', - foreground: '#FFFFFF', - caret: '#FBAC52', - selection: '#4C5964', - selectionMatch: '#3A546E', - gutterBackground: '#303841', - gutterForeground: '#FFFFFF70', - lineHighlight: '#00000059', - }, - styles: [ - { tag: t.labelName, color: '#A2A9B5' }, - { tag: [t.meta, t.comment], color: '#A2A9B5' }, - { tag: [t.attributeName, t.keyword], color: '#B78FBA' }, - { tag: t.function(t.variableName), color: '#5AB0B0' }, - { tag: [t.string, t.regexp, t.attributeValue], color: '#99C592' }, - { tag: t.operator, color: '#f47954' }, - // { tag: t.moduleKeyword, color: 'red' }, - { tag: [t.tagName, t.modifier], color: '#E35F63' }, - { tag: [t.number, t.definition(t.tagName), t.className, t.definition(t.variableName)], color: '#fbac52' }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: '#E35F63' }, - { tag: t.variableName, color: '#539ac4' }, - { tag: [t.propertyName, t.typeName], color: '#629ccd' }, - { tag: t.propertyName, color: '#36b7b5' }, - ], -}); diff --git a/src/strudel/codemirror/themes/teletext.mjs b/src/strudel/codemirror/themes/teletext.mjs deleted file mode 100644 index 7857cb1..0000000 --- a/src/strudel/codemirror/themes/teletext.mjs +++ /dev/null @@ -1,51 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -let colorA = '#6edee4'; -//let colorB = 'magenta'; -let colorB = 'white'; -let colorC = 'red'; -let colorD = '#f8fc55'; - -export const settings = { - background: '#000000', - foreground: colorA, // whats that? - caret: colorC, - selection: colorD, - selectionMatch: colorA, - lineHighlight: '#6edee440', // panel bg - lineBackground: '#00000040', - gutterBackground: 'transparent', - gutterForeground: '#8a919966', - // customStyle: '.cm-line { line-height: 1 }', -}; - -let punctuation = colorD; -let mini = colorB; - -export default createTheme({ - theme: 'dark', - settings, - styles: [ - { tag: t.labelName, color: colorB }, - { tag: t.keyword, color: colorA }, - { tag: t.operator, color: mini }, - { tag: t.special(t.variableName), color: colorA }, - { tag: t.typeName, color: colorA }, - { tag: t.atom, color: colorA }, - { tag: t.number, color: mini }, - { tag: t.definition(t.variableName), color: colorA }, - { tag: t.string, color: mini }, - { tag: t.special(t.string), color: mini }, - { tag: t.comment, color: punctuation }, - { tag: t.variableName, color: colorA }, - { tag: t.tagName, color: colorA }, - { tag: t.bracket, color: punctuation }, - { tag: t.meta, color: colorA }, - { tag: t.attributeName, color: colorA }, - { tag: t.propertyName, color: colorA }, // methods - { tag: t.className, color: colorA }, - { tag: t.invalid, color: colorC }, - { tag: [t.unit, t.punctuation], color: punctuation }, - ], -}); diff --git a/src/strudel/codemirror/themes/terminal.mjs b/src/strudel/codemirror/themes/terminal.mjs deleted file mode 100644 index b07bdb8..0000000 --- a/src/strudel/codemirror/themes/terminal.mjs +++ /dev/null @@ -1,37 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; -export const settings = { - background: 'black', - foreground: '#41FF00', // whats that? - caret: '#41FF00', - selection: '#ffffff20', - selectionMatch: '#036dd626', - lineHighlight: '#ffffff10', - gutterBackground: 'transparent', - gutterForeground: '#8a919966', -}; -export default createTheme({ - theme: 'dark', - settings, - styles: [ - { tag: t.labelName, color: 'inherit' }, - { tag: t.keyword, color: 'inherit' }, - { tag: t.operator, color: 'inherit' }, - { tag: t.special(t.variableName), color: 'inherit' }, - { tag: t.typeName, color: 'inherit' }, - { tag: t.atom, color: 'inherit' }, - { tag: t.number, color: 'inherit' }, - { tag: t.definition(t.variableName), color: 'inherit' }, - { tag: t.string, color: 'inherit' }, - { tag: t.special(t.string), color: 'inherit' }, - { tag: t.comment, color: 'inherit' }, - { tag: t.variableName, color: 'inherit' }, - { tag: t.tagName, color: 'inherit' }, - { tag: t.bracket, color: 'inherit' }, - { tag: t.meta, color: 'inherit' }, - { tag: t.attributeName, color: 'inherit' }, - { tag: t.propertyName, color: 'inherit' }, - { tag: t.className, color: 'inherit' }, - { tag: t.invalid, color: 'inherit' }, - ], -}); diff --git a/src/strudel/codemirror/themes/theme-helper.mjs b/src/strudel/codemirror/themes/theme-helper.mjs deleted file mode 100644 index ee9bad8..0000000 --- a/src/strudel/codemirror/themes/theme-helper.mjs +++ /dev/null @@ -1,42 +0,0 @@ -import { EditorView } from '@codemirror/view'; -import { syntaxHighlighting } from '@codemirror/language'; -import { HighlightStyle } from '@codemirror/language'; - -export const createTheme = ({ theme, settings, styles }) => { - const _theme = EditorView.theme( - { - '&': { - color: settings.foreground, - backgroundColor: settings.background, - }, - '.cm-gutters': { - backgroundColor: settings.gutterBackground, - color: settings.gutterForeground, - //borderRightColor: settings.gutterBorder - }, - '.cm-content': { - caretColor: settings.caret, - }, - '.cm-cursor, .cm-dropCursor': { - borderLeftColor: settings.caret, - }, - '.cm-activeLineGutter': { - // color: settings.gutterActiveForeground - backgroundColor: settings.lineHighlight, - }, - '.cm-activeLine': { - backgroundColor: settings.lineHighlight, - }, - '&.cm-focused .cm-selectionBackground, & .cm-line::selection, & .cm-selectionLayer .cm-selectionBackground, .cm-content ::selection': - { - background: settings.selection + ' !important', - }, - '& .cm-selectionMatch': { - backgroundColor: settings.selectionMatch, - }, - }, - { dark: theme === 'dark' }, - ); - const highlightStyle = HighlightStyle.define(styles); - return [_theme, syntaxHighlighting(highlightStyle)]; -}; diff --git a/src/strudel/codemirror/themes/tokioNightStorm.mjs b/src/strudel/codemirror/themes/tokioNightStorm.mjs deleted file mode 100644 index be973d5..0000000 --- a/src/strudel/codemirror/themes/tokioNightStorm.mjs +++ /dev/null @@ -1,52 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - background: '#24283b', - lineBackground: '#24283b99', - foreground: '#7982a9', - caret: '#c0caf5', - selection: '#6f7bb630', - selectionMatch: '#1f2335', - gutterBackground: '#24283b', - gutterForeground: '#7982a9', - gutterBorder: 'transparent', - lineHighlight: '#292e42', -}; - -export default createTheme({ - theme: 'dark', - settings: { - background: '#24283b', - foreground: '#7982a9', - caret: '#c0caf5', - selection: '#6f7bb630', - selectionMatch: '#343b5f', - gutterBackground: '#24283b', - gutterForeground: '#7982a9', - gutterBorder: 'transparent', - lineHighlight: '#292e427a', - }, - styles: [ - { tag: t.keyword, color: '#bb9af7' }, - { tag: [t.name, t.deleted, t.character, t.macroName], color: '#c0caf5' }, - { tag: [t.propertyName], color: '#7aa2f7' }, - { tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#9ece6a' }, - { tag: [t.function(t.variableName), t.labelName], color: '#7aa2f7' }, - { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#bb9af7' }, - { tag: [t.definition(t.name), t.separator], color: '#c0caf5' }, - { tag: [t.className], color: '#c0caf5' }, - { tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#ff9e64' }, - { tag: [t.typeName], color: '#2ac3de', fontStyle: '#2ac3de' }, - { tag: [t.operator, t.operatorKeyword], color: '#bb9af7' }, - { tag: [t.url, t.escape, t.regexp, t.link], color: '#b4f9f8' }, - { tag: [t.meta, t.comment], color: '#565f89' }, - { tag: t.strong, fontWeight: 'bold' }, - { tag: t.emphasis, fontStyle: 'italic' }, - { tag: t.link, textDecoration: 'underline' }, - { tag: t.heading, fontWeight: 'bold', color: '#89ddff' }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: '#c0caf5' }, - { tag: t.invalid, color: '#ff5370' }, - { tag: t.strikethrough, textDecoration: 'line-through' }, - ], -}); diff --git a/src/strudel/codemirror/themes/tokyoNight.mjs b/src/strudel/codemirror/themes/tokyoNight.mjs deleted file mode 100644 index c325b4a..0000000 --- a/src/strudel/codemirror/themes/tokyoNight.mjs +++ /dev/null @@ -1,52 +0,0 @@ -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' }, - ], -}); diff --git a/src/strudel/codemirror/themes/tokyoNightDay.mjs b/src/strudel/codemirror/themes/tokyoNightDay.mjs deleted file mode 100644 index 1cd58a4..0000000 --- a/src/strudel/codemirror/themes/tokyoNightDay.mjs +++ /dev/null @@ -1,53 +0,0 @@ -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' }, - ], -}); diff --git a/src/strudel/codemirror/themes/vscodeDark.mjs b/src/strudel/codemirror/themes/vscodeDark.mjs deleted file mode 100644 index 703790f..0000000 --- a/src/strudel/codemirror/themes/vscodeDark.mjs +++ /dev/null @@ -1,80 +0,0 @@ -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' }, - ], -}); diff --git a/src/strudel/codemirror/themes/vscodeLight.mjs b/src/strudel/codemirror/themes/vscodeLight.mjs deleted file mode 100644 index 9a73451..0000000 --- a/src/strudel/codemirror/themes/vscodeLight.mjs +++ /dev/null @@ -1,81 +0,0 @@ -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' }, - ], -}); diff --git a/src/strudel/codemirror/themes/whitescreen.mjs b/src/strudel/codemirror/themes/whitescreen.mjs deleted file mode 100644 index a2937c3..0000000 --- a/src/strudel/codemirror/themes/whitescreen.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; -export const settings = { - background: 'white', - foreground: 'black', // whats that? - caret: 'black', - selection: 'rgba(128, 203, 196, 0.5)', - selectionMatch: '#ffffff26', - lineHighlight: '#cccccc50', - lineBackground: '#ffffff50', - gutterBackground: 'transparent', - gutterForeground: 'black', - light: true, -}; -export default createTheme({ - theme: 'light', - settings, - styles: [ - { tag: t.labelName, color: 'inherit' }, - { tag: t.keyword, color: 'inherit' }, - { tag: t.operator, color: 'inherit' }, - { tag: t.special(t.variableName), color: 'inherit' }, - { tag: t.typeName, color: 'inherit' }, - { tag: t.atom, color: 'inherit' }, - { tag: t.number, color: 'inherit' }, - { tag: t.definition(t.variableName), color: 'inherit' }, - { tag: t.string, color: 'inherit' }, - { tag: t.special(t.string), color: 'inherit' }, - { tag: t.comment, color: 'inherit' }, - { tag: t.variableName, color: 'inherit' }, - { tag: t.tagName, color: 'inherit' }, - { tag: t.bracket, color: 'inherit' }, - { tag: t.meta, color: 'inherit' }, - { tag: t.attributeName, color: 'inherit' }, - { tag: t.propertyName, color: 'inherit' }, - { tag: t.className, color: 'inherit' }, - { tag: t.invalid, color: 'inherit' }, - ], -}); diff --git a/src/strudel/codemirror/themes/xcodeLight.mjs b/src/strudel/codemirror/themes/xcodeLight.mjs deleted file mode 100644 index 8977c13..0000000 --- a/src/strudel/codemirror/themes/xcodeLight.mjs +++ /dev/null @@ -1,38 +0,0 @@ -import { tags as t } from '@lezer/highlight'; -import { createTheme } from './theme-helper.mjs'; - -export const settings = { - light: true, - background: '#fff', - lineBackground: '#ffffff99', - foreground: '#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' }, - ], -}); diff --git a/src/strudel/codemirror/tooltip.mjs b/src/strudel/codemirror/tooltip.mjs deleted file mode 100644 index d1d0479..0000000 --- a/src/strudel/codemirror/tooltip.mjs +++ /dev/null @@ -1,79 +0,0 @@ -import { hoverTooltip } from '@codemirror/view'; -import jsdoc from '../../doc.json'; -import { Autocomplete, getSynonymDoc } from './autocomplete.mjs'; - -const getDocLabel = (doc) => doc.name || doc.longname; - -let ctrlDown = false; - -if (typeof window !== 'undefined') { - // Record Control key event to trigger or block the tooltip depending on the state - window.addEventListener( - 'keyup', - function (e) { - if (e.key == 'Control') { - ctrlDown = false; - } - }, - true, - ); - - window.addEventListener( - 'keydown', - function (e) { - if (e.key == 'Control') { - ctrlDown = true; - } - }, - true, - ); -} - -export const strudelTooltip = hoverTooltip( - (view, pos, side) => { - // Word selection from CodeMirror Hover Tooltip example https://codemirror.net/examples/tooltip/#hover-tooltips - if (!ctrlDown) { - return null; - } - let { from, to, text } = view.state.doc.lineAt(pos); - let start = pos, - end = pos; - while (start > from && /\w/.test(text[start - from - 1])) { - start--; - } - while (end < to && /\w/.test(text[end - from])) { - end++; - } - if ((start == pos && side < 0) || (end == pos && side > 0)) { - return null; - } - let word = text.slice(start - from, end - from); - // Get entry from Strudel documentation - let entry = jsdoc.docs.filter((doc) => getDocLabel(doc) === word)[0]; - if (!entry) { - // Try for synonyms - const doc = jsdoc.docs.filter((doc) => doc.synonyms && doc.synonyms.includes(word))[0]; - if (!doc) { - return null; - } - entry = getSynonymDoc(doc, word); - } - - return { - pos: start, - end, - above: false, - arrow: true, - create(view) { - let dom = document.createElement('div'); - dom.className = 'strudel-tooltip'; - const ac = Autocomplete(entry); - dom.appendChild(ac); - return { dom }; - }, - }; - }, - { hoverTime: 10 }, -); - -export const isTooltipEnabled = (on) => (on ? strudelTooltip : []); diff --git a/src/strudel/codemirror/vite.config.js b/src/strudel/codemirror/vite.config.js deleted file mode 100644 index 5df3edc..0000000 --- a/src/strudel/codemirror/vite.config.js +++ /dev/null @@ -1,19 +0,0 @@ -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', - }, -}); diff --git a/src/strudel/codemirror/widget.mjs b/src/strudel/codemirror/widget.mjs deleted file mode 100644 index 42d3b15..0000000 --- a/src/strudel/codemirror/widget.mjs +++ /dev/null @@ -1,142 +0,0 @@ -import { StateEffect, StateField } from '@codemirror/state'; -import { Decoration, EditorView, WidgetType } from '@codemirror/view'; -import { getWidgetID, registerWidgetType } from '@strudel/transpiler'; -import { Pattern } from '@strudel/core'; - -export const addWidget = StateEffect.define({ - map: ({ from, to }, change) => { - return { from: change.mapPos(from), to: change.mapPos(to) }; - }, -}); - -export const updateWidgets = (view, widgets) => { - view.dispatch({ effects: addWidget.of(widgets) }); -}; - -function getWidgets(widgetConfigs) { - return ( - widgetConfigs - // codemirror throws an error if we don't sort - .sort((a, b) => a.to - b.to) - .map((widgetConfig) => { - return Decoration.widget({ - widget: new BlockWidget(widgetConfig), - side: 0, - block: true, - }).range(widgetConfig.to); - }) - ); -} - -const widgetField = StateField.define( - /* */ { - create() { - return Decoration.none; - }, - update(widgets, tr) { - widgets = widgets.map(tr.changes); - for (let e of tr.effects) { - if (e.is(addWidget)) { - try { - widgets = widgets.update({ - filter: () => false, - add: getWidgets(e.value), - }); - } catch (error) { - console.log('err', error); - } - } - } - return widgets; - }, - provide: (f) => EditorView.decorations.from(f), - }, -); - -const widgetElements = {}; -export function setWidget(id, el) { - widgetElements[id] = el; - el.id = id; -} - -export class BlockWidget extends WidgetType { - constructor(widgetConfig) { - super(); - this.widgetConfig = widgetConfig; - } - eq() { - return true; - } - toDOM() { - const id = getWidgetID(this.widgetConfig); - const el = widgetElements[id]; - return el; - } - ignoreEvent(e) { - return true; - } -} - -export const widgetPlugin = [widgetField]; - -// widget implementer API to create a new widget type -export function registerWidget(type, fn) { - registerWidgetType(type); - if (fn) { - Pattern.prototype[type] = function (id, options = { fold: 1 }) { - // fn is expected to create a dom element and call setWidget(id, el); - // fn should also return the pattern - return fn(id, options, this); - }; - } -} - -// wire up @strudel/draw functions - -function getCanvasWidget(id, options = {}) { - const { width = 500, height = 60, pixelRatio = window.devicePixelRatio } = options; - let canvas = document.getElementById(id) || document.createElement('canvas'); - canvas.width = width * pixelRatio; - canvas.height = height * pixelRatio; - canvas.style.width = width + 'px'; - canvas.style.height = height + 'px'; - setWidget(id, canvas); - return canvas; -} - -registerWidget('_pianoroll', (id, options = {}, pat) => { - const ctx = getCanvasWidget(id, options).getContext('2d'); - return pat.tag(id).pianoroll({ fold: 1, ...options, ctx, id }); -}); - -registerWidget('_punchcard', (id, options = {}, pat) => { - const ctx = getCanvasWidget(id, options).getContext('2d'); - return pat.tag(id).punchcard({ fold: 1, ...options, ctx, id }); -}); - -registerWidget('_spiral', (id, options = {}, pat) => { - let _size = options.size || 275; - options = { width: _size, height: _size, ...options, size: _size / 5 }; - const ctx = getCanvasWidget(id, options).getContext('2d'); - return pat.tag(id).spiral({ ...options, ctx, id }); -}); - -registerWidget('_scope', (id, options = {}, pat) => { - options = { width: 500, height: 60, pos: 0.5, scale: 1, ...options }; - const ctx = getCanvasWidget(id, options).getContext('2d'); - return pat.tag(id).scope({ ...options, ctx, id }); -}); - -registerWidget('_pitchwheel', (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.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 }); -}); diff --git a/src/strudel/core/clockworker.js b/src/strudel/core/clockworker.js index 77a4536..9d0fad9 100644 --- a/src/strudel/core/clockworker.js +++ b/src/strudel/core/clockworker.js @@ -4,34 +4,34 @@ // import createClock from './zyklus.mjs'; function getTime() { - const seconds = performance.now() * 0.001; - return seconds; + const seconds = performance.now() * 0.001 + return seconds // return Math.round(seconds * precision) / precision; } -let num_cycles_at_cps_change = 0; -let num_ticks_since_cps_change = 0; -let num_seconds_at_cps_change = 0; -let cps = 0.5; +let num_cycles_at_cps_change = 0 +let num_ticks_since_cps_change = 0 +let num_seconds_at_cps_change = 0 +let cps = 0.5 // {id: {started: boolean}} -const clients = new Map(); -const duration = 0.1; -const channel = new BroadcastChannel('strudeltick'); +const clients = new Map() +const duration = 0.1 +const channel = new BroadcastChannel('strudeltick') const sendMessage = (type, payload) => { - channel.postMessage({ type, payload }); -}; + channel.postMessage({ 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; + 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, @@ -39,79 +39,79 @@ const sendTick = (phase, duration, tick, time) => { cps, time, cycle, - }); - num_ticks_since_cps_change++; -}; + }) + num_ticks_since_cps_change++ +} //create clock method from zyklus -const clock = createClock(getTime, sendTick, duration); -let started = false; +const clock = createClock(getTime, sendTick, duration) +let started = false const startClock = (id) => { - clients.set(id, { started: true }); + clients.set(id, { started: true }) if (started) { - return; + return } - clock.start(); - started = true; -}; + clock.start() + started = true +} const stopClock = async (id) => { - clients.set(id, { started: false }); + clients.set(id, { started: false }) - const otherClientStarted = Array.from(clients.values()).some((c) => c.started); + const otherClientStarted = Array.from(clients.values()).some((c) => c.started) //dont stop the clock if other instances are running... if (!started || otherClientStarted) { - return; + return } - clock.stop(); - setCycle(0); - started = false; -}; + clock.stop() + setCycle(0) + started = false +} const setCycle = (cycle) => { - num_ticks_since_cps_change = 0; - num_cycles_at_cps_change = cycle; -}; + num_ticks_since_cps_change = 0 + num_cycles_at_cps_change = cycle +} const processMessage = (message) => { - const { type, payload } = message; + const { type, payload } = message switch (type) { case 'cpschange': { if (payload.cps !== cps) { - const num_seconds_since_cps_change = num_ticks_since_cps_change * duration; - num_cycles_at_cps_change = num_cycles_at_cps_change + num_seconds_since_cps_change * cps; - num_seconds_at_cps_change = num_seconds_at_cps_change + num_seconds_since_cps_change; - cps = payload.cps; - num_ticks_since_cps_change = 0; + const num_seconds_since_cps_change = num_ticks_since_cps_change * duration + num_cycles_at_cps_change = num_cycles_at_cps_change + num_seconds_since_cps_change * cps + num_seconds_at_cps_change = num_seconds_at_cps_change + num_seconds_since_cps_change + cps = payload.cps + num_ticks_since_cps_change = 0 } - break; + break } case 'setcycle': { - setCycle(payload.cycle); - break; + setCycle(payload.cycle) + break } case 'toggle': { if (payload.started) { - startClock(message.id); + startClock(message.id) } else { - stopClock(message.id); + stopClock(message.id) } - break; + break } } -}; +} self.onconnect = function (e) { // the incoming port - const port = e.ports[0]; + const port = e.ports[0] port.addEventListener('message', function (e) { - processMessage(e.data); - }); - port.start(); // Required when using addEventListener. Otherwise called implicitly by onmessage setter. -}; + processMessage(e.data) + }) + port.start() // Required when using addEventListener. Otherwise called implicitly by onmessage setter. +} // used to consistently schedule events, for use in a service worker - see function createClock( @@ -119,43 +119,43 @@ function createClock( callback, // called slightly before each cycle duration = 0.05, // duration of each cycle interval = 0.1, // interval between callbacks - overlap = 0.1, // overlap between callbacks + overlap = 0.1 // overlap between callbacks ) { - let tick = 0; // counts callbacks - let phase = 0; // next callback time - let precision = 10 ** 4; // used to round phase - let minLatency = 0.01; - const setDuration = (setter) => (duration = setter(duration)); - overlap = overlap || interval / 2; + let tick = 0 // counts callbacks + let phase = 0 // next callback time + let precision = 10 ** 4 // used to round phase + let minLatency = 0.01 + const setDuration = (setter) => (duration = setter(duration)) + overlap = overlap || interval / 2 const onTick = () => { - const t = getTime(); - const lookahead = t + interval + overlap; // the time window for this tick + const t = getTime() + const lookahead = t + interval + overlap // the time window for this tick if (phase === 0) { - phase = t + minLatency; + phase = t + minLatency } // callback as long as we're inside the lookahead while (phase < lookahead) { - phase = Math.round(phase * precision) / precision; - phase >= t && callback(phase, duration, tick, t); - phase < t && console.log('TOO LATE', phase); // what if latency is added from outside? - phase += duration; // increment phase by duration - tick++; + phase = Math.round(phase * precision) / precision + phase >= t && callback(phase, duration, tick, t) + phase < t && console.log('TOO LATE', phase) // what if latency is added from outside? + phase += duration // increment phase by duration + tick++ } - }; - let intervalID; + } + let intervalID const start = () => { - clear(); // just in case start was called more than once - onTick(); - intervalID = setInterval(onTick, interval * 1000); - }; - const clear = () => intervalID !== undefined && clearInterval(intervalID); - const pause = () => clear(); + clear() // just in case start was called more than once + onTick() + intervalID = setInterval(onTick, interval * 1000) + } + const clear = () => intervalID !== undefined && clearInterval(intervalID) + const pause = () => clear() const stop = () => { - tick = 0; - phase = 0; - clear(); - }; - const getPhase = () => phase; + tick = 0 + phase = 0 + clear() + } + const getPhase = () => phase // setCallback - return { setDuration, start, stop, pause, duration, interval, getPhase, minLatency }; + return { setDuration, start, stop, pause, duration, interval, getPhase, minLatency } } diff --git a/src/strudel/core/controls.mjs b/src/strudel/core/controls.mjs index 3475fc2..22648b6 100644 --- a/src/strudel/core/controls.mjs +++ b/src/strudel/core/controls.mjs @@ -4,72 +4,72 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { Pattern, register, reify } from './pattern.mjs'; +import { Pattern, register, reify } from './pattern.mjs' export function createParam(names) { - let isMulti = Array.isArray(names); - names = !isMulti ? [names] : names; - const name = names[0]; + let isMulti = Array.isArray(names) + names = !isMulti ? [names] : names + const name = names[0] // todo: make this less confusing const withVal = (xs) => { - let bag; + let bag // check if we have an object with an unnamed control (.value) if (typeof xs === 'object' && xs.value !== undefined) { - bag = { ...xs }; // grab props that are already there - xs = xs.value; // grab the unnamed control for this one - delete bag.value; + bag = { ...xs } // grab props that are already there + xs = xs.value // grab the unnamed control for this one + delete bag.value } if (isMulti && Array.isArray(xs)) { - const result = bag || {}; + const result = bag || {} xs.forEach((x, i) => { if (i < names.length) { - result[names[i]] = x; + result[names[i]] = x } - }); - return result; + }) + return result } else if (bag) { - bag[name] = xs; - return bag; + bag[name] = xs + return bag } else { - return { [name]: xs }; + return { [name]: xs } } - }; + } // todo: make this less confusing const func = function (value, pat) { if (!pat) { - return reify(value).withValue(withVal); + return reify(value).withValue(withVal) } if (typeof value === 'undefined') { - return pat.fmap(withVal); + return pat.fmap(withVal) } - return pat.set(reify(value).withValue(withVal)); - }; + return pat.set(reify(value).withValue(withVal)) + } Pattern.prototype[name] = function (value) { - return func(value, this); - }; - return func; + return func(value, this) + } + return func } // maps control alias names to the "main" control name -const controlAlias = new Map(); +const controlAlias = new Map() export function isControlName(name) { - return controlAlias.has(name); + return controlAlias.has(name) } export function registerControl(names, ...aliases) { - const name = Array.isArray(names) ? names[0] : names; - let bag = {}; - bag[name] = createParam(names); - controlAlias.set(name, name); + const name = Array.isArray(names) ? names[0] : names + let bag = {} + bag[name] = createParam(names) + controlAlias.set(name, name) aliases.forEach((alias) => { - bag[alias] = bag[name]; - controlAlias.set(alias, name); - Pattern.prototype[alias] = Pattern.prototype[name]; - }); - return bag; + bag[alias] = bag[name] + controlAlias.set(alias, name) + Pattern.prototype[alias] = Pattern.prototype[name] + }) + return bag } /** @@ -85,7 +85,7 @@ export function registerControl(names, ...aliases) { * s("bd:0 bd:1 bd:0:0.3 bd:1:1.4") * */ -export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound'); +export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound') /** * Position in the wavetable of the wavetable oscillator @@ -96,7 +96,7 @@ export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound'); * @example * s("squelch").bank("wt_digital").seg(8).note("F1").wt("0 0.25 0.5 0.75 1") */ -export const { wt, wavetablePosition } = registerControl('wt', 'wavetablePosition'); +export const { wt, wavetablePosition } = registerControl('wt', 'wavetablePosition') /** * Amount of envelope applied wavetable oscillator's position envelope @@ -104,7 +104,7 @@ export const { wt, wavetablePosition } = registerControl('wt', 'wavetablePositio * @name wtenv * @param {number | Pattern} amount between 0 and 1 */ -export const { wtenv } = registerControl('wtenv'); +export const { wtenv } = registerControl('wtenv') /** * Attack time of the wavetable oscillator's position envelope * @@ -112,7 +112,7 @@ export const { wtenv } = registerControl('wtenv'); * @synonyms wtatt * @param {number | Pattern} time attack time in seconds */ -export const { wtattack, wtatt } = registerControl('wtattack', 'wtatt'); +export const { wtattack, wtatt } = registerControl('wtattack', 'wtatt') /** * Decay time of the wavetable oscillator's position envelope @@ -121,7 +121,7 @@ export const { wtattack, wtatt } = registerControl('wtattack', 'wtatt'); * @synonyms wtdec * @param {number | Pattern} time decay time in seconds */ -export const { wtdecay, wtdec } = registerControl('wtdecay', 'wtdec'); +export const { wtdecay, wtdec } = registerControl('wtdecay', 'wtdec') /** * Sustain time of the wavetable oscillator's position envelope @@ -130,7 +130,7 @@ export const { wtdecay, wtdec } = registerControl('wtdecay', 'wtdec'); * @synonyms wtsus * @param {number | Pattern} gain sustain level (0 to 1) */ -export const { wtsustain, wtsus } = registerControl('wtsustain', 'wtsus'); +export const { wtsustain, wtsus } = registerControl('wtsustain', 'wtsus') /** * Release time of the wavetable oscillator's position envelope @@ -139,7 +139,7 @@ export const { wtsustain, wtsus } = registerControl('wtsustain', 'wtsus'); * @synonyms wtrel * @param {number | Pattern} time release time in seconds */ -export const { wtrelease, wtrel } = registerControl('wtrelease', 'wtrel'); +export const { wtrelease, wtrel } = registerControl('wtrelease', 'wtrel') /** * Rate of the LFO for the wavetable oscillator's position @@ -147,14 +147,14 @@ export const { wtrelease, wtrel } = registerControl('wtrelease', 'wtrel'); * @name wtrate * @param {number | Pattern} rate rate in hertz */ -export const { wtrate } = registerControl('wtrate'); +export const { wtrate } = registerControl('wtrate') /** * cycle synced rate of the LFO for the wavetable oscillator's position * * @name wtsync * @param {number | Pattern} rate rate in cycles */ -export const { wtsync } = registerControl('wtsync'); +export const { wtsync } = registerControl('wtsync') /** * Depth of the LFO for the wavetable oscillator's position @@ -162,7 +162,7 @@ export const { wtsync } = registerControl('wtsync'); * @name wtdepth * @param {number | Pattern} depth depth of modulation */ -export const { wtdepth } = registerControl('wtdepth'); +export const { wtdepth } = registerControl('wtdepth') /** * Shape of the LFO for the wavetable oscillator's position @@ -170,7 +170,7 @@ export const { wtdepth } = registerControl('wtdepth'); * @name wtshape * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..) */ -export const { wtshape } = registerControl('wtshape'); +export const { wtshape } = registerControl('wtshape') /** * DC offset of the LFO for the wavetable oscillator's position @@ -178,7 +178,7 @@ export const { wtshape } = registerControl('wtshape'); * @name wtdc * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar */ -export const { wtdc } = registerControl('wtdc'); +export const { wtdc } = registerControl('wtdc') /** * Skew of the LFO for the wavetable oscillator's position @@ -186,7 +186,7 @@ export const { wtdc } = registerControl('wtdc'); * @name wtskew * @param {number | Pattern} skew How much to bend the LFO shape */ -export const { wtskew } = registerControl('wtskew'); +export const { wtskew } = registerControl('wtskew') /** * Amount of warp (alteration of the waveform) to apply to the wavetable oscillator @@ -198,7 +198,7 @@ export const { wtskew } = registerControl('wtskew'); * s("basique").bank("wt_digital").seg(8).note("F1").warp("0 0.25 0.5 0.75 1") * .warpmode("spin") */ -export const { warp, wavetableWarp } = registerControl('warp', 'wavetableWarp'); +export const { warp, wavetableWarp } = registerControl('warp', 'wavetableWarp') /** * Attack time of the wavetable oscillator's warp envelope @@ -207,7 +207,7 @@ export const { warp, wavetableWarp } = registerControl('warp', 'wavetableWarp'); * @synonyms warpatt * @param {number | Pattern} time attack time in seconds */ -export const { warpattack, warpatt } = registerControl('warpattack', 'warpatt'); +export const { warpattack, warpatt } = registerControl('warpattack', 'warpatt') /** * Decay time of the wavetable oscillator's warp envelope @@ -216,7 +216,7 @@ export const { warpattack, warpatt } = registerControl('warpattack', 'warpatt'); * @synonyms warpdec * @param {number | Pattern} time decay time in seconds */ -export const { warpdecay, warpdec } = registerControl('warpdecay', 'warpdec'); +export const { warpdecay, warpdec } = registerControl('warpdecay', 'warpdec') /** * Sustain time of the wavetable oscillator's warp envelope @@ -225,7 +225,7 @@ export const { warpdecay, warpdec } = registerControl('warpdecay', 'warpdec'); * @synonyms warpsus * @param {number | Pattern} gain sustain level (0 to 1) */ -export const { warpsustain, warpsus } = registerControl('warpsustain', 'warpsus'); +export const { warpsustain, warpsus } = registerControl('warpsustain', 'warpsus') /** * Release time of the wavetable oscillator's warp envelope @@ -234,7 +234,7 @@ export const { warpsustain, warpsus } = registerControl('warpsustain', 'warpsus' * @synonyms warprel * @param {number | Pattern} time release time in seconds */ -export const { warprelease, warprel } = registerControl('warprelease', 'warprel'); +export const { warprelease, warprel } = registerControl('warprelease', 'warprel') /** * Rate of the LFO for the wavetable oscillator's warp @@ -242,7 +242,7 @@ export const { warprelease, warprel } = registerControl('warprelease', 'warprel' * @name warprate * @param {number | Pattern} rate rate in hertz */ -export const { warprate } = registerControl('warprate'); +export const { warprate } = registerControl('warprate') /** * Depth of the LFO for the wavetable oscillator's warp @@ -250,7 +250,7 @@ export const { warprate } = registerControl('warprate'); * @name warpdepth * @param {number | Pattern} depth depth of modulation */ -export const { warpdepth } = registerControl('warpdepth'); +export const { warpdepth } = registerControl('warpdepth') /** * Shape of the LFO for the wavetable oscillator's warp @@ -258,7 +258,7 @@ export const { warpdepth } = registerControl('warpdepth'); * @name warpshape * @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..) */ -export const { warpshape } = registerControl('warpshape'); +export const { warpshape } = registerControl('warpshape') /** * DC offset of the LFO for the wavetable oscillator's warp @@ -266,7 +266,7 @@ export const { warpshape } = registerControl('warpshape'); * @name warpdc * @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar */ -export const { warpdc } = registerControl('warpdc'); +export const { warpdc } = registerControl('warpdc') /** * Skew of the LFO for the wavetable oscillator's warp @@ -274,7 +274,7 @@ export const { warpdc } = registerControl('warpdc'); * @name warpskew * @param {number | Pattern} skew How much to bend the LFO shape */ -export const { warpskew } = registerControl('warpskew'); +export const { warpskew } = registerControl('warpskew') /** * Type of warp (alteration of the waveform) to apply to the wavetable oscillator. @@ -290,7 +290,7 @@ export const { warpskew } = registerControl('warpskew'); * .warpmode("*2") * */ -export const { warpmode, wavetableWarpMode } = registerControl('warpmode', 'wavetableWarpMode'); +export const { warpmode, wavetableWarpMode } = registerControl('warpmode', 'wavetableWarpMode') /** * Amount of randomness of the initial phase of the wavetable oscillator. @@ -302,7 +302,10 @@ export const { warpmode, wavetableWarpMode } = registerControl('warpmode', 'wave * s("basique").bank("wt_digital").seg(16).wtphaserand("<0 1>") * */ -export const { wtphaserand, wavetablePhaseRand } = registerControl('wtphaserand', 'wavetablePhaseRand'); +export const { wtphaserand, wavetablePhaseRand } = registerControl( + 'wtphaserand', + 'wavetablePhaseRand' +) /** * Amount of envelope applied wavetable oscillator's position envelope @@ -310,7 +313,7 @@ export const { wtphaserand, wavetablePhaseRand } = registerControl('wtphaserand' * @name warpenv * @param {number | Pattern} amount between 0 and 1 */ -export const { warpenv } = registerControl('warpenv'); +export const { warpenv } = registerControl('warpenv') /** * cycle synced rate of the LFO for the wavetable warp position @@ -318,7 +321,7 @@ export const { warpenv } = registerControl('warpenv'); * @name warpsync * @param {number | Pattern} rate rate in cycles */ -export const { warpsync } = registerControl('warpsync'); +export const { warpsync } = registerControl('warpsync') /** * Define a custom webaudio node to use as a sound source. @@ -329,7 +332,7 @@ export const { warpsync } = registerControl('warpsync'); * @synonyms src * */ -export const { source, src } = registerControl('source', 'src'); +export const { source, src } = registerControl('source', 'src') /** * Selects the given index from the sample map. * Numbers too high will wrap around. @@ -341,7 +344,7 @@ export const { source, src } = registerControl('source', 'src'); * s("bd sd [~ bd] sd,hh*6").n("<0 1>") */ // also see https://codeberg.org/uzu/strudel/pulls/63 -export const { n } = registerControl('n'); +export const { n } = registerControl('n') /** * Plays the given note name or midi number. A note name consists of * @@ -363,7 +366,7 @@ export const { n } = registerControl('n'); * @example * note("fbb1 a#0 cbbb-1 e##-2").sound("saw") */ -export const { note } = registerControl(['note', 'n']); +export const { note } = registerControl(['note', 'n']) /** * A pattern of numbers that speed up (or slow down) samples while they play. Currently only supported by osc / superdirt. @@ -375,7 +378,7 @@ export const { note } = registerControl(['note', 'n']); * s("sax").accelerate("<0 1 2 4 8 16>").slow(2).osc() * */ -export const { accelerate } = registerControl('accelerate'); +export const { accelerate } = registerControl('accelerate') /** * Sets the velocity from 0 to 1. Is multiplied together with gain. * @@ -385,7 +388,7 @@ export const { accelerate } = registerControl('accelerate'); * .gain(".4!2 1 .4!2 1 .4 1") * .velocity(".4 1") */ -export const { velocity } = registerControl('velocity'); +export const { velocity } = registerControl('velocity') /** * Controls the gain by an exponential amount. * @@ -395,7 +398,7 @@ export const { velocity } = registerControl('velocity'); * s("hh*8").gain(".4!2 1 .4!2 1 .4 1").fast(2) * */ -export const { gain } = registerControl('gain'); +export const { gain } = registerControl('gain') /** * Gain applied after all effects have been processed. * @@ -405,7 +408,7 @@ export const { gain } = registerControl('gain'); * .compressor("-20:20:10:.002:.02").postgain(1.5) * */ -export const { postgain } = registerControl('postgain'); +export const { postgain } = registerControl('postgain') /** * Like `gain`, but linear. * @@ -416,7 +419,7 @@ export const { postgain } = registerControl('postgain'); * s("bd*8").amp(".1*2 .5 .1*2 .5 .1 .5").osc() * */ -export const { amp } = registerControl('amp'); +export const { amp } = registerControl('amp') /** * Amplitude envelope attack time: Specifies how long it takes for the sound to reach its peak value, relative to the onset. * @@ -427,7 +430,7 @@ export const { amp } = registerControl('amp'); * note("c3 e3 f3 g3").attack("<0 .1 .5>") * */ -export const { attack, att } = registerControl('attack', 'att'); +export const { attack, att } = registerControl('attack', 'att') /** * Sets the Frequency Modulation Harmonicity Ratio. @@ -444,7 +447,7 @@ export const { attack, att } = registerControl('attack', 'att'); * ._scope() * */ -export const { fmh } = registerControl(['fmh', 'fmi'], 'fmh'); +export const { fmh } = registerControl(['fmh', 'fmi'], 'fmh') /** * Sets the Frequency Modulation of the synth. * Controls the modulation index, which defines the brightness of the sound. @@ -458,7 +461,7 @@ export const { fmh } = registerControl(['fmh', 'fmi'], 'fmh'); * ._scope() * */ -export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm'); +export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm') // fm envelope /** * Ramp type of fm envelope. Exp might be a bit broken.. @@ -474,7 +477,7 @@ export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm'); * ._scope() * */ -export const { fmenv } = registerControl('fmenv'); +export const { fmenv } = registerControl('fmenv') /** * Attack time for the FM envelope: time it takes to reach maximum modulation * @@ -487,7 +490,7 @@ export const { fmenv } = registerControl('fmenv'); * ._scope() * */ -export const { fmattack } = registerControl('fmattack'); +export const { fmattack } = registerControl('fmattack') /** * Waveform of the fm modulator @@ -500,7 +503,7 @@ export const { fmattack } = registerControl('fmattack'); * n("0 1 2 3".fast(4)).chord("").voicing().s("sawtooth").fmwave("brown").fm(.6) * */ -export const { fmwave } = registerControl('fmwave'); +export const { fmwave } = registerControl('fmwave') /** * Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase. @@ -515,7 +518,7 @@ export const { fmwave } = registerControl('fmwave'); * ._scope() * */ -export const { fmdecay } = registerControl('fmdecay'); +export const { fmdecay } = registerControl('fmdecay') /** * Sustain level for the FM envelope: how much modulation is applied after the decay phase * @@ -529,10 +532,10 @@ export const { fmdecay } = registerControl('fmdecay'); * ._scope() * */ -export const { fmsustain } = registerControl('fmsustain'); +export const { fmsustain } = registerControl('fmsustain') // these are not really useful... skipping for now -export const { fmrelease } = registerControl('fmrelease'); -export const { fmvelocity } = registerControl('fmvelocity'); +export const { fmrelease } = registerControl('fmrelease') +export const { fmvelocity } = registerControl('fmvelocity') /** * Select the sound bank to use. To be used together with `s`. The bank name (+ "_") will be prepended to the value of `s`. @@ -543,7 +546,7 @@ export const { fmvelocity } = registerControl('fmvelocity'); * s("bd sd [~ bd] sd").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd") * */ -export const { bank } = registerControl('bank'); +export const { bank } = registerControl('bank') /** * mix control for the chorus effect @@ -554,12 +557,12 @@ export const { bank } = registerControl('bank'); * note("d d a# a").s("sawtooth").chorus(.5) * */ -export const { chorus } = registerControl('chorus'); +export const { chorus } = registerControl('chorus') // analyser node send amount 0 - 1 (used by scope) -export const { analyze } = registerControl('analyze'); +export const { analyze } = registerControl('analyze') // fftSize of analyser -export const { fft } = registerControl('fft'); +export const { fft } = registerControl('fft') /** * Amplitude envelope decay time: the time it takes after the attack time to reach the sustain level. @@ -572,7 +575,7 @@ export const { fft } = registerControl('fft'); * note("c3 e3 f3 g3").decay("<.1 .2 .3 .4>").sustain(0) * */ -export const { decay, dec } = registerControl('decay', 'dec'); +export const { decay, dec } = registerControl('decay', 'dec') /** * Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset. * @@ -583,7 +586,7 @@ export const { decay, dec } = registerControl('decay', 'dec'); * note("c3 e3 f3 g3").decay(.2).sustain("<0 .1 .4 .6 1>") * */ -export const { sustain, sus } = registerControl('sustain', 'sus'); +export const { sustain, sus } = registerControl('sustain', 'sus') /** * Amplitude envelope release time: The time it takes after the offset to go from sustain level to zero. * @@ -594,8 +597,8 @@ export const { sustain, sus } = registerControl('sustain', 'sus'); * note("c3 e3 g3 c4").release("<0 .1 .4 .6 1>/2") * */ -export const { release, rel } = registerControl('release', 'rel'); -export const { hold } = registerControl('hold'); +export const { release, rel } = registerControl('release', 'rel') +export const { hold } = registerControl('hold') // TODO: in tidal, it seems to be normalized /** * Sets the center frequency of the **b**and-**p**ass **f**ilter. When using mininotation, you @@ -608,7 +611,7 @@ export const { hold } = registerControl('hold'); * s("bd sd [~ bd] sd,hh*6").bpf("<1000 2000 4000 8000>") * */ -export const { bandf, bpf, bp } = registerControl(['bandf', 'bandq', 'bpenv'], 'bpf', 'bp'); +export const { bandf, bpf, bp } = registerControl(['bandf', 'bandq', 'bpenv'], 'bpf', 'bp') // TODO: in tidal, it seems to be normalized /** * Sets the **b**and-**p**ass **q**-factor (resonance). @@ -622,7 +625,7 @@ export const { bandf, bpf, bp } = registerControl(['bandf', 'bandq', 'bpenv'], ' */ // currently an alias of 'bandq' https://codeberg.org/uzu/strudel/issues/496 // ['bpq'], -export const { bandq, bpq } = registerControl('bandq', 'bpq'); +export const { bandq, bpq } = registerControl('bandq', 'bpq') /** * A pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample. * @@ -634,7 +637,7 @@ export const { bandq, bpq } = registerControl('bandq', 'bpq'); * s("rave").begin("<0 .25 .5 .75>").fast(2) * */ -export const { begin } = registerControl('begin'); +export const { begin } = registerControl('begin') /** * The same as .begin, but cuts off the end off each sample. * @@ -645,7 +648,7 @@ export const { begin } = registerControl('begin'); * s("bd*2,oh*4").end("<.1 .2 .5 1>").fast(2) * */ -export const { end } = registerControl('end'); +export const { end } = registerControl('end') /** * Loops the sample. * Note that the tempo of the loop is not synced with the cycle tempo. @@ -657,7 +660,7 @@ export const { end } = registerControl('end'); * s("casio").loop(1) * */ -export const { loop } = registerControl('loop'); +export const { loop } = registerControl('loop') /** * Begin to loop at a specific point in the sample (inbetween `begin` and `end`). * Note that the loop point must be inbetween `begin` and `end`, and before `loopEnd`! @@ -670,7 +673,7 @@ export const { loop } = registerControl('loop'); * s("space").loop(1) * .loopBegin("<0 .125 .25>")._scope() */ -export const { loopBegin, loopb } = registerControl('loopBegin', 'loopb'); +export const { loopBegin, loopb } = registerControl('loopBegin', 'loopb') /** * * End the looping section at a specific point in the sample (inbetween `begin` and `end`). @@ -683,7 +686,7 @@ export const { loopBegin, loopb } = registerControl('loopBegin', 'loopb'); * s("space").loop(1) * .loopEnd("<1 .75 .5 .25>")._scope() */ -export const { loopEnd, loope } = registerControl('loopEnd', 'loope'); +export const { loopEnd, loope } = registerControl('loopEnd', 'loope') /** * Bit crusher effect. * @@ -694,7 +697,7 @@ export const { loopEnd, loope } = registerControl('loopEnd', 'loope'); * */ // ['clhatdecay'], -export const { crush } = registerControl('crush'); +export const { crush } = registerControl('crush') /** * Fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers * @@ -704,7 +707,7 @@ export const { crush } = registerControl('crush'); * s("bd sd [~ bd] sd,hh*8").coarse("<1 4 8 16 32>") * */ -export const { coarse } = registerControl('coarse'); +export const { coarse } = registerControl('coarse') /** * Modulate the amplitude of a sound with a continuous waveform @@ -716,7 +719,10 @@ export const { coarse } = registerControl('coarse'); * note("d d d# d".fast(4)).s("supersaw").tremolo("<3 2 100> ").tremoloskew("<.5>") * */ -export const { tremolo } = registerControl(['tremolo', 'tremolodepth', 'tremoloskew', 'tremolophase'], 'trem'); +export const { tremolo } = registerControl( + ['tremolo', 'tremolodepth', 'tremoloskew', 'tremolophase'], + 'trem' +) /** * Modulate the amplitude of a sound with a continuous waveform @@ -730,8 +736,8 @@ export const { tremolo } = registerControl(['tremolo', 'tremolodepth', 'tremolos */ export const { tremolosync } = registerControl( ['tremolosync', 'tremolodepth', 'tremoloskew', 'tremolophase'], - 'tremsync', -); + 'tremsync' +) /** * Depth of amplitude modulation @@ -743,7 +749,7 @@ export const { tremolosync } = registerControl( * note("a1 a1 a#1 a1".fast(4)).s("pulse").tremsync(4).tremolodepth("<1 2 .7>") * */ -export const { tremolodepth } = registerControl('tremolodepth', 'tremdepth'); +export const { tremolodepth } = registerControl('tremolodepth', 'tremdepth') /** * Alter the shape of the modulation waveform * @@ -754,7 +760,7 @@ export const { tremolodepth } = registerControl('tremolodepth', 'tremdepth'); * note("{f a c e}%16").s("sawtooth").tremsync(4).tremoloskew("<.5 0 1>") * */ -export const { tremoloskew } = registerControl('tremoloskew', 'tremskew'); +export const { tremoloskew } = registerControl('tremoloskew', 'tremskew') /** * Alter the phase of the modulation waveform @@ -766,7 +772,7 @@ export const { tremoloskew } = registerControl('tremoloskew', 'tremskew'); * note("{f a c e}%16").s("sawtooth").tremsync(4).tremolophase("<0 .25 .66>") * */ -export const { tremolophase } = registerControl('tremolophase', 'tremphase'); +export const { tremolophase } = registerControl('tremolophase', 'tremphase') /** * Shape of amplitude modulation @@ -778,7 +784,7 @@ export const { tremolophase } = registerControl('tremolophase', 'tremphase'); * note("{f g c d}%16").tremsync(4).tremoloshape("").s("sawtooth") * */ -export const { tremoloshape } = registerControl('tremoloshape', 'tremshape'); +export const { tremoloshape } = registerControl('tremoloshape', 'tremshape') /** * Filter overdrive for supported filter types * @@ -788,7 +794,7 @@ export const { tremoloshape } = registerControl('tremoloshape', 'tremshape'); * note("{f g g c d a a#}%16".sub(17)).s("supersaw").lpenv(8).lpf(150).lpq(.8).ftype('ladder').drive("<.5 4>") * */ -export const { drive } = registerControl('drive'); +export const { drive } = registerControl('drive') /** * Modulate the amplitude of an orbit to create a "sidechain" like effect. @@ -807,7 +813,7 @@ export const { drive } = registerControl('drive'); * $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:3").duckattack(0.2).duckdepth(1) * */ -export const { duck } = registerControl('duckorbit', 'duck'); +export const { duck } = registerControl('duckorbit', 'duck') /** * The amount of ducking applied to target orbit @@ -825,7 +831,7 @@ export const { duck } = registerControl('duckorbit', 'duck'); * $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:3").duckattack(0.2).duckdepth("1:0.5") * */ -export const { duckdepth } = registerControl('duckdepth'); +export const { duckdepth } = registerControl('duckdepth') /** * The time required for the ducked signal(s) to reach their lowest volume. @@ -853,7 +859,7 @@ export const { duckdepth } = registerControl('duckdepth'); * ducker: s("bd*4").bank("tr909").duckorbit("4:7").duckonset("0.3:0.003").duckattack(0.25) * */ -export const { duckonset } = registerControl('duckonset', 'duckons'); +export const { duckonset } = registerControl('duckonset', 'duckons') /** * The time required for the ducked signal(s) to return to their normal volume. @@ -874,7 +880,7 @@ export const { duckonset } = registerControl('duckonset', 'duckons'); * ducker: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:5").duckattack("0.4:0.1") * */ -export const { duckattack } = registerControl('duckattack', 'duckatt'); +export const { duckattack } = registerControl('duckattack', 'duckatt') /** * Create byte beats with custom expressions @@ -887,7 +893,7 @@ export const { duckattack } = registerControl('duckattack', 'duckatt'); * s("bytebeat").bbexpr('t*(t>>15^t>>66)') * */ -export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr'); +export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr') /** * Create byte beats with custom expressions @@ -900,7 +906,7 @@ export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpressio * 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'); +export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime', 'bbst') /** * Allows you to set the output channels on the interface @@ -913,7 +919,7 @@ export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime', * note("e a d b g").channels("3:4") * */ -export const { channels, ch } = registerControl('channels', 'ch'); +export const { channels, ch } = registerControl('channels', 'ch') /** * Controls the pulsewidth of the pulse oscillator @@ -925,7 +931,7 @@ export const { channels, ch } = registerControl('channels', 'ch'); * @example * n(run(8)).scale("D:pentatonic").s("pulse").pw("0 .75 .5 1") */ -export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']); +export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']) /** * Controls the lfo rate for the pulsewidth of the pulse oscillator @@ -937,7 +943,7 @@ export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']); * */ -export const { pwrate } = registerControl('pwrate'); +export const { pwrate } = registerControl('pwrate') /** * Controls the lfo sweep for the pulsewidth of the pulse oscillator @@ -948,7 +954,7 @@ export const { pwrate } = registerControl('pwrate'); * n(run(8)).scale("D:pentatonic").s("pulse").pw("0.5").pwrate("<5 .1 25>").pwsweep("<0.3 .8>") * */ -export const { pwsweep } = registerControl('pwsweep'); +export const { pwsweep } = registerControl('pwsweep') /** * Phaser audio effect that approximates popular guitar pedals. @@ -964,8 +970,8 @@ export const { pwsweep } = registerControl('pwsweep'); export const { phaserrate, ph, phaser } = registerControl( ['phaserrate', 'phaserdepth', 'phasercenter', 'phasersweep'], 'ph', - 'phaser', -); + 'phaser' +) /** * The frequency sweep range of the lfo for the phaser effect. Defaults to 2000 @@ -978,7 +984,7 @@ export const { phaserrate, ph, phaser } = registerControl( * .phaser(2).phasersweep("<800 2000 4000>") * */ -export const { phasersweep, phs } = registerControl('phasersweep', 'phs'); +export const { phasersweep, phs } = registerControl('phasersweep', 'phs') /** * The center frequency of the phaser in HZ. Defaults to 1000 @@ -992,7 +998,7 @@ export const { phasersweep, phs } = registerControl('phasersweep', 'phs'); * */ -export const { phasercenter, phc } = registerControl('phasercenter', 'phc'); +export const { phasercenter, phc } = registerControl('phasercenter', 'phc') /** * The amount the signal is affected by the phaser effect. Defaults to 0.75 @@ -1006,7 +1012,7 @@ export const { phasercenter, phc } = registerControl('phasercenter', 'phc'); * */ // also a superdirt control -export const { phaserdepth, phd, phasdp } = registerControl('phaserdepth', 'phd', 'phasdp'); +export const { phaserdepth, phd, phasdp } = registerControl('phaserdepth', 'phd', 'phasdp') /** * Choose the channel the pattern is sent to in superdirt @@ -1015,7 +1021,7 @@ export const { phaserdepth, phd, phasdp } = registerControl('phaserdepth', 'phd' * @param {number | Pattern} channel channel number * */ -export const { channel } = registerControl('channel'); +export const { channel } = registerControl('channel') /** * In the style of classic drum-machines, `cut` will stop a playing sample as soon as another samples with in same cutgroup is to be played. An example would be an open hi-hat followed by a closed one, essentially muting the open. * @@ -1025,7 +1031,7 @@ export const { channel } = registerControl('channel'); * s("[oh hh]*4").cut(1) * */ -export const { cut } = registerControl('cut'); +export const { cut } = registerControl('cut') /** * Applies the cutoff frequency of the **l**ow-**p**ass **f**ilter. * @@ -1040,7 +1046,12 @@ export const { cut } = registerControl('cut'); * s("bd*16").lpf("1000:0 1000:10 1000:20 1000:30") * */ -export const { cutoff, ctf, lpf, lp } = registerControl(['cutoff', 'resonance', 'lpenv'], 'ctf', 'lpf', 'lp'); +export const { cutoff, ctf, lpf, lp } = registerControl( + ['cutoff', 'resonance', 'lpenv'], + 'ctf', + 'lpf', + 'lp' +) /** * Sets the lowpass filter envelope modulation depth. @@ -1054,7 +1065,7 @@ export const { cutoff, ctf, lpf, lp } = registerControl(['cutoff', 'resonance', * .lpa(.5) * .lpenv("<4 2 1 0 -1 -2 -4>/4") */ -export const { lpenv, lpe } = registerControl('lpenv', 'lpe'); +export const { lpenv, lpe } = registerControl('lpenv', 'lpe') /** * Sets the highpass filter envelope modulation depth. * @name hpenv @@ -1067,7 +1078,7 @@ export const { lpenv, lpe } = registerControl('lpenv', 'lpe'); * .hpa(.5) * .hpenv("<4 2 1 0 -1 -2 -4>/4") */ -export const { hpenv, hpe } = registerControl('hpenv', 'hpe'); +export const { hpenv, hpe } = registerControl('hpenv', 'hpe') /** * Sets the bandpass filter envelope modulation depth. * @name bpenv @@ -1080,7 +1091,7 @@ export const { hpenv, hpe } = registerControl('hpenv', 'hpe'); * .bpa(.5) * .bpenv("<4 2 1 0 -1 -2 -4>/4") */ -export const { bpenv, bpe } = registerControl('bpenv', 'bpe'); +export const { bpenv, bpe } = registerControl('bpenv', 'bpe') /** * Sets the attack duration for the lowpass filter envelope. * @name lpattack @@ -1093,7 +1104,7 @@ export const { bpenv, bpe } = registerControl('bpenv', 'bpe'); * .lpa("<.5 .25 .1 .01>/4") * .lpenv(4) */ -export const { lpattack, lpa } = registerControl('lpattack', 'lpa'); +export const { lpattack, lpa } = registerControl('lpattack', 'lpa') /** * Sets the attack duration for the highpass filter envelope. * @name hpattack @@ -1106,7 +1117,7 @@ export const { lpattack, lpa } = registerControl('lpattack', 'lpa'); * .hpa("<.5 .25 .1 .01>/4") * .hpenv(4) */ -export const { hpattack, hpa } = registerControl('hpattack', 'hpa'); +export const { hpattack, hpa } = registerControl('hpattack', 'hpa') /** * Sets the attack duration for the bandpass filter envelope. * @name bpattack @@ -1119,7 +1130,7 @@ export const { hpattack, hpa } = registerControl('hpattack', 'hpa'); * .bpa("<.5 .25 .1 .01>/4") * .bpenv(4) */ -export const { bpattack, bpa } = registerControl('bpattack', 'bpa'); +export const { bpattack, bpa } = registerControl('bpattack', 'bpa') /** * Sets the decay duration for the lowpass filter envelope. * @name lpdecay @@ -1132,7 +1143,7 @@ export const { bpattack, bpa } = registerControl('bpattack', 'bpa'); * .lpd("<.5 .25 .1 0>/4") * .lpenv(4) */ -export const { lpdecay, lpd } = registerControl('lpdecay', 'lpd'); +export const { lpdecay, lpd } = registerControl('lpdecay', 'lpd') /** * Sets the decay duration for the highpass filter envelope. * @name hpdecay @@ -1146,7 +1157,7 @@ export const { lpdecay, lpd } = registerControl('lpdecay', 'lpd'); * .hps(0.2) * .hpenv(4) */ -export const { hpdecay, hpd } = registerControl('hpdecay', 'hpd'); +export const { hpdecay, hpd } = registerControl('hpdecay', 'hpd') /** * Sets the decay duration for the bandpass filter envelope. * @name bpdecay @@ -1160,7 +1171,7 @@ export const { hpdecay, hpd } = registerControl('hpdecay', 'hpd'); * .bps(0.2) * .bpenv(4) */ -export const { bpdecay, bpd } = registerControl('bpdecay', 'bpd'); +export const { bpdecay, bpd } = registerControl('bpdecay', 'bpd') /** * Sets the sustain amplitude for the lowpass filter envelope. * @name lpsustain @@ -1174,7 +1185,7 @@ export const { bpdecay, bpd } = registerControl('bpdecay', 'bpd'); * .lps("<0 .25 .5 1>/4") * .lpenv(4) */ -export const { lpsustain, lps } = registerControl('lpsustain', 'lps'); +export const { lpsustain, lps } = registerControl('lpsustain', 'lps') /** * Sets the sustain amplitude for the highpass filter envelope. * @name hpsustain @@ -1188,7 +1199,7 @@ export const { lpsustain, lps } = registerControl('lpsustain', 'lps'); * .hps("<0 .25 .5 1>/4") * .hpenv(4) */ -export const { hpsustain, hps } = registerControl('hpsustain', 'hps'); +export const { hpsustain, hps } = registerControl('hpsustain', 'hps') /** * Sets the sustain amplitude for the bandpass filter envelope. * @name bpsustain @@ -1202,7 +1213,7 @@ export const { hpsustain, hps } = registerControl('hpsustain', 'hps'); * .bps("<0 .25 .5 1>/4") * .bpenv(4) */ -export const { bpsustain, bps } = registerControl('bpsustain', 'bps'); +export const { bpsustain, bps } = registerControl('bpsustain', 'bps') /** * Sets the release time for the lowpass filter envelope. * @name lprelease @@ -1217,7 +1228,7 @@ export const { bpsustain, bps } = registerControl('bpsustain', 'bps'); * .lpr("<.5 .25 .1 0>/4") * .release(.5) */ -export const { lprelease, lpr } = registerControl('lprelease', 'lpr'); +export const { lprelease, lpr } = registerControl('lprelease', 'lpr') /** * Sets the release time for the highpass filter envelope. * @name hprelease @@ -1232,7 +1243,7 @@ export const { lprelease, lpr } = registerControl('lprelease', 'lpr'); * .hpr("<.5 .25 .1 0>/4") * .release(.5) */ -export const { hprelease, hpr } = registerControl('hprelease', 'hpr'); +export const { hprelease, hpr } = registerControl('hprelease', 'hpr') /** * Sets the release time for the bandpass filter envelope. * @name bprelease @@ -1247,7 +1258,7 @@ export const { hprelease, hpr } = registerControl('hprelease', 'hpr'); * .bpr("<.5 .25 .1 0>/4") * .release(.5) */ -export const { bprelease, bpr } = registerControl('bprelease', 'bpr'); +export const { bprelease, bpr } = registerControl('bprelease', 'bpr') /** * Sets the filter type. The ladder filter is more aggressive. More types might be added in the future. * @name ftype @@ -1261,7 +1272,7 @@ export const { bprelease, bpr } = registerControl('bprelease', 'bpr'); * .lpenv(3).lpq(1) * .ftype("") */ -export const { ftype } = registerControl('ftype'); +export const { ftype } = registerControl('ftype') /** * controls the center of the filter envelope. 0 is unipolar positive, .5 is bipolar, 1 is unipolar negative @@ -1271,7 +1282,7 @@ export const { ftype } = registerControl('ftype'); * note("{f g g c d a a#}%8").s("sawtooth").lpf("{1000}%2") * .lpenv(8).fanchor("<0 .5 1>") */ -export const { fanchor } = registerControl('fanchor'); +export const { fanchor } = registerControl('fanchor') /** * Applies the cutoff frequency of the **h**igh-**p**ass **f**ilter. * @@ -1304,7 +1315,7 @@ export const { fanchor } = registerControl('fanchor'); * .vib("<.5 1 2 4 8 16>:12") * ._scope() */ -export const { vib, vibrato, v } = registerControl(['vib', 'vibmod'], 'vibrato', 'v'); +export const { vib, vibrato, v } = registerControl(['vib', 'vibmod'], 'vibrato', 'v') /** * Adds pink noise to the mix * @@ -1313,7 +1324,7 @@ export const { vib, vibrato, v } = registerControl(['vib', 'vibmod'], 'vibrato', * @example * sound("/2") */ -export const { noise } = registerControl('noise'); +export const { noise } = registerControl('noise') /** * Sets the vibrato depth in semitones. Only has an effect if `vibrato` | `vib` | `v` is is also set * @@ -1330,8 +1341,8 @@ export const { noise } = registerControl('noise'); * .vibmod("<.25 .5 1 2 12>:8") * ._scope() */ -export const { vibmod, vmod } = registerControl(['vibmod', 'vib'], 'vmod'); -export const { hcutoff, hpf, hp } = registerControl(['hcutoff', 'hresonance', 'hpenv'], 'hpf', 'hp'); +export const { vibmod, vmod } = registerControl(['vibmod', 'vib'], 'vmod') +export const { hcutoff, hpf, hp } = registerControl(['hcutoff', 'hresonance', 'hpenv'], 'hpf', 'hp') /** * Controls the **h**igh-**p**ass **q**-value. * @@ -1342,7 +1353,7 @@ export const { hcutoff, hpf, hp } = registerControl(['hcutoff', 'hresonance', 'h * s("bd sd [~ bd] sd,hh*8").hpf(2000).hpq("<0 10 20 30>") * */ -export const { hresonance, hpq } = registerControl('hresonance', 'hpq'); +export const { hresonance, hpq } = registerControl('hresonance', 'hpq') /** * Controls the **l**ow-**p**ass **q**-value. * @@ -1354,7 +1365,7 @@ export const { hresonance, hpq } = registerControl('hresonance', 'hpq'); * */ // currently an alias of 'resonance' https://codeberg.org/uzu/strudel/issues/496 -export const { resonance, lpq } = registerControl('resonance', 'lpq'); +export const { resonance, lpq } = registerControl('resonance', 'lpq') /** * DJ filter, below 0.5 is low pass filter, above is high pass filter. * @@ -1364,7 +1375,7 @@ export const { resonance, lpq } = registerControl('resonance', 'lpq'); * n(irand(16).seg(8)).scale("d:phrygian").s("supersaw").djf("<.5 .3 .2 .75>") * */ -export const { djf } = registerControl('djf'); +export const { djf } = registerControl('djf') // ['cutoffegint'], // TODO: does not seem to work /** @@ -1382,7 +1393,7 @@ export const { djf } = registerControl('djf'); * s("bd bd").delay("0.65:0.25:0.9 0.65:0.125:0.7") * */ -export const { delay } = registerControl(['delay', 'delaytime', 'delayfeedback']); +export const { delay } = registerControl(['delay', 'delaytime', 'delayfeedback']) /** * Sets the level of the signal that is fed back into the delay. * Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it @@ -1394,7 +1405,7 @@ export const { delay } = registerControl(['delay', 'delaytime', 'delayfeedback'] * s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>") * */ -export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback', 'delayfb', 'dfb'); +export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback', 'delayfb', 'dfb') /** * Sets the level of the signal that is fed back into the delay. @@ -1407,7 +1418,7 @@ export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback', * s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>") * */ -export const { delayspeed } = registerControl('delayspeed'); +export const { delayspeed } = registerControl('delayspeed') /** * Sets the time of the delay effect. * @@ -1418,7 +1429,7 @@ export const { delayspeed } = registerControl('delayspeed'); * note("d d a# a".fast(2)).s("sawtooth").delay(.8).delaytime(1/2).delayspeed("<2 .5 -1 -2>") * */ -export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', 'dt'); +export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', 'dt') /** * Sets the time of the delay effect in cycles. @@ -1430,7 +1441,7 @@ export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', * s("bd bd").delay(.25).delaysync("<1 2 3 5>".div(8)) * */ -export const { delaysync } = registerControl('delaysync'); +export const { delaysync } = registerControl('delaysync') /** * Specifies whether delaytime is calculated relative to cps. @@ -1444,7 +1455,7 @@ export const { delaysync } = registerControl('delaysync'); * */ -export const { lock } = registerControl('lock'); +export const { lock } = registerControl('lock') /** * Set detune for stacked voices of supported oscillators * @@ -1455,7 +1466,7 @@ export const { lock } = registerControl('lock'); * note("d f a a# a d3").fast(2).s("supersaw").detune("<.1 .2 .5 24.1>") * */ -export const { detune, det } = registerControl('detune', 'det'); +export const { detune, det } = registerControl('detune', 'det') /** * Set number of stacked voices for supported oscillators * @@ -1465,7 +1476,7 @@ export const { detune, det } = registerControl('detune', 'det'); * note("d f a a# a d3").fast(2).s("supersaw").unison("<1 2 7>") * */ -export const { unison } = registerControl('unison'); +export const { unison } = registerControl('unison') /** * Set the stereo pan spread for supported oscillators @@ -1476,7 +1487,7 @@ export const { unison } = registerControl('unison'); * note("d f a a# a d3").fast(2).s("supersaw").spread("<0 .3 1>") * */ -export const { spread } = registerControl('spread'); +export const { spread } = registerControl('spread') /** * Set dryness of reverb. See `room` and `size` for more information about reverb. * @@ -1487,7 +1498,7 @@ export const { spread } = registerControl('spread'); * @superdirtOnly * */ -export const { dry } = registerControl('dry'); +export const { dry } = registerControl('dry') // TODO: does not seem to do anything /* * Used when using `begin`/`end` or `chop`/`striate` and friends, to change the fade out time of the 'grain' envelope. @@ -1499,9 +1510,9 @@ export const { dry } = registerControl('dry'); * s("oh*4").end(.1).fadeTime("<0 .2 .4 .8>").osc() * */ -export const { fadeTime, fadeOutTime } = registerControl('fadeTime', 'fadeOutTime'); +export const { fadeTime, fadeOutTime } = registerControl('fadeTime', 'fadeOutTime') // TODO: see above -export const { fadeInTime } = registerControl('fadeInTime'); +export const { fadeInTime } = registerControl('fadeInTime') /** * Set frequency of sound. * @@ -1513,7 +1524,7 @@ export const { fadeInTime } = registerControl('fadeInTime'); * freq("110".mul.out(".5 1.5 .6 [2 3]")).s("superzow").osc() * */ -export const { freq } = registerControl('freq'); +export const { freq } = registerControl('freq') // pitch envelope /** * Attack time of pitch envelope. @@ -1525,7 +1536,7 @@ export const { freq } = registerControl('freq'); * note("c eb g bb").pattack("0 .1 .25 .5").slow(2) * */ -export const { pattack, patt } = registerControl('pattack', 'patt'); +export const { pattack, patt } = registerControl('pattack', 'patt') /** * Decay time of pitch envelope. * @@ -1536,9 +1547,9 @@ export const { pattack, patt } = registerControl('pattack', 'patt'); * note("").pdecay("<0 .1 .25 .5>") * */ -export const { pdecay, pdec } = registerControl('pdecay', 'pdec'); +export const { pdecay, pdec } = registerControl('pdecay', 'pdec') // TODO: how to use psustain?! -export const { psustain, psus } = registerControl('psustain', 'psus'); +export const { psustain, psus } = registerControl('psustain', 'psus') /** * Release time of pitch envelope * @@ -1551,7 +1562,7 @@ export const { psustain, psus } = registerControl('psustain', 'psus'); * .prelease("<0 .1 .25 .5>") * */ -export const { prelease, prel } = registerControl('prelease', 'prel'); +export const { prelease, prel } = registerControl('prelease', 'prel') /** * Amount of pitch envelope. Negative values will flip the envelope. * If you don't set other pitch envelope controls, `pattack:.2` will be the default. @@ -1563,7 +1574,7 @@ export const { prelease, prel } = registerControl('prelease', 'prel'); * .penv("<12 7 1 .5 0 -1 -7 -12>") * */ -export const { penv } = registerControl('penv'); +export const { penv } = registerControl('penv') /** * Curve of envelope. Defaults to linear. exponential is good for kicks * @@ -1576,7 +1587,7 @@ export const { penv } = registerControl('penv'); * .pcurve("<0 1>") * */ -export const { pcurve } = registerControl('pcurve'); +export const { pcurve } = registerControl('pcurve') /** * Sets the range anchor of the envelope: * - anchor 0: range = [note, note + penv] @@ -1589,9 +1600,9 @@ export const { pcurve } = registerControl('pcurve'); * note("c c4").penv(12).panchor("<0 .5 1 .5>") * */ -export const { panchor } = registerControl('panchor'); +export const { panchor } = registerControl('panchor') // TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate -export const { gate, gat } = registerControl('gate', 'gat'); +export const { gate, gat } = registerControl('gate', 'gat') // ['hatgrain'], // ['lagogo'], // ['lclap'], @@ -1611,7 +1622,7 @@ export const { gate, gat } = registerControl('gate', 'gat'); * @superdirtOnly * */ -export const { leslie } = registerControl('leslie'); +export const { leslie } = registerControl('leslie') /** * Rate of modulation / rotation for leslie effect * @@ -1623,7 +1634,7 @@ export const { leslie } = registerControl('leslie'); * */ // TODO: the rate seems to "lag" (in the example, 1 will be fast) -export const { lrate } = registerControl('lrate'); +export const { lrate } = registerControl('lrate') /** * Physical size of the cabinet in meters. Be careful, it might be slightly larger than your computer. Affects the Doppler amount (pitch warble) * @@ -1634,15 +1645,15 @@ export const { lrate } = registerControl('lrate'); * @superdirtOnly * */ -export const { lsize } = registerControl('lsize'); +export const { lsize } = registerControl('lsize') /** * Sets the displayed text for an event on the pianoroll * * @name label * @param {string} label text to display */ -export const { activeLabel } = registerControl('activeLabel'); -export const { label } = registerControl(['label', 'activeLabel']); +export const { activeLabel } = registerControl('activeLabel') +export const { label } = registerControl(['label', 'activeLabel']) // ['lfo'], // ['lfocutoffint'], // ['lfodelay'], @@ -1656,20 +1667,20 @@ export const { label } = registerControl(['label', 'activeLabel']); // ['lophat'], // ['lsnare'], // TODO: what is this? not found in tidal doc -export const { degree } = registerControl('degree'); +export const { degree } = registerControl('degree') // TODO: what is this? not found in tidal doc -export const { mtranspose } = registerControl('mtranspose'); +export const { mtranspose } = registerControl('mtranspose') // TODO: what is this? not found in tidal doc -export const { ctranspose } = registerControl('ctranspose'); +export const { ctranspose } = registerControl('ctranspose') // TODO: what is this? not found in tidal doc -export const { harmonic } = registerControl('harmonic'); +export const { harmonic } = registerControl('harmonic') // TODO: what is this? not found in tidal doc -export const { stepsPerOctave } = registerControl('stepsPerOctave'); +export const { stepsPerOctave } = registerControl('stepsPerOctave') // TODO: what is this? not found in tidal doc -export const { octaveR } = registerControl('octaveR'); +export const { octaveR } = registerControl('octaveR') // TODO: why is this needed? what's the difference to late / early? Answer: it's in seconds, and delays the message at // OSC time (so can't be negative, at least not beyond the latency value) -export const { nudge } = registerControl('nudge'); +export const { nudge } = registerControl('nudge') // TODO: the following doc is just a guess, it's not documented in tidal doc. /** * Sets the default octave of a synth. @@ -1680,7 +1691,7 @@ export const { nudge } = registerControl('nudge'); * n("0,4,7").s('supersquare').octave("<3 4 5 6>").osc() * @superDirtOnly */ -export const { octave } = registerControl('octave'); +export const { octave } = registerControl('octave') // ['ophatdecay'], // TODO: example @@ -1695,11 +1706,11 @@ export const { octave } = registerControl('octave'); * s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2) * ) */ -export const { orbit } = registerControl('orbit'); +export const { orbit } = registerControl('orbit') // TODO: what is this? not found in tidal doc Answer: gain is limited to maximum of 2. This allows you to go over that -export const { overgain } = registerControl('overgain'); +export const { overgain } = registerControl('overgain') // TODO: what is this? not found in tidal doc. Similar to above, but limited to 1 -export const { overshape } = registerControl('overshape'); +export const { overshape } = registerControl('overshape') /** * Sets position in stereo. * @@ -1711,7 +1722,7 @@ export const { overshape } = registerControl('overshape'); * s("bd rim sd rim bd ~ cp rim").pan(sine.slow(2)) * */ -export const { pan } = registerControl('pan'); +export const { pan } = registerControl('pan') // TODO: this has no effect (see example) /* * Controls how much multichannel output is fanned out @@ -1722,7 +1733,7 @@ export const { pan } = registerControl('pan'); * s("[bd hh]*2").pan("<.5 1 .5 0>").panspan("<0 .5 1>").osc() * */ -export const { panspan } = registerControl('panspan'); +export const { panspan } = registerControl('panspan') // TODO: this has no effect (see example) /* * Controls how much multichannel output is spread @@ -1733,35 +1744,35 @@ export const { panspan } = registerControl('panspan'); * s("[bd hh]*2").pan("<.5 1 .5 0>").pansplay("<0 .5 1>").osc() * */ -export const { pansplay } = registerControl('pansplay'); -export const { panwidth } = registerControl('panwidth'); -export const { panorient } = registerControl('panorient'); +export const { pansplay } = registerControl('pansplay') +export const { panwidth } = registerControl('panwidth') +export const { panorient } = registerControl('panorient') // ['pitch1'], // ['pitch2'], // ['pitch3'], // ['portamento'], // TODO: LFO rate see https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare -export const { rate } = registerControl('rate'); +export const { rate } = registerControl('rate') // TODO: slide param for certain synths -export const { slide } = registerControl('slide'); +export const { slide } = registerControl('slide') // TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare -export const { semitone } = registerControl('semitone'); +export const { semitone } = registerControl('semitone') // TODO: synth param -export const { voice } = registerControl('voice'); +export const { voice } = registerControl('voice') // voicings // https://codeberg.org/uzu/strudel/issues/506 // chord to voice, like C Eb Fm7 G7. the symbols can be defined via addVoicings -export const { chord } = registerControl('chord'); +export const { chord } = registerControl('chord') // which dictionary to use for the voicings -export const { dictionary, dict } = registerControl('dictionary', 'dict'); +export const { dictionary, dict } = registerControl('dictionary', 'dict') // the top note to align the voicing to, defaults to c5 -export const { anchor } = registerControl('anchor'); +export const { anchor } = registerControl('anchor') // how the voicing is offset from the anchored position -export const { offset } = registerControl('offset'); +export const { offset } = registerControl('offset') // how many octaves are voicing steps spread apart, defaults to 1 -export const { octaves } = registerControl('octaves'); +export const { octaves } = registerControl('octaves') // below = anchor note will be removed from the voicing, useful for melody harmonization -export const { mode } = registerControl(['mode', 'anchor']); +export const { mode } = registerControl(['mode', 'anchor']) /** * Sets the level of reverb. @@ -1776,7 +1787,7 @@ export const { mode } = registerControl(['mode', 'anchor']); * s("bd sd [~ bd] sd").room("<0.9:1 0.9:4>") * */ -export const { room } = registerControl(['room', 'size']); +export const { room } = registerControl(['room', 'size']) /** * Reverb lowpass starting frequency (in hertz). * When this property is changed, the reverb will be recaculated, so only change this sparsely.. @@ -1789,7 +1800,7 @@ export const { room } = registerControl(['room', 'size']); * @example * s("bd sd [~ bd] sd").room(0.5).rlp(5000) */ -export const { roomlp, rlp } = registerControl('roomlp', 'rlp'); +export const { roomlp, rlp } = registerControl('roomlp', 'rlp') /** * Reverb lowpass frequency at -60dB (in hertz). * When this property is changed, the reverb will be recaculated, so only change this sparsely.. @@ -1803,7 +1814,7 @@ export const { roomlp, rlp } = registerControl('roomlp', 'rlp'); * s("bd sd [~ bd] sd").room(0.5).rlp(5000).rdim(400) * */ -export const { roomdim, rdim } = registerControl('roomdim', 'rdim'); +export const { roomdim, rdim } = registerControl('roomdim', 'rdim') /** * Reverb fade time (in seconds). * When this property is changed, the reverb will be recaculated, so only change this sparsely.. @@ -1817,7 +1828,7 @@ export const { roomdim, rdim } = registerControl('roomdim', 'rdim'); * s("bd sd [~ bd] sd").room(0.5).rlp(5000).rfade(4) * */ -export const { roomfade, rfade } = registerControl('roomfade', 'rfade'); +export const { roomfade, rfade } = registerControl('roomfade', 'rfade') /** * Sets the sample to use as an impulse response for the reverb. * @name iresponse @@ -1827,7 +1838,7 @@ export const { roomfade, rfade } = registerControl('roomfade', 'rfade'); * s("bd sd [~ bd] sd").room(.8).ir("") * */ -export const { ir, iresponse } = registerControl(['ir', 'i'], 'iresponse'); +export const { ir, iresponse } = registerControl(['ir', 'i'], 'iresponse') /** * Sets speed of the sample for the impulse response. @@ -1838,7 +1849,7 @@ export const { ir, iresponse } = registerControl(['ir', 'i'], 'iresponse'); * $: s("brk/2").fit().scrub(irand(16).div(16).seg(8)).ir("swpad:4").room(.2).irspeed("<2 1 .5>/2").irbegin(.5).roomsize(.5) * */ -export const { irspeed } = registerControl('irspeed'); +export const { irspeed } = registerControl('irspeed') /** * Sets the beginning of the IR response sample @@ -1850,7 +1861,7 @@ export const { irspeed } = registerControl('irspeed'); * $: s("brk/2").fit().scrub(irand(16).div(16).seg(8)).ir("swpad:4").room(.65).irspeed("-2").irbegin("<0 .5 .75>/2").roomsize(.6) * */ -export const { irbegin } = registerControl('irbegin'); +export const { irbegin } = registerControl('irbegin') /** * Sets the room size of the reverb, see `room`. * When this property is changed, the reverb will be recaculated, so only change this sparsely.. @@ -1867,7 +1878,7 @@ export const { irbegin } = registerControl('irbegin'); // TODO: find out why : // s("bd sd [~ bd] sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc() // .. does not work. Is it because room is only one effect? -export const { roomsize, size, sz, rsize } = registerControl('roomsize', 'size', 'sz', 'rsize'); +export const { roomsize, size, sz, rsize } = registerControl('roomsize', 'size', 'sz', 'rsize') // ['sagogo'], // ['sclap'], // ['sclaves'], @@ -1884,7 +1895,7 @@ export const { roomsize, size, sz, rsize } = registerControl('roomsize', 'size', * s("bd sd [~ bd] sd,hh*8").shape("<0 .2 .4 .6 .8>") * */ -export const { shape } = registerControl(['shape', 'shapevol']); +export const { shape } = registerControl(['shape', 'shapevol']) /** * Wave shaping distortion. CAUTION: it can get loud. * Second option in optional array syntax (ex: ".9:.5") applies a postgain to the output. Third option sets the waveshaping type. @@ -1903,7 +1914,7 @@ export const { shape } = registerControl(['shape', 'shapevol']); * s("bd:4*4").bank("tr808").distort("3:0.5:diode") * */ -export const { distort, dist } = registerControl(['distort', 'distortvol', 'distorttype'], 'dist'); +export const { distort, dist } = registerControl(['distort', 'distortvol', 'distorttype'], 'dist') /** * Postgain for waveshaping distortion. @@ -1914,7 +1925,7 @@ export const { distort, dist } = registerControl(['distort', 'distortvol', 'dist * @example * s("bd*4").bank("tr909").distort(2).distortvol(0.8) */ -export const { distortvol } = registerControl('distortvol', 'distvol'); +export const { distortvol } = registerControl('distortvol', 'distvol') /** * Type of waveshaping distortion to apply. @@ -1931,7 +1942,7 @@ export const { distortvol } = registerControl('distortvol', 'distvol'); * .distort(rand.range(1, 8)) * .distorttype("") */ -export const { distorttype } = registerControl('distorttype', 'disttype'); +export const { distorttype } = registerControl('distorttype', 'disttype') /** * Dynamics Compressor. The params are `compressor("threshold:ratio:knee:attack:release")` @@ -1949,11 +1960,11 @@ export const { compressor } = registerControl([ 'compressorKnee', 'compressorAttack', 'compressorRelease', -]); -export const { compressorKnee } = registerControl('compressorKnee'); -export const { compressorRatio } = registerControl('compressorRatio'); -export const { compressorAttack } = registerControl('compressorAttack'); -export const { compressorRelease } = registerControl('compressorRelease'); +]) +export const { compressorKnee } = registerControl('compressorKnee') +export const { compressorRatio } = registerControl('compressorRatio') +export const { compressorAttack } = registerControl('compressorAttack') +export const { compressorRelease } = registerControl('compressorRelease') /** * Changes the speed of sample playback, i.e. a cheap way of changing pitch. * @@ -1965,7 +1976,7 @@ export const { compressorRelease } = registerControl('compressorRelease'); * speed("1 1.5*2 [2 1.1]").s("piano").clip(1) * */ -export const { speed } = registerControl('speed'); +export const { speed } = registerControl('speed') /** * Changes the speed of sample playback, i.e. a cheap way of changing pitch. @@ -1976,7 +1987,7 @@ export const { speed } = registerControl('speed'); * s("gm_flute").stretch("1 2 .5") * */ -export const { stretch } = registerControl('stretch'); +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`. * @@ -1988,7 +1999,7 @@ export const { stretch } = registerControl('stretch'); * */ -export const { unit } = registerControl('unit'); +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: * @@ -2001,7 +2012,7 @@ export const { unit } = registerControl('unit'); * @superdirtOnly * */ -export const { squiz } = registerControl('squiz'); +export const { squiz } = registerControl('squiz') // TODO: what is this? not found in tidal doc // ['stutterdepth'], // TODO: what is this? not found in tidal doc @@ -2027,7 +2038,7 @@ export const { squiz } = registerControl('squiz'); * s("bd sd mt ht bd [~ cp] ht lt").vowel("[a|e|i|o|u]") * */ -export const { vowel } = registerControl('vowel'); +export const { vowel } = registerControl('vowel') /* // TODO: find out how it works * Made by Calum Gunn. Divides an audio stream into tiny segments, using the signal's zero-crossings as segment boundaries, and discards a fraction of them. Takes a number between 1 and 100, denoted the percentage of segments to drop. The SuperCollider manual describes the Waveloss effect this way: * @@ -2038,7 +2049,7 @@ export const { vowel } = registerControl('vowel'); * * @name waveloss */ -export const { waveloss } = registerControl('waveloss'); +export const { waveloss } = registerControl('waveloss') /** * Noise crackle density * @@ -2048,47 +2059,47 @@ export const { waveloss } = registerControl('waveloss'); * s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4)) * */ -export const { density } = registerControl('density'); +export const { density } = registerControl('density') // ['modwheel'], -export const { expression } = registerControl('expression'); -export const { sustainpedal } = registerControl('sustainpedal'); +export const { expression } = registerControl('expression') +export const { sustainpedal } = registerControl('sustainpedal') -export const { fshift } = registerControl('fshift'); -export const { fshiftnote } = registerControl('fshiftnote'); -export const { fshiftphase } = registerControl('fshiftphase'); +export const { fshift } = registerControl('fshift') +export const { fshiftnote } = registerControl('fshiftnote') +export const { fshiftphase } = registerControl('fshiftphase') -export const { triode } = registerControl('triode'); -export const { krush } = registerControl('krush'); -export const { kcutoff } = registerControl('kcutoff'); -export const { octer } = registerControl('octer'); -export const { octersub } = registerControl('octersub'); -export const { octersubsub } = registerControl('octersubsub'); -export const { ring } = registerControl('ring'); -export const { ringf } = registerControl('ringf'); -export const { ringdf } = registerControl('ringdf'); -export const { freeze } = registerControl('freeze'); -export const { xsdelay } = registerControl('xsdelay'); -export const { tsdelay } = registerControl('tsdelay'); -export const { real } = registerControl('real'); -export const { imag } = registerControl('imag'); -export const { enhance } = registerControl('enhance'); -export const { partials } = registerControl('partials'); -export const { comb } = registerControl('comb'); -export const { smear } = registerControl('smear'); -export const { scram } = registerControl('scram'); -export const { binshift } = registerControl('binshift'); -export const { hbrick } = registerControl('hbrick'); -export const { lbrick } = registerControl('lbrick'); +export const { triode } = registerControl('triode') +export const { krush } = registerControl('krush') +export const { kcutoff } = registerControl('kcutoff') +export const { octer } = registerControl('octer') +export const { octersub } = registerControl('octersub') +export const { octersubsub } = registerControl('octersubsub') +export const { ring } = registerControl('ring') +export const { ringf } = registerControl('ringf') +export const { ringdf } = registerControl('ringdf') +export const { freeze } = registerControl('freeze') +export const { xsdelay } = registerControl('xsdelay') +export const { tsdelay } = registerControl('tsdelay') +export const { real } = registerControl('real') +export const { imag } = registerControl('imag') +export const { enhance } = registerControl('enhance') +export const { partials } = registerControl('partials') +export const { comb } = registerControl('comb') +export const { smear } = registerControl('smear') +export const { scram } = registerControl('scram') +export const { binshift } = registerControl('binshift') +export const { hbrick } = registerControl('hbrick') +export const { lbrick } = registerControl('lbrick') -export const { frameRate } = registerControl('frameRate'); -export const { frames } = registerControl('frames'); -export const { hours } = registerControl('hours'); -export const { minutes } = registerControl('minutes'); -export const { seconds } = registerControl('seconds'); -export const { songPtr } = registerControl('songPtr'); -export const { uid } = registerControl('uid'); -export const { val } = registerControl('val'); -export const { cps } = registerControl('cps'); +export const { frameRate } = registerControl('frameRate') +export const { frames } = registerControl('frames') +export const { hours } = registerControl('hours') +export const { minutes } = registerControl('minutes') +export const { seconds } = registerControl('seconds') +export const { songPtr } = registerControl('songPtr') +export const { uid } = registerControl('uid') +export const { val } = registerControl('val') +export const { cps } = registerControl('cps') /** * Multiplies the duration with the given number. Also cuts samples off at the end if they exceed the duration. * @@ -2099,7 +2110,7 @@ export const { cps } = registerControl('cps'); * note("c a f e").s("piano").clip("<.5 1 2>") * */ -export const { clip, legato } = registerControl('clip', 'legato'); +export const { clip, legato } = registerControl('clip', 'legato') /** * Sets the duration of the event in cycles. Similar to clip / legato, it also cuts samples off at the end if they exceed the duration. @@ -2111,24 +2122,24 @@ export const { clip, legato } = registerControl('clip', 'legato'); * note("c a f e").s("piano").dur("<.5 1 2>") * */ -export const { duration, dur } = registerControl('duration', 'dur'); +export const { duration, dur } = registerControl('duration', 'dur') // ZZFX -export const { zrand } = registerControl('zrand'); -export const { curve } = registerControl('curve'); +export const { zrand } = registerControl('zrand') +export const { curve } = registerControl('curve') // superdirt duplicate // export const {slide]} = registerControl('slide']); -export const { deltaSlide } = registerControl('deltaSlide'); -export const { pitchJump } = registerControl('pitchJump'); -export const { pitchJumpTime } = registerControl('pitchJumpTime'); -export const { lfo, repeatTime } = registerControl('lfo', 'repeatTime'); +export const { deltaSlide } = registerControl('deltaSlide') +export const { pitchJump } = registerControl('pitchJump') +export const { pitchJumpTime } = registerControl('pitchJumpTime') +export const { lfo, repeatTime } = registerControl('lfo', 'repeatTime') // noise on the frequency or as bubo calls it "frequency fog" :) -export const { znoise } = registerControl('znoise'); -export const { zmod } = registerControl('zmod'); +export const { znoise } = registerControl('znoise') +export const { zmod } = registerControl('zmod') // like crush but scaled differently -export const { zcrush } = registerControl('zcrush'); -export const { zdelay } = registerControl('zdelay'); -export const { zzfx } = registerControl('zzfx'); +export const { zcrush } = registerControl('zcrush') +export const { zdelay } = registerControl('zdelay') +export const { zzfx } = registerControl('zzfx') /** * Sets the color of the hap in visualizations like pianoroll or highlighting. @@ -2136,12 +2147,12 @@ export const { zzfx } = registerControl('zzfx'); * @synonyms colour * @param {string} color Hexadecimal or CSS color name */ -export const { color, colour } = registerControl(['color', 'colour']); +export const { color, colour } = registerControl(['color', 'colour']) // TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13 export let createParams = (...names) => - names.reduce((acc, name) => Object.assign(acc, { [name]: createParam(name) }), {}); + names.reduce((acc, name) => Object.assign(acc, { [name]: createParam(name) }), {}) /** * ADSR envelope: Combination of Attack, Decay, Sustain, and Release. @@ -2155,25 +2166,25 @@ export let createParams = (...names) => * note("[c3 bb2 f3 eb3]*2").sound("sawtooth").lpf(600).adsr(".1:.1:.5:.2") */ export const adsr = register('adsr', (adsr, pat) => { - adsr = !Array.isArray(adsr) ? [adsr] : adsr; - const [attack, decay, sustain, release] = adsr; - return pat.set({ attack, decay, sustain, release }); -}); + adsr = !Array.isArray(adsr) ? [adsr] : adsr + const [attack, decay, sustain, release] = adsr + return pat.set({ attack, decay, sustain, release }) +}) export const ad = register('ad', (t, pat) => { - t = !Array.isArray(t) ? [t] : t; - const [attack, decay = attack] = t; - return pat.attack(attack).decay(decay); -}); + t = !Array.isArray(t) ? [t] : t + const [attack, decay = attack] = t + return pat.attack(attack).decay(decay) +}) export const ds = register('ds', (t, pat) => { - t = !Array.isArray(t) ? [t] : t; - const [decay, sustain = 0] = t; - return pat.set({ decay, sustain }); -}); + t = !Array.isArray(t) ? [t] : t + const [decay, sustain = 0] = t + return pat.set({ decay, sustain }) +}) export const ar = register('ar', (t, pat) => { - t = !Array.isArray(t) ? [t] : t; - const [attack, release = attack] = t; - return pat.set({ attack, release }); -}); + t = !Array.isArray(t) ? [t] : t + const [attack, release = attack] = t + return pat.set({ attack, release }) +}) //MIDI @@ -2185,9 +2196,9 @@ export const ar = register('ar', (t, pat) => { * @example * note("c4").midichan(1).midi() */ -export const { midichan } = registerControl('midichan'); +export const { midichan } = registerControl('midichan') -export const { midimap } = registerControl('midimap'); +export const { midimap } = registerControl('midimap') /** * MIDI port: Sets the MIDI port for the event. @@ -2197,7 +2208,7 @@ export const { midimap } = registerControl('midimap'); * @example * note("c a f e").midiport("<0 1 2 3>").midi() */ -export const { midiport } = registerControl('midiport'); +export const { midiport } = registerControl('midiport') /** * MIDI command: Sends a MIDI command message. @@ -2207,7 +2218,7 @@ export const { midiport } = registerControl('midiport'); * @example * midicmd("clock*48,/2").midi() */ -export const { midicmd } = registerControl('midicmd'); +export const { midicmd } = registerControl('midicmd') /** * MIDI control: Sends a MIDI control change message. @@ -2218,11 +2229,11 @@ export const { midicmd } = registerControl('midicmd'); */ export const control = register('control', (args, pat) => { if (!Array.isArray(args)) { - throw new Error('control expects an array of [ccn, ccv]'); + throw new Error('control expects an array of [ccn, ccv]') } - const [_ccn, _ccv] = args; - return pat.ccn(_ccn).ccv(_ccv); -}); + const [_ccn, _ccv] = args + return pat.ccn(_ccn).ccv(_ccv) +}) /** * MIDI control number: Sends a MIDI control change message. @@ -2230,15 +2241,15 @@ export const control = register('control', (args, pat) => { * @name ccn * @param {number | Pattern} MIDI control number (0-127) */ -export const { ccn } = registerControl('ccn'); +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'); +export const { ccv } = registerControl('ccv') +export const { ctlNum } = registerControl('ctlNum') // TODO: ctlVal? /** @@ -2248,7 +2259,7 @@ export const { ctlNum } = registerControl('ctlNum'); * @example * note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi() */ -export const { nrpnn } = registerControl('nrpnn'); +export const { nrpnn } = registerControl('nrpnn') /** * MIDI NRPN non-registered parameter value: Sends a MIDI NRPN non-registered parameter value message. * @name nrpv @@ -2256,7 +2267,7 @@ export const { nrpnn } = registerControl('nrpnn'); * @example * note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi() */ -export const { nrpv } = registerControl('nrpv'); +export const { nrpv } = registerControl('nrpv') /** * MIDI program number: Sends a MIDI program change message. @@ -2266,7 +2277,7 @@ export const { nrpv } = registerControl('nrpv'); * @example * note("c4").progNum(10).midichan(1).midi() */ -export const { progNum } = registerControl('progNum'); +export const { progNum } = registerControl('progNum') /** * MIDI sysex: Sends a MIDI sysex message. @@ -2278,11 +2289,11 @@ export const { progNum } = registerControl('progNum'); */ export const sysex = register('sysex', (args, pat) => { if (!Array.isArray(args)) { - throw new Error('sysex expects an array of [id, data]'); + throw new Error('sysex expects an array of [id, data]') } - const [id, data] = args; - return pat.sysexid(id).sysexdata(data); -}); + const [id, data] = args + return pat.sysexid(id).sysexdata(data) +}) /** * MIDI sysex ID: Sends a MIDI sysex identifier message. * @name sysexid @@ -2290,7 +2301,7 @@ export const sysex = register('sysex', (args, pat) => { * @example * note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi() */ -export const { sysexid } = registerControl('sysexid'); +export const { sysexid } = registerControl('sysexid') /** * MIDI sysex data: Sends a MIDI sysex message. * @name sysexdata @@ -2298,7 +2309,7 @@ export const { sysexid } = registerControl('sysexid'); * @example * note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi() */ -export const { sysexdata } = registerControl('sysexdata'); +export const { sysexdata } = registerControl('sysexdata') /** * MIDI pitch bend: Sends a MIDI pitch bend message. @@ -2307,7 +2318,7 @@ export const { sysexdata } = registerControl('sysexdata'); * @example * note("c4").midibend(sine.slow(4).range(-0.4,0.4)).midi() */ -export const { midibend } = registerControl('midibend'); +export const { midibend } = registerControl('midibend') /** * MIDI key after touch: Sends a MIDI key after touch message. * @name miditouch @@ -2315,17 +2326,17 @@ export const { midibend } = registerControl('midibend'); * @example * note("c4").miditouch(sine.slow(4).range(0,1)).midi() */ -export const { miditouch } = registerControl('miditouch'); +export const { miditouch } = registerControl('miditouch') // TODO: what is this? -export const { polyTouch } = registerControl('polyTouch'); +export const { polyTouch } = registerControl('polyTouch') export const getControlName = (alias) => { if (controlAlias.has(alias)) { - return controlAlias.get(alias); + return controlAlias.get(alias) } - return alias; -}; + return alias +} /** * Sets properties in a batch. @@ -2338,13 +2349,13 @@ export const getControlName = (alias) => { * "{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]; + 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; - }); -}); + 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 @@ -2365,12 +2376,12 @@ export const scrub = register( (beginPat, pat) => { return beginPat.outerBind((v) => { if (!Array.isArray(v)) { - v = [v]; + v = [v] } - const [beginVal, speedMultiplier = 1] = v; + const [beginVal, speedMultiplier = 1] = v - return pat.begin(beginVal).mul(speed(speedMultiplier)).clip(1); - }); + return pat.begin(beginVal).mul(speed(speedMultiplier)).clip(1) + }) }, - false, -); + false +) diff --git a/src/strudel/core/cyclist.mjs b/src/strudel/core/cyclist.mjs index 59e410c..a06a8ea 100644 --- a/src/strudel/core/cyclist.mjs +++ b/src/strudel/core/cyclist.mjs @@ -4,8 +4,8 @@ Copyright (C) 2022 Strudel contributors - see . */ -import createClock from './zyklus.mjs'; -import { errorLogger, logger } from './logger.mjs'; +import createClock from './zyklus.mjs' +import { errorLogger, logger } from './logger.mjs' export class Cyclist { constructor({ @@ -19,122 +19,126 @@ export class Cyclist { clearInterval, beforeStart, }) { - this.started = false; - this.beforeStart = beforeStart; - this.cps = 0.5; - this.num_ticks_since_cps_change = 0; - this.lastTick = 0; // absolute time when last tick (clock callback) happened - this.lastBegin = 0; // query begin of last tick - this.lastEnd = 0; // query end of last tick - this.getTime = getTime; // get absolute time - this.num_cycles_at_cps_change = 0; - this.seconds_at_cps_change; // clock phase when cps was changed - this.onToggle = onToggle; - this.latency = latency; // fixed trigger time offset + this.started = false + this.beforeStart = beforeStart + this.cps = 0.5 + this.num_ticks_since_cps_change = 0 + this.lastTick = 0 // absolute time when last tick (clock callback) happened + this.lastBegin = 0 // query begin of last tick + this.lastEnd = 0 // query end of last tick + this.getTime = getTime // get absolute time + this.num_cycles_at_cps_change = 0 + this.seconds_at_cps_change // clock phase when cps was changed + this.onToggle = onToggle + this.latency = latency // fixed trigger time offset this.clock = createClock( getTime, // called slightly before each cycle (phase, duration, _, t) => { if (this.num_ticks_since_cps_change === 0) { - this.num_cycles_at_cps_change = this.lastEnd; - this.seconds_at_cps_change = phase; + this.num_cycles_at_cps_change = this.lastEnd + this.seconds_at_cps_change = phase } - this.num_ticks_since_cps_change++; - const seconds_since_cps_change = this.num_ticks_since_cps_change * duration; - const num_cycles_since_cps_change = seconds_since_cps_change * this.cps; + this.num_ticks_since_cps_change++ + const seconds_since_cps_change = this.num_ticks_since_cps_change * duration + const num_cycles_since_cps_change = seconds_since_cps_change * this.cps try { - const begin = this.lastEnd; - this.lastBegin = begin; - const end = this.num_cycles_at_cps_change + num_cycles_since_cps_change; - this.lastEnd = end; - this.lastTick = phase; + const begin = this.lastEnd + this.lastBegin = begin + const end = this.num_cycles_at_cps_change + num_cycles_since_cps_change + this.lastEnd = end + this.lastTick = phase if (phase < t) { // avoid querying haps that are in the past anyway - console.log(`skip query: too late`); - return; + console.log(`skip query: too late`) + return } // query the pattern for events - const haps = this.pattern.queryArc(begin, end, { _cps: this.cps, cyclist: 'cyclist' }); + const haps = this.pattern.queryArc(begin, end, { _cps: this.cps, cyclist: 'cyclist' }) haps.forEach((hap) => { if (hap.hasOnset()) { const targetTime = - (hap.whole.begin - this.num_cycles_at_cps_change) / this.cps + this.seconds_at_cps_change + latency; - const duration = hap.duration / this.cps; + (hap.whole.begin - this.num_cycles_at_cps_change) / this.cps + + this.seconds_at_cps_change + + latency + const duration = hap.duration / this.cps // the following line is dumb and only here for backwards compatibility // see https://codeberg.org/uzu/strudel/pulls/1004 - const deadline = targetTime - phase; + const deadline = targetTime - phase // this onTrigger has another signature - onTrigger?.(hap, deadline, duration, this.cps, targetTime); + 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; + this.cps = hap.value.cps + this.num_ticks_since_cps_change = 0 } } - }); + }) } catch (e) { - errorLogger(e); - onError?.(e); + errorLogger(e) + onError?.(e) } }, interval, // duration of each cycle 0.1, 0.1, setInterval, - clearInterval, - ); + clearInterval + ) } now() { if (!this.started) { - return 0; + return 0 } - const secondsSinceLastTick = this.getTime() - this.lastTick - this.clock.duration; - return this.lastBegin + secondsSinceLastTick * this.cps; // + this.clock.minLatency; + const secondsSinceLastTick = this.getTime() - this.lastTick - this.clock.duration + return this.lastBegin + secondsSinceLastTick * this.cps // + this.clock.minLatency; } setStarted(v) { - this.started = v; - this.onToggle?.(v); + this.started = v + this.onToggle?.(v) } async start() { - await this.beforeStart?.(); - this.num_ticks_since_cps_change = 0; - this.num_cycles_at_cps_change = 0; + await this.beforeStart?.() + this.num_ticks_since_cps_change = 0 + this.num_cycles_at_cps_change = 0 if (!this.pattern) { - throw new Error('Scheduler: no pattern set! call .setPattern first.'); + throw new Error('Scheduler: no pattern set! call .setPattern first.') } - logger('[cyclist] start'); - this.clock.start(); - this.setStarted(true); + logger('[cyclist] start') + this.clock.start() + this.setStarted(true) } pause() { - logger('[cyclist] pause'); - this.clock.pause(); - this.setStarted(false); + logger('[cyclist] pause') + this.clock.pause() + this.setStarted(false) } stop() { - logger('[cyclist] stop'); - this.clock.stop(); - this.lastEnd = 0; - this.setStarted(false); + logger('[cyclist] stop') + this.clock.stop() + this.lastEnd = 0 + this.setStarted(false) } async setPattern(pat, autostart = false) { - this.pattern = pat; + this.pattern = pat if (autostart && !this.started) { - await this.start(); + await this.start() } } setCps(cps = 0.5) { if (this.cps === cps) { - return; + return } - this.cps = cps; - this.num_ticks_since_cps_change = 0; + this.cps = cps + this.num_ticks_since_cps_change = 0 } log(begin, end, haps) { - const onsets = haps.filter((h) => h.hasOnset()); - console.log(`${begin.toFixed(4)} - ${end.toFixed(4)} ${Array(onsets.length).fill('I').join('')}`); + const onsets = haps.filter((h) => h.hasOnset()) + console.log( + `${begin.toFixed(4)} - ${end.toFixed(4)} ${Array(onsets.length).fill('I').join('')}` + ) } } diff --git a/src/strudel/core/drawLine.mjs b/src/strudel/core/drawLine.mjs index 7509c0f..79a8c97 100644 --- a/src/strudel/core/drawLine.mjs +++ b/src/strudel/core/drawLine.mjs @@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see . */ -import Fraction, { gcd } from './fraction.mjs'; +import Fraction, { gcd } from './fraction.mjs' /** * Intended for a debugging, drawLine renders the pattern as a string, where each character represents the same time span. @@ -24,39 +24,39 @@ import Fraction, { gcd } from './fraction.mjs'; * silence; */ function drawLine(pat, chars = 60) { - let cycle = 0; - let pos = Fraction(0); - let lines = ['']; - let emptyLine = ''; // this will be the "reference" empty line, which will be copied into extra lines + let cycle = 0 + let pos = Fraction(0) + let lines = [''] + let emptyLine = '' // this will be the "reference" empty line, which will be copied into extra lines while (lines[0].length < chars) { - const haps = pat.queryArc(cycle, cycle + 1); - const durations = haps.filter((hap) => hap.hasOnset()).map((hap) => hap.duration); - const charFraction = gcd(...durations); - const totalSlots = charFraction.inverse(); // number of character slots for the current cycle - lines = lines.map((line) => line + '|'); // add pipe character before each cycle - emptyLine += '|'; + const haps = pat.queryArc(cycle, cycle + 1) + const durations = haps.filter((hap) => hap.hasOnset()).map((hap) => hap.duration) + const charFraction = gcd(...durations) + const totalSlots = charFraction.inverse() // number of character slots for the current cycle + lines = lines.map((line) => line + '|') // add pipe character before each cycle + emptyLine += '|' for (let i = 0; i < totalSlots; i++) { - const [begin, end] = [pos, pos.add(charFraction)]; - const matches = haps.filter((hap) => hap.whole.begin.lte(begin) && hap.whole.end.gte(end)); - const missingLines = matches.length - lines.length; + const [begin, end] = [pos, pos.add(charFraction)] + const matches = haps.filter((hap) => hap.whole.begin.lte(begin) && hap.whole.end.gte(end)) + const missingLines = matches.length - lines.length if (missingLines > 0) { - lines = lines.concat(Array(missingLines).fill(emptyLine)); + lines = lines.concat(Array(missingLines).fill(emptyLine)) } lines = lines.map((line, i) => { - const hap = matches[i]; + const hap = matches[i] if (hap) { - const isOnset = hap.whole.begin.eq(begin); - const char = isOnset ? '' + hap.value : '-'; - return line + char; + const isOnset = hap.whole.begin.eq(begin) + const char = isOnset ? '' + hap.value : '-' + return line + char } - return line + '.'; - }); - emptyLine += '.'; - pos = pos.add(charFraction); + return line + '.' + }) + emptyLine += '.' + pos = pos.add(charFraction) } - cycle++; + cycle++ } - return lines.join('\n'); + return lines.join('\n') } -export default drawLine; +export default drawLine diff --git a/src/strudel/core/euclid.mjs b/src/strudel/core/euclid.mjs index 44ab07f..5cd93da 100644 --- a/src/strudel/core/euclid.mjs +++ b/src/strudel/core/euclid.mjs @@ -10,46 +10,46 @@ https://rohandrape.net/?t=hmt 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 . */ -import { timeCat, register, silence, stack, pure, _morph } from './pattern.mjs'; -import { rotate, flatten, splitAt, zipWith } from './util.mjs'; -import Fraction, { lcm } from './fraction.mjs'; +import { timeCat, register, silence, stack, pure, _morph } from './pattern.mjs' +import { rotate, flatten, splitAt, zipWith } from './util.mjs' +import Fraction, { lcm } from './fraction.mjs' const left = function (n, x) { - const [ons, offs] = n; - const [xs, ys] = x; - const [_xs, __xs] = splitAt(offs, xs); + const [ons, offs] = n + const [xs, ys] = x + const [_xs, __xs] = splitAt(offs, xs) return [ [offs, ons - offs], [zipWith((a, b) => a.concat(b), _xs, ys), __xs], - ]; -}; + ] +} const right = function (n, x) { - const [ons, offs] = n; - const [xs, ys] = x; - const [_ys, __ys] = splitAt(ons, ys); + const [ons, offs] = n + const [xs, ys] = x + const [_ys, __ys] = splitAt(ons, ys) const result = [ [ons, offs - ons], [zipWith((a, b) => a.concat(b), xs, _ys), __ys], - ]; - return result; -}; + ] + return result +} const _bjork = function (n, x) { - const [ons, offs] = n; - return Math.min(ons, offs) <= 1 ? [n, x] : _bjork(...(ons > offs ? left(n, x) : right(n, x))); -}; + const [ons, offs] = n + return Math.min(ons, offs) <= 1 ? [n, x] : _bjork(...(ons > offs ? left(n, x) : right(n, x))) +} export const bjork = function (ons, steps) { - const inverted = ons < 0; - const absOns = Math.abs(ons); - const offs = steps - absOns; - const ones = Array(absOns).fill([1]); - const zeros = Array(offs).fill([0]); - const result = _bjork([absOns, offs], [ones, zeros]); - const pattern = flatten(result[1][0]).concat(flatten(result[1][1])); - return inverted ? pattern.map((x) => 1 - x) : pattern; -}; + const inverted = ons < 0 + const absOns = Math.abs(ons) + const offs = steps - absOns + const ones = Array(absOns).fill([1]) + const zeros = Array(offs).fill([0]) + const result = _bjork([absOns, offs], [ones, zeros]) + const pattern = flatten(result[1][0]).concat(flatten(result[1][1])) + return inverted ? pattern.map((x) => 1 - x) : pattern +} /** * Changes the structure of the pattern to form an Euclidean rhythm. @@ -128,28 +128,31 @@ export const bjork = function (ons, steps) { */ const _euclidRot = function (pulses, steps, rotation) { - const b = bjork(pulses, steps); + const b = bjork(pulses, steps) if (rotation) { - return rotate(b, -rotation); + return rotate(b, -rotation) } - return b; -}; + return b +} export const euclid = register('euclid', function (pulses, steps, pat) { - return pat.struct(_euclidRot(pulses, steps, 0)); -}); + return pat.struct(_euclidRot(pulses, steps, 0)) +}) export const e = register('e', function (euc, pat) { if (!Array.isArray(euc)) { - euc = [euc]; + euc = [euc] } - const [pulses, steps = pulses, rot = 0] = euc; - return pat.struct(_euclidRot(pulses, steps, rot)); -}); + const [pulses, steps = pulses, rot = 0] = euc + return pat.struct(_euclidRot(pulses, steps, rot)) +}) -export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], function (pulses, steps, rotation, pat) { - return pat.struct(_euclidRot(pulses, steps, rotation)); -}); +export const { euclidrot, euclidRot } = register( + ['euclidrot', 'euclidRot'], + function (pulses, steps, rotation, pat) { + return pat.struct(_euclidRot(pulses, steps, rotation)) + } +) /** * Similar to `euclid`, but each pulse is held until the next pulse, @@ -166,20 +169,20 @@ export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], fun const _euclidLegato = function (pulses, steps, rotation, pat) { if (pulses < 1) { - return silence; + return silence } - const bin_pat = _euclidRot(pulses, steps, 0); + const bin_pat = _euclidRot(pulses, steps, 0) const gapless = bin_pat .join('') .split('1') .slice(1) - .map((s) => [s.length + 1, true]); - return pat.struct(timeCat(...gapless)).late(Fraction(rotation).div(steps)); -}; + .map((s) => [s.length + 1, true]) + return pat.struct(timeCat(...gapless)).late(Fraction(rotation).div(steps)) +} export const euclidLegato = register(['euclidLegato'], function (pulses, steps, pat) { - return _euclidLegato(pulses, steps, 0, pat); -}); + return _euclidLegato(pulses, steps, 0, pat) +}) /** * Similar to `euclid`, but each pulse is held until the next pulse, @@ -193,9 +196,12 @@ export const euclidLegato = register(['euclidLegato'], function (pulses, steps, * @example * note("c3").euclidLegatoRot(3,5,2) */ -export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, steps, rotation, pat) { - return _euclidLegato(pulses, steps, rotation, pat); -}); +export const euclidLegatoRot = register( + ['euclidLegatoRot'], + function (pulses, steps, rotation, pat) { + return _euclidLegato(pulses, steps, rotation, pat) + } +) /** * A 'euclid' variant with an additional parameter that morphs the resulting @@ -215,7 +221,10 @@ export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, s * sound("hh").euclidish(7,12,sine.slow(8)) * .pan(sine.slow(8)) */ -export const { euclidish, eish } = register(['euclidish', 'eish'], function (pulses, steps, perc, pat) { - const morphed = _morph(bjork(pulses, steps), new Array(pulses).fill(1), perc); - return pat.struct(morphed).setSteps(steps); -}); +export const { euclidish, eish } = register( + ['euclidish', 'eish'], + function (pulses, steps, perc, pat) { + const morphed = _morph(bjork(pulses, steps), new Array(pulses).fill(1), perc) + return pat.struct(morphed).setSteps(steps) + } +) diff --git a/src/strudel/core/evaluate.mjs b/src/strudel/core/evaluate.mjs index 0599d86..2cd3b6d 100644 --- a/src/strudel/core/evaluate.mjs +++ b/src/strudel/core/evaluate.mjs @@ -4,51 +4,62 @@ Copyright (C) 2022 Strudel contributors - see . */ -export const strudelScope = {}; +export const strudelScope = {} export const evalScope = async (...args) => { - const results = await Promise.allSettled(args); - const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value); + const results = await Promise.allSettled(args) + const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value) results.forEach((result, i) => { if (result.status === 'rejected') { - console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason); + console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason) } - }); + }) // Object.assign(globalThis, ...modules); // below is a fix for above commented out line // same error as https://github.com/vitest-dev/vitest/issues/1807 when running this on astro server modules.forEach((module) => { Object.entries(module).forEach(([name, value]) => { - globalThis[name] = value; - strudelScope[name] = value; - }); - }); - return modules; -}; + // globalThis[name] = value + strudelScope[name] = value + }) + }) + return modules +} + +export const addToScope = (...obj) => { + obj.forEach((o) => { + Object.entries(o).forEach(([name, value]) => { + strudelScope[name] = value + }) + }) +} function safeEval(str, options = {}) { - const { wrapExpression = true, wrapAsync = true } = options; + const { wrapExpression = true, wrapAsync = true } = options if (wrapExpression) { - str = `{${str}}`; + str = `{${str}}` } if (wrapAsync) { - str = `(async ()=>${str})()`; + str = `(async ()=>${str})()` } - const body = `"use strict";return (${str})`; - return Function(body)(); + const body = `"use strict";return (${str})` + console.log(strudelScope) + const scopedKeys = Object.keys(strudelScope) + const scopedValues = Object.values(strudelScope) + return Function(...scopedKeys, body)(...scopedValues) } export const evaluate = async (code, transpiler, transpilerOptions) => { - let meta = {}; + let meta = {} if (transpiler) { // transform syntactically correct js code to semantically usable code - const transpiled = transpiler(code, transpilerOptions); - code = transpiled.output; - meta = transpiled; + const transpiled = transpiler(code, transpilerOptions) + code = transpiled.output + meta = transpiled } // if no transpiler is given, we expect a single instruction (!wrapExpression) - const options = { wrapExpression: !!transpiler }; - let evaluated = await safeEval(code, options); - return { mode: 'javascript', pattern: evaluated, meta }; -}; + const options = { wrapExpression: !!transpiler } + let evaluated = await safeEval(code, options) + return { mode: 'javascript', pattern: evaluated, meta } +} diff --git a/src/strudel/core/fraction.mjs b/src/strudel/core/fraction.mjs index 076fbad..90f60df 100644 --- a/src/strudel/core/fraction.mjs +++ b/src/strudel/core/fraction.mjs @@ -4,91 +4,91 @@ Copyright (C) 2022 Strudel contributors - see . */ -import Fraction from 'fraction.js'; -import { TimeSpan } from './timespan.mjs'; -import { removeUndefineds } from './util.mjs'; +import Fraction from 'fraction.js' +import { TimeSpan } from './timespan.mjs' +import { removeUndefineds } from './util.mjs' // Returns the start of the cycle. Fraction.prototype.sam = function () { - return this.floor(); -}; + return this.floor() +} // Returns the start of the next cycle. Fraction.prototype.nextSam = function () { - return this.sam().add(1); -}; + return this.sam().add(1) +} // Returns a TimeSpan representing the begin and end of the Time value's cycle Fraction.prototype.wholeCycle = function () { - return new TimeSpan(this.sam(), this.nextSam()); -}; + return new TimeSpan(this.sam(), this.nextSam()) +} // The position of a time value relative to the start of its cycle. Fraction.prototype.cyclePos = function () { - return this.sub(this.sam()); -}; + return this.sub(this.sam()) +} Fraction.prototype.lt = function (other) { - return this.compare(other) < 0; -}; + return this.compare(other) < 0 +} Fraction.prototype.gt = function (other) { - return this.compare(other) > 0; -}; + return this.compare(other) > 0 +} Fraction.prototype.lte = function (other) { - return this.compare(other) <= 0; -}; + return this.compare(other) <= 0 +} Fraction.prototype.gte = function (other) { - return this.compare(other) >= 0; -}; + return this.compare(other) >= 0 +} Fraction.prototype.eq = function (other) { - return this.compare(other) == 0; -}; + return this.compare(other) == 0 +} Fraction.prototype.ne = function (other) { - return this.compare(other) != 0; -}; + return this.compare(other) != 0 +} Fraction.prototype.max = function (other) { - return this.gt(other) ? this : other; -}; + return this.gt(other) ? this : other +} Fraction.prototype.maximum = function (...others) { - others = others.map((x) => new Fraction(x)); - return others.reduce((max, other) => other.max(max), this); -}; + others = others.map((x) => new Fraction(x)) + return others.reduce((max, other) => other.max(max), this) +} Fraction.prototype.min = function (other) { - return this.lt(other) ? this : other; -}; + return this.lt(other) ? this : other +} Fraction.prototype.mulmaybe = function (other) { - return other !== undefined ? this.mul(other) : undefined; -}; + return other !== undefined ? this.mul(other) : undefined +} Fraction.prototype.divmaybe = function (other) { - return other !== undefined ? this.div(other) : undefined; -}; + return other !== undefined ? this.div(other) : undefined +} Fraction.prototype.addmaybe = function (other) { - return other !== undefined ? this.add(other) : undefined; -}; + return other !== undefined ? this.add(other) : undefined +} Fraction.prototype.submaybe = function (other) { - return other !== undefined ? this.sub(other) : undefined; -}; + return other !== undefined ? this.sub(other) : undefined +} Fraction.prototype.show = function (/* excludeWhole = false */) { // return this.toFraction(excludeWhole); - return this.s * this.n + '/' + this.d; -}; + return this.s * this.n + '/' + this.d +} Fraction.prototype.or = function (other) { - return this.eq(0) ? other : this; -}; + return this.eq(0) ? other : this +} const fraction = (n) => { if (typeof n === 'number') { @@ -102,35 +102,36 @@ const fraction = (n) => { */ // n = String(n); // this is actually faster but imprecise... } - return Fraction(n); -}; + return Fraction(n) +} export const gcd = (...fractions) => { - fractions = removeUndefineds(fractions); + fractions = removeUndefineds(fractions) if (fractions.length === 0) { - return undefined; + return undefined } - return fractions.reduce((gcd, fraction) => gcd.gcd(fraction), fraction(1)); -}; + return fractions.reduce((gcd, fraction) => gcd.gcd(fraction), fraction(1)) +} export const lcm = (...fractions) => { - fractions = removeUndefineds(fractions); + fractions = removeUndefineds(fractions) if (fractions.length === 0) { - return undefined; + return undefined } - const x = fractions.pop(); + const x = fractions.pop() return fractions.reduce( - (lcm, fraction) => (lcm === undefined || fraction === undefined ? undefined : lcm.lcm(fraction)), - x, - ); -}; + (lcm, fraction) => + lcm === undefined || fraction === undefined ? undefined : lcm.lcm(fraction), + x + ) +} -export const isFraction = (x) => x instanceof Fraction; +export const isFraction = (x) => x instanceof Fraction -fraction._original = Fraction; +fraction._original = Fraction -export default fraction; +export default fraction // "If you concern performance, cache Fraction.js objects and pass arrays/objects.“ // -> tested memoized version, but it's slower than unmemoized, even with repeated evaluation diff --git a/src/strudel/core/hap.mjs b/src/strudel/core/hap.mjs index 5f820d6..5e083ff 100644 --- a/src/strudel/core/hap.mjs +++ b/src/strudel/core/hap.mjs @@ -3,8 +3,10 @@ hap.mjs - Copyright (C) 2022 Strudel contributors - see 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 . */ -import Fraction from './fraction.mjs'; -import { stringifyValues } from './util.mjs'; +import Fraction from './fraction.mjs' +import { stringifyValues } from './util.mjs' + +/* eslint-disable @typescript-eslint/no-this-alias */ export class Hap { /* @@ -23,91 +25,91 @@ export class Hap { */ constructor(whole, part, value, context = {}, stateful = false) { - this.whole = whole; - this.part = part; - this.value = value; - this.context = context; - this.stateful = stateful; + this.whole = whole + this.part = part + this.value = value + this.context = context + this.stateful = stateful if (stateful) { - console.assert(typeof this.value === 'function', 'Stateful values must be functions'); + console.assert(typeof this.value === 'function', 'Stateful values must be functions') } } get duration() { - let duration; + let duration if (typeof this.value?.duration === 'number') { - duration = Fraction(this.value.duration); + duration = Fraction(this.value.duration) } else { - duration = this.whole.end.sub(this.whole.begin); + duration = this.whole.end.sub(this.whole.begin) } if (typeof this.value?.clip === 'number') { - return duration.mul(this.value.clip); + return duration.mul(this.value.clip) } - return duration; + return duration } get endClipped() { - return this.whole.begin.add(this.duration); + return this.whole.begin.add(this.duration) } isActive(currentTime) { - return this.whole.begin <= currentTime && this.endClipped >= currentTime; + return this.whole.begin <= currentTime && this.endClipped >= currentTime } isInPast(currentTime) { - return currentTime > this.endClipped; + return currentTime > this.endClipped } isInNearPast(margin, currentTime) { - return currentTime - margin <= this.endClipped; + return currentTime - margin <= this.endClipped } isInFuture(currentTime) { - return currentTime < this.whole.begin; + return currentTime < this.whole.begin } isInNearFuture(margin, currentTime) { - return currentTime < this.whole.begin && currentTime > this.whole.begin - margin; + return currentTime < this.whole.begin && currentTime > this.whole.begin - margin } isWithinTime(min, max) { - return this.whole.begin <= max && this.endClipped >= min; + return this.whole.begin <= max && this.endClipped >= min } wholeOrPart() { - return this.whole ? this.whole : this.part; + return this.whole ? this.whole : this.part } withSpan(func) { // Returns a new hap with the function f applies to the hap timespan. - const whole = this.whole ? func(this.whole) : undefined; - return new Hap(whole, func(this.part), this.value, this.context); + const whole = this.whole ? func(this.whole) : undefined + return new Hap(whole, func(this.part), this.value, this.context) } withValue(func) { // Returns a new hap with the function f applies to the hap value. - return new Hap(this.whole, this.part, func(this.value), this.context); + return new Hap(this.whole, this.part, func(this.value), this.context) } hasOnset() { // Test whether the hap contains the onset, i.e that // the beginning of the part is the same as that of the whole timespan.""" - return this.whole != undefined && this.whole.begin.equals(this.part.begin); + return this.whole != undefined && this.whole.begin.equals(this.part.begin) } hasTag(tag) { - return this.context.tags?.includes(tag); + return this.context.tags?.includes(tag) } resolveState(state) { if (this.stateful && this.hasOnset()) { - console.log('stateful'); - const func = this.value; - const [newState, newValue] = func(state); - return [newState, new Hap(this.whole, this.part, newValue, this.context, false)]; + console.log('stateful') + const func = this.value + const [newState, newValue] = func(state) + return [newState, new Hap(this.whole, this.part, newValue, this.context, false)] } - return [state, this]; + return [state, this] } spanEquals(other) { - return (this.whole == undefined && other.whole == undefined) || this.whole.equals(other.whole); + return (this.whole == undefined && other.whole == undefined) || this.whole.equals(other.whole) } equals(other) { @@ -116,7 +118,7 @@ export class Hap { this.part.equals(other.part) && // TODO would == be better ?? this.value === other.value - ); + ) } show(compact = false) { @@ -125,40 +127,45 @@ export class Hap { ? compact ? JSON.stringify(this.value).slice(1, -1).replaceAll('"', '').replaceAll(',', ' ') : JSON.stringify(this.value) - : this.value; - var spans = ''; + : this.value + var spans = '' if (this.whole == undefined) { - spans = '~' + this.part.show; + spans = '~' + this.part.show } else { - var is_whole = this.whole.begin.equals(this.part.begin) && this.whole.end.equals(this.part.end); + var is_whole = + this.whole.begin.equals(this.part.begin) && this.whole.end.equals(this.part.end) if (!this.whole.begin.equals(this.part.begin)) { - spans = this.whole.begin.show() + ' ⇜ '; + spans = this.whole.begin.show() + ' ⇜ ' } if (!is_whole) { - spans += '('; + spans += '(' } - spans += this.part.show(); + spans += this.part.show() if (!is_whole) { - spans += ')'; + spans += ')' } if (!this.whole.end.equals(this.part.end)) { - spans += ' ⇝ ' + this.whole.end.show(); + spans += ' ⇝ ' + this.whole.end.show() } } - return '[ ' + spans + ' | ' + value + ' ]'; + return '[ ' + spans + ' | ' + value + ' ]' } showWhole(compact = false) { - return `${this.whole == undefined ? '~' : this.whole.show()}: ${stringifyValues(this.value, compact)}`; + return `${this.whole == undefined ? '~' : this.whole.show()}: ${stringifyValues(this.value, compact)}` } combineContext(b) { - const a = this; - return { ...a.context, ...b.context, locations: (a.context.locations || []).concat(b.context.locations || []) }; + const a = this + return { + ...a.context, + ...b.context, + locations: (a.context.locations || []).concat(b.context.locations || []), + } } setContext(context) { - return new Hap(this.whole, this.part, this.value, context); + return new Hap(this.whole, this.part, this.value, context) } ensureObjectValue() { @@ -169,10 +176,10 @@ export class Hap { if (typeof this.value !== 'object') { throw new Error( `expected hap.value to be an object, but got "${this.value}". Hint: append .note() or .s() to the end`, - 'error', - ); + 'error' + ) } } } -export default Hap; +export default Hap diff --git a/src/strudel/core/index.mjs b/src/strudel/core/index.mjs index e4daf44..cbe2d90 100644 --- a/src/strudel/core/index.mjs +++ b/src/strudel/core/index.mjs @@ -4,37 +4,29 @@ Copyright (C) 2022 Strudel contributors - see . */ -import * as controls from './controls.mjs'; // legacy -export * from './euclid.mjs'; -import Fraction from './fraction.mjs'; -import createClock from './zyklus.mjs'; -import { logger } from './logger.mjs'; -export { Fraction, controls, createClock }; -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'; -export * from './speak.mjs'; -export * from './evaluate.mjs'; -export * from './repl.mjs'; -export * from './cyclist.mjs'; -export * from './logger.mjs'; -export * from './time.mjs'; -export * from './ui.mjs'; -export { default as drawLine } from './drawLine.mjs'; +import * as controls from './controls.mjs' // legacy +export * from './euclid.mjs' +import Fraction from './fraction.mjs' +import createClock from './zyklus.mjs' +import { logger } from './logger.mjs' +export { Fraction, controls, createClock } +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' +export * from './speak.mjs' +export * from './evaluate.mjs' +export * from './repl.mjs' +export * from './cyclist.mjs' +export * from './logger.mjs' +export * from './time.mjs' +export * from './ui.mjs' +export { default as drawLine } from './drawLine.mjs' // below won't work with runtime.mjs (json import fails) /* import * as p from './package.json'; export const version = p.version; */ -logger('🌀 @strudel/core loaded 🌀'); -if (globalThis._strudelLoaded) { - console.warn( - `@strudel/core was loaded more than once... -This might happen when you have multiple versions of strudel installed. -Please check with "npm ls @strudel/core".`, - ); -} -globalThis._strudelLoaded = true; +logger('🌀 @strudel/core loaded 🌀') diff --git a/src/strudel/core/logger.mjs b/src/strudel/core/logger.mjs index 6727c24..7bfc9f3 100644 --- a/src/strudel/core/logger.mjs +++ b/src/strudel/core/logger.mjs @@ -1,24 +1,24 @@ -export const logKey = 'strudel.log'; +export const logKey = 'strudel.log' let debounce = 1000, lastMessage, - lastTime; + lastTime export function errorLogger(e, origin = 'cyclist') { if (process.env.NODE_ENV === 'development') { - console.error(e); + console.error(e) } - logger(`[${origin}] error: ${e.message}`); + logger(`[${origin}] error: ${e.message}`) } export function logger(message, type, data = {}) { - let t = performance.now(); + let t = performance.now() if (lastMessage === message && t - lastTime < debounce) { - return; + return } - lastMessage = message; - lastTime = t; - console.log(`%c${message}`, 'background-color: black;color:white;border-radius:15px'); + lastMessage = message + lastTime = t + console.log(`%c${message}`) if (typeof document !== 'undefined' && typeof CustomEvent !== 'undefined') { document.dispatchEvent( new CustomEvent(logKey, { @@ -27,9 +27,9 @@ export function logger(message, type, data = {}) { type, data, }, - }), - ); + }) + ) } } -logger.key = logKey; +logger.key = logKey diff --git a/src/strudel/core/neocyclist.mjs b/src/strudel/core/neocyclist.mjs index 3e41207..ceee8f6 100644 --- a/src/strudel/core/neocyclist.mjs +++ b/src/strudel/core/neocyclist.mjs @@ -4,102 +4,104 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { logger } from './logger.mjs'; -import { ClockCollator, cycleToSeconds } from './util.mjs'; +import { logger } from './logger.mjs' +import { ClockCollator, cycleToSeconds } from './util.mjs' export class NeoCyclist { constructor({ onTrigger, onToggle, getTime }) { - this.started = false; - this.cps = 0.5; - this.getTime = getTime; // get absolute time - this.time_at_last_tick_message = 0; + this.started = false + this.cps = 0.5 + this.getTime = getTime // get absolute time + this.time_at_last_tick_message = 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 = new SharedWorker(new URL('./clockworker.js', import.meta.url)); - this.worker.port.start(); - this.channel = new BroadcastChannel('strudeltick'); + 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 = new SharedWorker(new URL('./clockworker.js', import.meta.url)) + this.worker.port.start() + this.channel = new BroadcastChannel('strudeltick') const tickCallback = (payload) => { - const { cps, begin, end, cycle, time } = payload; - this.cps = cps; - this.cycle = cycle; - const currentTime = this.collator.calculateOffset(time) + time; - processHaps(begin, end, currentTime); - this.time_at_last_tick_message = currentTime; - }; + const { cps, begin, end, cycle, time } = payload + this.cps = cps + this.cycle = cycle + const currentTime = this.collator.calculateOffset(time) + time + processHaps(begin, end, currentTime) + this.time_at_last_tick_message = currentTime + } const processHaps = (begin, end, currentTime) => { if (this.started === false) { - return; + return } - const haps = this.pattern.queryArc(begin, end, { _cps: this.cps, cyclist: 'neocyclist' }); + const haps = this.pattern.queryArc(begin, end, { _cps: this.cps, cyclist: 'neocyclist' }) haps.forEach((hap) => { if (hap.hasOnset()) { - 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); + 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) } - }); - }; + }) + } // receive messages from worker clock and process them this.channel.onmessage = (message) => { if (!this.started) { - return; + return } - const { payload, type } = message.data; + const { payload, type } = message.data switch (type) { case 'tick': { - tickCallback(payload); + tickCallback(payload) } } - }; + } } sendMessage(type, payload) { - this.worker.port.postMessage({ type, payload, id: this.id }); + this.worker.port.postMessage({ type, payload, id: this.id }) } now() { - const gap = (this.getTime() - this.time_at_last_tick_message) * this.cps; - return this.cycle + gap; + const gap = (this.getTime() - this.time_at_last_tick_message) * this.cps + return this.cycle + gap } setCps(cps = 1) { - this.sendMessage('cpschange', { cps }); + this.sendMessage('cpschange', { cps }) } setCycle(cycle) { - this.sendMessage('setcycle', { cycle }); + this.sendMessage('setcycle', { cycle }) } setStarted(started) { - this.sendMessage('toggle', { started }); - this.started = started; - this.onToggle?.(started); + this.sendMessage('toggle', { started }) + this.started = started + this.onToggle?.(started) } start() { - logger('[cyclist] start'); - this.setStarted(true); + logger('[cyclist] start') + this.setStarted(true) } stop() { - logger('[cyclist] stop'); - this.collator.reset(); - this.setStarted(false); + logger('[cyclist] stop') + this.collator.reset() + this.setStarted(false) } setPattern(pat, autostart = false) { - this.pattern = pat; + this.pattern = pat if (autostart && !this.started) { - this.start(); + this.start() } } log(begin, end, haps) { - const onsets = haps.filter((h) => h.hasOnset()); - console.log(`${begin.toFixed(4)} - ${end.toFixed(4)} ${Array(onsets.length).fill('I').join('')}`); + const onsets = haps.filter((h) => h.hasOnset()) + console.log( + `${begin.toFixed(4)} - ${end.toFixed(4)} ${Array(onsets.length).fill('I').join('')}` + ) } } diff --git a/src/strudel/core/pattern.mjs b/src/strudel/core/pattern.mjs index f5b0c67..8eef076 100644 --- a/src/strudel/core/pattern.mjs +++ b/src/strudel/core/pattern.mjs @@ -4,11 +4,13 @@ Copyright (C) 2025 Strudel contributors - see . */ -import TimeSpan from './timespan.mjs'; -import Fraction, { isFraction, lcm } from './fraction.mjs'; -import Hap from './hap.mjs'; -import State from './state.mjs'; -import { unionWithObj } from './value.mjs'; +/* eslint-disable @typescript-eslint/no-this-alias */ + +import TimeSpan from './timespan.mjs' +import Fraction, { isFraction, lcm } from './fraction.mjs' +import Hap from './hap.mjs' +import State from './state.mjs' +import { unionWithObj } from './value.mjs' import { uniqsortr, @@ -23,22 +25,22 @@ import { pairs, zipWith, stringifyValues, -} from './util.mjs'; -import drawLine from './drawLine.mjs'; -import { logger } from './logger.mjs'; +} from './util.mjs' +import drawLine from './drawLine.mjs' +import { logger } from './logger.mjs' -let stringParser; +let stringParser -let __steps = true; +let __steps = true export const calculateSteps = function (x) { - __steps = x ? true : false; -}; + __steps = x ? true : false +} // parser is expected to turn a string into a pattern // if set, the reify function will parse all strings with it // intended to use with mini to automatically interpret all strings as mini notation -export const setStringParser = (parser) => (stringParser = parser); +export const setStringParser = (parser) => (stringParser = parser) /** @class Class representing a pattern. */ export class Pattern { @@ -49,33 +51,33 @@ export class Pattern { * @noAutocomplete */ constructor(query, steps = undefined) { - this.query = query; - this._Pattern = true; // this property is used to detectinstance of another Pattern - this._steps = steps; // in terms of number of steps per cycle + this.query = query + this._Pattern = true // this property is used to detectinstance of another Pattern + this._steps = steps // in terms of number of steps per cycle } get _steps() { - return this.__steps; + return this.__steps } set _steps(steps) { - this.__steps = steps === undefined ? undefined : Fraction(steps); + this.__steps = steps === undefined ? undefined : Fraction(steps) } setSteps(steps) { - this._steps = steps; - return this; + this._steps = steps + return this } withSteps(f) { if (!__steps) { - return this; + return this } - return new Pattern(this.query, this._steps === undefined ? undefined : f(this._steps)); + return new Pattern(this.query, this._steps === undefined ? undefined : f(this._steps)) } get hasSteps() { - return this._steps !== undefined; + return this._steps !== undefined } ////////////////////////////////////////////////////////////////////// @@ -91,14 +93,14 @@ export class Pattern { * "0 1 2".withValue(v => v + 10).log() */ withValue(func) { - const result = new Pattern((state) => this.query(state).map((hap) => hap.withValue(func))); - result._steps = this._steps; - return result; + const result = new Pattern((state) => this.query(state).map((hap) => hap.withValue(func))) + result._steps = this._steps + return result } // runs func on query state withState(func) { - return new Pattern((state) => this.query(func(state))); + return new Pattern((state) => this.query(func(state))) } /** @@ -106,7 +108,7 @@ export class Pattern { * @noAutocomplete */ fmap(func) { - return this.withValue(func); + return this.withValue(func) } /** @@ -119,27 +121,29 @@ export class Pattern { * @returns Pattern */ appWhole(whole_func, pat_val) { - const pat_func = this; + const pat_func = this const query = function (state) { - const hap_funcs = pat_func.query(state); - const hap_vals = pat_val.query(state); + const hap_funcs = pat_func.query(state) + const hap_vals = pat_val.query(state) const apply = function (hap_func, hap_val) { - const s = hap_func.part.intersection(hap_val.part); + const s = hap_func.part.intersection(hap_val.part) if (s == undefined) { - return undefined; + return undefined } return new Hap( whole_func(hap_func.whole, hap_val.whole), s, hap_func.value(hap_val.value), - hap_val.combineContext(hap_func), - ); - }; + hap_val.combineContext(hap_func) + ) + } return flatten( - hap_funcs.map((hap_func) => removeUndefineds(hap_vals.map((hap_val) => apply(hap_func, hap_val)))), - ); - }; - return new Pattern(query); + hap_funcs.map((hap_func) => + removeUndefineds(hap_vals.map((hap_val) => apply(hap_func, hap_val))) + ) + ) + } + return new Pattern(query) } /** @@ -155,20 +159,20 @@ export class Pattern { * @returns Pattern */ appBoth(pat_val) { - const pat_func = this; + const pat_func = this // Tidal's <*> const whole_func = function (span_a, span_b) { if (span_a == undefined || span_b == undefined) { - return undefined; + return undefined } - return span_a.intersection_e(span_b); - }; - const result = pat_func.appWhole(whole_func, pat_val); - if (__steps) { - result._steps = lcm(pat_val._steps, pat_func._steps); + return span_a.intersection_e(span_b) } - return result; + const result = pat_func.appWhole(whole_func, pat_val) + if (__steps) { + result._steps = lcm(pat_val._steps, pat_func._steps) + } + return result } /** @@ -182,28 +186,28 @@ export class Pattern { * @returns Pattern */ appLeft(pat_val) { - const pat_func = this; + const pat_func = this const query = function (state) { - const haps = []; + const haps = [] for (const hap_func of pat_func.query(state)) { - const hap_vals = pat_val.query(state.setSpan(hap_func.wholeOrPart())); + const hap_vals = pat_val.query(state.setSpan(hap_func.wholeOrPart())) for (const hap_val of hap_vals) { - const new_whole = hap_func.whole; - const new_part = hap_func.part.intersection(hap_val.part); + const new_whole = hap_func.whole + const new_part = hap_func.part.intersection(hap_val.part) if (new_part) { - const new_value = hap_func.value(hap_val.value); - const new_context = hap_val.combineContext(hap_func); - const hap = new Hap(new_whole, new_part, new_value, new_context); - haps.push(hap); + const new_value = hap_func.value(hap_val.value) + const new_context = hap_val.combineContext(hap_func) + const hap = new Hap(new_whole, new_part, new_value, new_context) + haps.push(hap) } } } - return haps; - }; - const result = new Pattern(query); - result._steps = this._steps; - return result; + return haps + } + const result = new Pattern(query) + result._steps = this._steps + return result } /** @@ -215,32 +219,32 @@ export class Pattern { * @returns Pattern */ appRight(pat_val) { - const pat_func = this; + const pat_func = this const query = function (state) { - const haps = []; + const haps = [] for (const hap_val of pat_val.query(state)) { - const hap_funcs = pat_func.query(state.setSpan(hap_val.wholeOrPart())); + const hap_funcs = pat_func.query(state.setSpan(hap_val.wholeOrPart())) for (const hap_func of hap_funcs) { - const new_whole = hap_val.whole; - const new_part = hap_func.part.intersection(hap_val.part); + const new_whole = hap_val.whole + const new_part = hap_func.part.intersection(hap_val.part) if (new_part) { - const new_value = hap_func.value(hap_val.value); - const new_context = hap_val.combineContext(hap_func); - const hap = new Hap(new_whole, new_part, new_value, new_context); - haps.push(hap); + const new_value = hap_func.value(hap_val.value) + const new_context = hap_val.combineContext(hap_func) + const hap = new Hap(new_whole, new_part, new_value, new_context) + haps.push(hap) } } } - return haps; - }; - const result = new Pattern(query); - result._steps = pat_val._steps; - return result; + return haps + } + const result = new Pattern(query) + result._steps = pat_val._steps + return result } bindWhole(choose_whole, func) { - const pat_val = this; + const pat_val = this const query = function (state) { const withWhole = function (a, b) { return new Hap( @@ -249,58 +253,58 @@ export class Pattern { b.value, Object.assign({}, a.context, b.context, { locations: (a.context.locations || []).concat(b.context.locations || []), - }), - ); - }; + }) + ) + } const match = function (a) { return func(a.value) .query(state.setSpan(a.part)) - .map((b) => withWhole(a, b)); - }; - return flatten(pat_val.query(state).map((a) => match(a))); - }; - return new Pattern(query); + .map((b) => withWhole(a, b)) + } + return flatten(pat_val.query(state).map((a) => match(a))) + } + return new Pattern(query) } bind(func) { const whole_func = function (a, b) { if (a == undefined || b == undefined) { - return undefined; + return undefined } - return a.intersection_e(b); - }; - return this.bindWhole(whole_func, func); + return a.intersection_e(b) + } + return this.bindWhole(whole_func, func) } join() { // Flattens a pattern of patterns into a pattern, where wholes are // the intersection of matched inner and outer haps. - return this.bind(id); + return this.bind(id) } outerBind(func) { - return this.bindWhole((a) => a, func).setSteps(this._steps); + return this.bindWhole((a) => a, func).setSteps(this._steps) } outerJoin() { // Flattens a pattern of patterns into a pattern, where wholes are // taken from outer haps. - return this.outerBind(id); + return this.outerBind(id) } innerBind(func) { - return this.bindWhole((_, b) => b, func); + return this.bindWhole((_, b) => b, func) } innerJoin() { // Flattens a pattern of patterns into a pattern, where wholes are // taken from inner haps. - return this.innerBind(id); + return this.innerBind(id) } // Flatterns patterns of patterns, by retriggering/resetting inner patterns at onsets of outer pattern haps resetJoin(restart = false) { - const pat_of_pats = this; + const pat_of_pats = this return new Pattern((state) => { return ( pat_of_pats @@ -319,20 +323,20 @@ export class Pattern { // Supports continuous haps in the inner pattern inner_hap.whole ? inner_hap.whole.intersection(outer_hap.whole) : undefined, inner_hap.part.intersection(outer_hap.part), - inner_hap.value, - ).setContext(outer_hap.combineContext(inner_hap)), + inner_hap.value + ).setContext(outer_hap.combineContext(inner_hap)) ) // Drop haps that didn't intersect .filter((hap) => hap.part) - ); + ) }) .flat() - ); - }); + ) + }) } restartJoin() { - return this.resetJoin(true); + return this.resetJoin(true) } // Like the other joins above, joins a pattern of patterns of values, into a flatter @@ -341,57 +345,57 @@ export class Pattern { squeezeJoin() { // A pattern of patterns, which we call the 'outer' pattern, with patterns // as values which we call the 'inner' patterns. - const pat_of_pats = this; + const pat_of_pats = this function query(state) { // Get the events with the inner patterns. Ignore continuous events (without 'wholes') - const haps = pat_of_pats.discreteOnly().query(state); + const haps = pat_of_pats.discreteOnly().query(state) // A function to map over the events from the outer pattern. function flatHap(outerHap) { // Get the inner pattern, slowed and shifted so that the 'whole' // timespan of the outer event corresponds to the first cycle of the // inner event - const inner_pat = outerHap.value._focusSpan(outerHap.wholeOrPart()); + const inner_pat = outerHap.value._focusSpan(outerHap.wholeOrPart()) // Get the inner events, from the timespan of the outer event's part - const innerHaps = inner_pat.query(state.setSpan(outerHap.part)); + const innerHaps = inner_pat.query(state.setSpan(outerHap.part)) // A function to map over the inner events, to combine them with the // outer event function munge(outer, inner) { - let whole = undefined; + let whole = undefined if (inner.whole && outer.whole) { - whole = inner.whole.intersection(outer.whole); + whole = inner.whole.intersection(outer.whole) if (!whole) { // The wholes are present, but don't intersect - return undefined; + return undefined } } - const part = inner.part.intersection(outer.part); + const part = inner.part.intersection(outer.part) if (!part) { // The parts don't intersect - return undefined; + return undefined } - const context = inner.combineContext(outer); - return new Hap(whole, part, inner.value, context); + const context = inner.combineContext(outer) + return new Hap(whole, part, inner.value, context) } - return innerHaps.map((innerHap) => munge(outerHap, innerHap)); + return innerHaps.map((innerHap) => munge(outerHap, innerHap)) } - const result = flatten(haps.map(flatHap)); + const result = flatten(haps.map(flatHap)) // remove undefineds - return result.filter((x) => x); + return result.filter((x) => x) } - return new Pattern(query); + return new Pattern(query) } squeezeBind(func) { - return this.fmap(func).squeezeJoin(); + return this.fmap(func).squeezeJoin() } polyJoin = function () { - const pp = this; - return pp.fmap((p) => p.extend(pp._steps.div(p._steps))).outerJoin(); - }; + const pp = this + return pp.fmap((p) => p.extend(pp._steps.div(p._steps))).outerJoin() + } polyBind(func) { - return this.fmap(func).polyJoin(); + return this.fmap(func).polyJoin() } ////////////////////////////////////////////////////////////////////// @@ -412,10 +416,10 @@ export class Pattern { */ queryArc(begin, end, controls = {}) { try { - return this.query(new State(new TimeSpan(begin, end), controls)); + return this.query(new State(new TimeSpan(begin, end), controls)) } catch (err) { - logger(`[query]: ${err.message}`, 'error'); - return []; + logger(`[query]: ${err.message}`, 'error') + return [] } } @@ -427,11 +431,11 @@ export class Pattern { * @noAutocomplete */ splitQueries() { - const pat = this; + const pat = this const q = (state) => { - return flatten(state.span.spanCycles.map((subspan) => pat.query(state.setSpan(subspan)))); - }; - return new Pattern(q); + return flatten(state.span.spanCycles.map((subspan) => pat.query(state.setSpan(subspan)))) + } + return new Pattern(q) } /** @@ -442,18 +446,18 @@ export class Pattern { * @noAutocomplete */ withQuerySpan(func) { - return new Pattern((state) => this.query(state.withSpan(func))); + return new Pattern((state) => this.query(state.withSpan(func))) } withQuerySpanMaybe(func) { - const pat = this; + const pat = this return new Pattern((state) => { - const newState = state.withSpan(func); + const newState = state.withSpan(func) if (!newState.span) { - return []; + return [] } - return pat.query(newState); - }); + return pat.query(newState) + }) } /** @@ -464,7 +468,7 @@ export class Pattern { * @noAutocomplete */ withQueryTime(func) { - return new Pattern((state) => this.query(state.withSpan((span) => span.withTime(func)))); + return new Pattern((state) => this.query(state.withSpan((span) => span.withTime(func)))) } /** @@ -476,7 +480,7 @@ export class Pattern { * @noAutocomplete */ withHapSpan(func) { - return new Pattern((state) => this.query(state).map((hap) => hap.withSpan(func))); + return new Pattern((state) => this.query(state).map((hap) => hap.withSpan(func))) } /** @@ -487,7 +491,7 @@ export class Pattern { * @noAutocomplete */ withHapTime(func) { - return this.withHapSpan((span) => span.withTime(func)); + return this.withHapSpan((span) => span.withTime(func)) } /** @@ -497,9 +501,9 @@ export class Pattern { * @noAutocomplete */ withHaps(func) { - const result = new Pattern((state) => func(this.query(state), state)); - result._steps = this._steps; - return result; + const result = new Pattern((state) => func(this.query(state), state)) + result._steps = this._steps + return result } /** @@ -509,7 +513,7 @@ export class Pattern { * @noAutocomplete */ withHap(func) { - return this.withHaps((haps) => haps.map(func)); + return this.withHaps((haps) => haps.map(func)) } /** @@ -519,7 +523,7 @@ export class Pattern { * @noAutocomplete */ setContext(context) { - return this.withHap((hap) => hap.setContext(context)); + return this.withHap((hap) => hap.setContext(context)) } /** @@ -529,12 +533,12 @@ export class Pattern { * @noAutocomplete */ withContext(func) { - const result = this.withHap((hap) => hap.setContext(func(hap.context))); + const result = this.withHap((hap) => hap.setContext(func(hap.context))) if (this.__pure !== undefined) { - result.__pure = this.__pure; - result.__pure_loc = this.__pure_loc; + result.__pure = this.__pure + result.__pure_loc = this.__pure_loc } - return result; + return result } /** @@ -543,7 +547,7 @@ export class Pattern { * @noAutocomplete */ stripContext() { - return this.withHap((hap) => hap.setContext({})); + return this.withHap((hap) => hap.setContext({})) } /** @@ -558,16 +562,16 @@ export class Pattern { const location = { start, end, - }; - const result = this.withContext((context) => { - const locations = (context.locations || []).concat([location]); - return { ...context, locations }; - }); - if (this.__pure) { - result.__pure = this.__pure; - result.__pure_loc = location; } - return result; + const result = this.withContext((context) => { + const locations = (context.locations || []).concat([location]) + return { ...context, locations } + }) + if (this.__pure) { + result.__pure = this.__pure + result.__pure_loc = location + } + return result } /** @@ -577,7 +581,7 @@ export class Pattern { * @noAutocomplete */ filterHaps(hap_test) { - return new Pattern((state) => this.query(state).filter(hap_test)); + return new Pattern((state) => this.query(state).filter(hap_test)) } /** @@ -588,7 +592,9 @@ export class Pattern { * @noAutocomplete */ filterValues(value_test) { - return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value))).setSteps(this._steps); + return new Pattern((state) => + this.query(state).filter((hap) => value_test(hap.value)) + ).setSteps(this._steps) } /** @@ -598,7 +604,7 @@ export class Pattern { * @noAutocomplete */ removeUndefineds() { - return this.filterValues((val) => val != undefined); + return this.filterValues((val) => val != undefined) } /** @@ -612,7 +618,7 @@ export class Pattern { // Returns a new pattern that will only return haps where the start // of the 'whole' timespan matches the start of the 'part' // timespan, i.e. the haps that include their 'onset'. - return this.filterHaps((hap) => hap.hasOnset()); + return this.filterHaps((hap) => hap.hasOnset()) } /** @@ -623,7 +629,7 @@ export class Pattern { */ discreteOnly() { // removes continuous haps that don't have a 'whole' timespan - return this.filterHaps((hap) => hap.whole); + return this.filterHaps((hap) => hap.whole) } /** @@ -633,49 +639,49 @@ export class Pattern { */ defragmentHaps() { // remove continuous haps - const pat = this.discreteOnly(); + const pat = this.discreteOnly() return pat.withHaps((haps) => { - const result = []; + const result = [] for (var i = 0; i < haps.length; ++i) { - var searching = true; - var a = haps[i]; + var searching = true + var a = haps[i] while (searching) { - const a_value = JSON.stringify(haps[i].value); - var found = false; + const a_value = JSON.stringify(haps[i].value) + var found = false for (var j = i + 1; j < haps.length; j++) { - const b = haps[j]; + const b = haps[j] if (a.whole.equals(b.whole)) { if (a.part.begin.eq(b.part.end)) { if (a_value === JSON.stringify(b.value)) { // eat the matching hap into 'a' - a = new Hap(a.whole, new TimeSpan(b.part.begin, a.part.end), a.value); - haps.splice(j, 1); + a = new Hap(a.whole, new TimeSpan(b.part.begin, a.part.end), a.value) + haps.splice(j, 1) // restart the search - found = true; - break; + found = true + break } } else if (b.part.begin.eq(a.part.end)) { if (a_value == JSON.stringify(b.value)) { // eat the matching hap into 'a' - a = new Hap(a.whole, new TimeSpan(a.part.begin, b.part.end), a.value); - haps.splice(j, 1); + a = new Hap(a.whole, new TimeSpan(a.part.begin, b.part.end), a.value) + haps.splice(j, 1) // restart the search - found = true; - break; + found = true + break } } } } - searching = found; + searching = found } - result.push(a); + result.push(a) } - return result; - }); + return result + }) } /** @@ -687,11 +693,11 @@ export class Pattern { * @noAutocomplete */ firstCycle(with_context = false) { - var self = this; + var self = this if (!with_context) { - self = self.stripContext(); + self = self.stripContext() } - return self.query(new State(new TimeSpan(Fraction(0), Fraction(1)))); + return self.query(new State(new TimeSpan(Fraction(0), Fraction(1)))) } /** @@ -699,7 +705,7 @@ export class Pattern { * @noAutocomplete */ get firstCycleValues() { - return this.firstCycle().map((hap) => hap.value); + return this.firstCycle().map((hap) => hap.value) } /** @@ -708,8 +714,8 @@ export class Pattern { */ get showFirstCycle() { return this.firstCycle().map( - (hap) => `${hap.value}: ${hap.whole.begin.toFraction()} - ${hap.whole.end.toFraction()}`, - ); + (hap) => `${hap.value}: ${hap.whole.begin.toFraction()} - ${hap.whole.end.toFraction()}` + ) } /** @@ -724,47 +730,47 @@ export class Pattern { a.part.begin .sub(b.part.begin) .or(a.part.end.sub(b.part.end)) - .or(a.whole.begin.sub(b.whole.begin).or(a.whole.end.sub(b.whole.end))), - ), - ); + .or(a.whole.begin.sub(b.whole.begin).or(a.whole.end.sub(b.whole.end))) + ) + ) } asNumber() { - return this.fmap(parseNumeral); + return this.fmap(parseNumeral) } ////////////////////////////////////////////////////////////////////// // Operators - see 'make composers' later.. _opIn(other, func) { - return this.fmap(func).appLeft(reify(other)); + return this.fmap(func).appLeft(reify(other)) } _opOut(other, func) { - return this.fmap(func).appRight(reify(other)); + return this.fmap(func).appRight(reify(other)) } _opMix(other, func) { - return this.fmap(func).appBoth(reify(other)); + return this.fmap(func).appBoth(reify(other)) } _opSqueeze(other, func) { - const otherPat = reify(other); - return this.fmap((a) => otherPat.fmap((b) => func(a)(b))).squeezeJoin(); + const otherPat = reify(other) + return this.fmap((a) => otherPat.fmap((b) => func(a)(b))).squeezeJoin() } _opSqueezeOut(other, func) { - const thisPat = this; - const otherPat = reify(other); - return otherPat.fmap((a) => thisPat.fmap((b) => func(b)(a))).squeezeJoin(); + const thisPat = this + const otherPat = reify(other) + return otherPat.fmap((a) => thisPat.fmap((b) => func(b)(a))).squeezeJoin() } _opReset(other, func) { - const otherPat = reify(other); - return otherPat.fmap((b) => this.fmap((a) => func(a)(b))).resetJoin(); + const otherPat = reify(other) + return otherPat.fmap((b) => this.fmap((a) => func(a)(b))).resetJoin() } _opRestart(other, func) { - const otherPat = reify(other); - return otherPat.fmap((b) => this.fmap((a) => func(a)(b))).restartJoin(); + const otherPat = reify(other) + return otherPat.fmap((b) => this.fmap((a) => func(a)(b))).restartJoin() } _opPoly(other, func) { - const otherPat = reify(other); - return this.fmap((b) => otherPat.fmap((a) => func(a)(b))).polyJoin(); + const otherPat = reify(other) + return this.fmap((b) => otherPat.fmap((a) => func(a)(b))).polyJoin() } ////////////////////////////////////////////////////////////////////// @@ -788,7 +794,7 @@ export class Pattern { * .scale('C minor').note() */ layer(...funcs) { - return stack(...funcs.map((func) => func(this))); + return stack(...funcs.map((func) => func(this))) } /** @@ -802,33 +808,33 @@ export class Pattern { * .scale('C minor').note() */ superimpose(...funcs) { - return this.stack(...funcs.map((func) => func(this))); + return this.stack(...funcs.map((func) => func(this))) } ////////////////////////////////////////////////////////////////////// // Multi-pattern functions stack(...pats) { - return stack(this, ...pats); + return stack(this, ...pats) } sequence(...pats) { - return sequence(this, ...pats); + return sequence(this, ...pats) } seq(...pats) { - return sequence(this, ...pats); + return sequence(this, ...pats) } cat(...pats) { - return cat(this, ...pats); + return cat(this, ...pats) } fastcat(...pats) { - return fastcat(this, ...pats); + return fastcat(this, ...pats) } slowcat(...pats) { - return slowcat(this, ...pats); + return slowcat(this, ...pats) } ////////////////////////////////////////////////////////////////////// @@ -840,15 +846,15 @@ export class Pattern { ...hap.context, onTrigger: (...args) => { // run previously set trigger, if it exists - hap.context.onTrigger?.(...args); - onTrigger(...args); + hap.context.onTrigger?.(...args) + onTrigger(...args) }, // if dominantTrigger is set to true, the default output (webaudio) will be disabled // when using multiple triggers, you cannot flip this flag to false again! // example: x.csound('CooLSynth').log() as well as x.log().csound('CooLSynth') should work the same dominantTrigger: hap.context.dominantTrigger || dominant, - }), - ); + }) + ) } /** @@ -860,8 +866,8 @@ export class Pattern { */ log(func = (hap) => `[hap] ${hap.showWhole(true)}`, getData = (hap) => ({ hap })) { return this.onTrigger((...args) => { - logger(func(...args), undefined, getData(...args)); - }, false); + logger(func(...args), undefined, getData(...args)) + }, false) } /** @@ -873,15 +879,15 @@ export class Pattern { * s("bd sd").gain("0.25 0.5 1").n("2 1 0").logValues() */ logValues(func = (value) => `[hap] ${stringifyValues(value, true)}`) { - return this.log((hap) => func(hap.value)); + return this.log((hap) => func(hap.value)) } ////////////////////////////////////////////////////////////////////// // Visualisation drawLine() { - console.log(drawLine(this)); - return this; + console.log(drawLine(this)) + return this } ////////////////////////////////////////////////////////////////////// @@ -890,8 +896,8 @@ export class Pattern { // Breaks a pattern into a pattern of patterns, according to the structure of the given binary pattern. unjoin(pieces, func = id) { return pieces.withHap((hap) => - hap.withValue((v) => (v ? func(this.ribbon(hap.whole.begin, hap.whole.duration)) : this)), - ); + hap.withValue((v) => (v ? func(this.ribbon(hap.whole.begin, hap.whole.duration)) : this)) + ) } /** @@ -906,7 +912,7 @@ export class Pattern { * sound("bd sd ht lt").into("1 0", hurry(2)) */ into(pieces, func) { - return this.unjoin(pieces, func).innerJoin(); + return this.unjoin(pieces, func).innerJoin() } } @@ -915,27 +921,27 @@ export class Pattern { // returns Array where each list of haps satisfies eq function groupHapsBy(eq, haps) { - let groups = []; + let groups = [] haps.forEach((hap) => { - const match = groups.findIndex(([other]) => eq(hap, other)); + const match = groups.findIndex(([other]) => eq(hap, other)) if (match === -1) { - groups.push([hap]); + groups.push([hap]) } else { - groups[match].push(hap); + groups[match].push(hap) } - }); - return groups; + }) + return groups } // congruent haps = haps with equal spans -const congruent = (a, b) => a.spanEquals(b); +const congruent = (a, b) => a.spanEquals(b) // Pattern> -> Pattern> // returned pattern contains arrays of congruent haps Pattern.prototype.collect = function () { return this.withHaps((haps) => - groupHapsBy(congruent, haps).map((_haps) => new Hap(_haps[0].whole, _haps[0].part, _haps, {})), - ); -}; + groupHapsBy(congruent, haps).map((_haps) => new Hap(_haps[0].whole, _haps[0].part, _haps, {})) + ) +} /** * Selects indices in in stacked notes. @@ -948,8 +954,8 @@ export const arpWith = register('arpWith', (func, pat) => { .collect() .fmap((v) => reify(func(v))) .innerJoin() - .withHap((h) => new Hap(h.whole, h.part, h.value.value, h.combineContext(h.value))); -}); + .withHap((h) => new Hap(h.whole, h.part, h.value.value, h.combineContext(h.value))) +}) /** * Selects indices in in stacked notes. @@ -960,8 +966,8 @@ export const arpWith = register('arpWith', (func, pat) => { export const arp = register( 'arp', (indices, pat) => pat.arpWith((haps) => reify(indices).fmap((i) => haps[i % haps.length])), - false, -); + false +) /* * Takes a time duration followed by one or more patterns, and shifts the given patterns in time, so they are @@ -996,23 +1002,23 @@ addToPrototype('weaveWith', function (t, ...funcs) { // compose matrix functions function _nonArrayObject(x) { - return !Array.isArray(x) && typeof x === 'object' && !isFraction(x); + return !Array.isArray(x) && typeof x === 'object' && !isFraction(x) } function _composeOp(a, b, func) { if (_nonArrayObject(a) || _nonArrayObject(b)) { if (!_nonArrayObject(a)) { - a = { value: a }; + a = { value: a } } if (!_nonArrayObject(b)) { - b = { value: b }; + b = { value: b } } - return unionWithObj(a, b, func); + return unionWithObj(a, b, func) } - return func(a, b); + return func(a, b) } // Make composers -(function () { +;(function () { // pattern composers const composers = { set: [(a, b) => b], @@ -1086,60 +1092,60 @@ function _composeOp(a, b, func) { // bitwise ops func: [(a, b) => b(a)], - }; + } - const hows = ['In', 'Out', 'Mix', 'Squeeze', 'SqueezeOut', 'Reset', 'Restart', 'Poly']; + const hows = ['In', 'Out', 'Mix', 'Squeeze', 'SqueezeOut', 'Reset', 'Restart', 'Poly'] // generate methods to do what and how for (const [what, [op, preprocess]] of Object.entries(composers)) { // make plain version, e.g. pat._add(value) adds that plain value // to all the values in pat Pattern.prototype['_' + what] = function (value) { - return this.fmap((x) => op(x, value)); - }; + return this.fmap((x) => op(x, value)) + } // make patternified monster version Object.defineProperty(Pattern.prototype, what, { // a getter that returns a function, so 'pat' can be // accessed by closures that are methods of that function.. get: function () { - const pat = this; + const pat = this // wrap the 'in' function as default behaviour - const wrapper = (...other) => pat[what]['in'](...other); + const wrapper = (...other) => pat[what]['in'](...other) // add methods to that function for each behaviour for (const how of hows) { wrapper[how.toLowerCase()] = function (...other) { - var howpat = pat; - other = sequence(other); + var howpat = pat + other = sequence(other) if (preprocess) { - howpat = preprocess(howpat); - other = preprocess(other); + howpat = preprocess(howpat) + other = preprocess(other) } - var result; + var result // hack to remove undefs when doing 'keepif' if (what === 'keepif') { // avoid union, as we want to throw away the value of 'b' completely - result = howpat['_op' + how](other, (a) => (b) => op(a, b)); - result = result.removeUndefineds(); + result = howpat['_op' + how](other, (a) => (b) => op(a, b)) + result = result.removeUndefineds() } else { - result = howpat['_op' + how](other, (a) => (b) => _composeOp(a, b, op)); + result = howpat['_op' + how](other, (a) => (b) => _composeOp(a, b, op)) } - return result; - }; + return result + } } - wrapper.squeezein = wrapper.squeeze; + wrapper.squeezein = wrapper.squeeze - return wrapper; + return wrapper }, - }); + }) // Default op to 'set', e.g. pat.squeeze(pat2) = pat.set.squeeze(pat2) for (const how of hows) { Pattern.prototype[how.toLowerCase()] = function (...args) { - return this.set[how.toLowerCase()](args); - }; + return this.set[how.toLowerCase()](args) + } } } @@ -1153,11 +1159,11 @@ function _composeOp(a, b, func) { * .slow(2) */ Pattern.prototype.struct = function (...args) { - return this.keepif.out(...args); - }; + return this.keepif.out(...args) + } Pattern.prototype.structAll = function (...args) { - return this.keep.out(...args); - }; + return this.keep.out(...args) + } /** * Returns silence when mask is 0 or "~" * @@ -1165,11 +1171,11 @@ function _composeOp(a, b, func) { * note("c [eb,g] d [eb,g]").mask("<1 [0 1]>") */ Pattern.prototype.mask = function (...args) { - return this.keepif.in(...args); - }; + return this.keepif.in(...args) + } Pattern.prototype.maskAll = function (...args) { - return this.keep.in(...args); - }; + return this.keep.in(...args) + } /** * Resets the pattern to the start of the cycle for each onset of the reset pattern. * @@ -1177,11 +1183,11 @@ function _composeOp(a, b, func) { * s("[ sd]*2, hh*8").reset("") */ Pattern.prototype.reset = function (...args) { - return this.keepif.reset(...args); - }; + return this.keepif.reset(...args) + } Pattern.prototype.resetAll = function (...args) { - return this.keep.reset(...args); - }; + return this.keep.reset(...args) + } /** * Restarts the pattern for each onset of the restart pattern. * While reset will only reset the current cycle, restart will start from cycle 0. @@ -1190,18 +1196,18 @@ function _composeOp(a, b, func) { * s("[ sd]*2, hh*8").restart("") */ Pattern.prototype.restart = function (...args) { - return this.keepif.restart(...args); - }; + return this.keepif.restart(...args) + } Pattern.prototype.restartAll = function (...args) { - return this.keep.restart(...args); - }; -})(); + return this.keep.restart(...args) + } +})() // aliases -export const polyrhythm = stack; -export const pr = stack; +export const polyrhythm = stack +export const pr = stack -export const pm = polymeter; +export const pm = polymeter // methods that create patterns, which are added to patternified Pattern methods // TODO: remove? this is only used in old transpiler (shapeshifter) @@ -1230,7 +1236,7 @@ export const pm = polymeter; * @example * gap(3) // "~@3" */ -export const gap = (steps) => new Pattern(() => [], steps); +export const gap = (steps) => new Pattern(() => [], steps) /** * Does absolutely nothing.. @@ -1238,10 +1244,10 @@ export const gap = (steps) => new Pattern(() => [], steps); * @example * silence // "~" */ -export const silence = gap(1); +export const silence = gap(1) /* Like silence, but with a 'steps' (relative duration) of 0 */ -export const nothing = gap(0); +export const nothing = gap(0) /** * A discrete value that repeats once per cycle. @@ -1253,16 +1259,18 @@ export const nothing = gap(0); */ export function pure(value) { function query(state) { - return state.span.spanCycles.map((subspan) => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value)); + return state.span.spanCycles.map( + (subspan) => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value) + ) } - const result = new Pattern(query, 1); - result.__pure = value; - return result; + const result = new Pattern(query, 1) + result.__pure = value + return result } export function isPattern(thing) { // thing?.constructor?.name !== 'Pattern' // <- this will fail when code is mangled - const is = thing instanceof Pattern || thing?._Pattern; + const is = thing instanceof Pattern || thing?._Pattern // TODO: find out how to check wrong core dependency. below will never work !thing === 'undefined' // wrapping it in (..) will result other checks to log that warning (e.g. isPattern('kalimba')) /* if (!thing instanceof Pattern) { @@ -1273,28 +1281,28 @@ export function isPattern(thing) { ); console.log(thing); } */ - return is; + return is } export function reify(thing) { // Turns something into a pattern, unless it's already a pattern if (isPattern(thing)) { - return thing; + return thing } if (stringParser && typeof thing === 'string') { - return stringParser(thing); + return stringParser(thing) } - return pure(thing); + return pure(thing) } /** Takes a list of patterns, and returns a pattern of lists. */ export function sequenceP(pats) { - let result = pure([]); + let result = pure([]) for (const pat of pats) { - result = result.bind((list) => pat.fmap((v) => list.concat([v]))); + result = result.bind((list) => pat.fmap((v) => list.concat([v]))) } - return result; + return result } /** @@ -1314,40 +1322,42 @@ export function sequenceP(pats) { */ export function stack(...pats) { // Array test here is to avoid infinite recursions.. - pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat))); - const query = (state) => flatten(pats.map((pat) => pat.query(state))); - const result = new Pattern(query); + pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat))) + const query = (state) => flatten(pats.map((pat) => pat.query(state))) + const result = new Pattern(query) if (__steps) { - result._steps = lcm(...pats.map((pat) => pat._steps)); + result._steps = lcm(...pats.map((pat) => pat._steps)) } - return result; + return result } function _stackWith(func, pats) { - pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat))); + pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat))) if (pats.length === 0) { - return silence; + return silence } if (pats.length === 1) { - return pats[0]; + return pats[0] } - const [left, ...right] = pats.map((pat) => pat._steps); - const steps = __steps ? left.maximum(...right) : undefined; - return stack(...func(steps, pats)); + const [left, ...right] = pats.map((pat) => pat._steps) + const steps = __steps ? left.maximum(...right) : undefined + return stack(...func(steps, pats)) } export function stackLeft(...pats) { return _stackWith( - (steps, pats) => pats.map((pat) => (pat._steps.eq(steps) ? pat : stepcat(pat, gap(steps.sub(pat._steps))))), - pats, - ); + (steps, pats) => + pats.map((pat) => (pat._steps.eq(steps) ? pat : stepcat(pat, gap(steps.sub(pat._steps))))), + pats + ) } export function stackRight(...pats) { return _stackWith( - (steps, pats) => pats.map((pat) => (pat._steps.eq(steps) ? pat : stepcat(gap(steps.sub(pat._steps)), pat))), - pats, - ); + (steps, pats) => + pats.map((pat) => (pat._steps.eq(steps) ? pat : stepcat(gap(steps.sub(pat._steps)), pat))), + pats + ) } export function stackCentre(...pats) { @@ -1355,30 +1365,30 @@ export function stackCentre(...pats) { (steps, pats) => pats.map((pat) => { if (pat._steps.eq(steps)) { - return pat; + return pat } - const g = gap(steps.sub(pat._steps).div(2)); - return stepcat(g, pat, g); + const g = gap(steps.sub(pat._steps).div(2)) + return stepcat(g, pat, g) }), - pats, - ); + pats + ) } export function stackBy(by, ...pats) { - const [left, ...right] = pats.map((pat) => pat._steps); - const steps = left.maximum(...right); + const [left, ...right] = pats.map((pat) => pat._steps) + const steps = left.maximum(...right) const lookup = { centre: stackCentre, left: stackLeft, right: stackRight, expand: stack, repeat: (...args) => polymeter(...args).steps(steps), - }; + } return by .inhabit(lookup) .fmap((func) => func(...pats)) .innerJoin() - .setSteps(steps); + .setSteps(steps) } /** @@ -1392,28 +1402,30 @@ export function stackBy(by, ...pats) { */ export function slowcat(...pats) { // Array test here is to avoid infinite recursions.. - pats = pats.map((pat) => (Array.isArray(pat) ? fastcat(...pat) : reify(pat))); + pats = pats.map((pat) => (Array.isArray(pat) ? fastcat(...pat) : reify(pat))) if (pats.length == 1) { - return pats[0]; + return pats[0] } const query = function (state) { - const span = state.span; - const pat_n = _mod(span.begin.sam(), pats.length); - const pat = pats[pat_n]; + const span = state.span + const pat_n = _mod(span.begin.sam(), pats.length) + const pat = pats[pat_n] if (!pat) { // pat_n can be negative, if the span is in the past.. - return []; + return [] } // A bit of maths to make sure that cycles from constituent patterns aren't skipped. // For example if three patterns are slowcat-ed, the fourth cycle of the result should // be the second (rather than fourth) cycle from the first pattern. - const offset = span.begin.floor().sub(span.begin.div(pats.length).floor()); - return pat.withHapTime((t) => t.add(offset)).query(state.setSpan(span.withTime((t) => t.sub(offset)))); - }; - const steps = __steps ? lcm(...pats.map((x) => x._steps)) : undefined; - return new Pattern(query).splitQueries().setSteps(steps); + const offset = span.begin.floor().sub(span.begin.div(pats.length).floor()) + return pat + .withHapTime((t) => t.add(offset)) + .query(state.setSpan(span.withTime((t) => t.sub(offset)))) + } + const steps = __steps ? lcm(...pats.map((x) => x._steps)) : undefined + return new Pattern(query).splitQueries().setSteps(steps) } /** Concatenation: combines a list of patterns, switching between them successively, one per cycle. Unlike slowcat, this version will skip cycles. @@ -1421,13 +1433,13 @@ export function slowcat(...pats) { * @return {Pattern} */ export function slowcatPrime(...pats) { - pats = pats.map(reify); + pats = pats.map(reify) const query = function (state) { - const pat_n = Math.floor(state.span.begin) % pats.length; - const pat = pats[pat_n]; // can be undefined for same cases e.g. /#cHVyZSg0MikKICAuZXZlcnkoMyxhZGQoNykpCiAgLmxhdGUoLjUp - return pat?.query(state) || []; - }; - return new Pattern(query).splitQueries(); + const pat_n = Math.floor(state.span.begin) % pats.length + const pat = pats[pat_n] // can be undefined for same cases e.g. /#cHVyZSg0MikKICAuZXZlcnkoMyxhZGQoNykpCiAgLmxhdGUoLjUp + return pat?.query(state) || [] + } + return new Pattern(query).splitQueries() } /** The given items are con**cat**enated, where each one takes one cycle. @@ -1446,7 +1458,7 @@ export function slowcatPrime(...pats) { * ) */ export function cat(...pats) { - return slowcat(...pats); + return slowcat(...pats) } /** @@ -1461,9 +1473,9 @@ export function cat(...pats) { * ).note() */ export function arrange(...sections) { - const total = sections.reduce((sum, [cycles]) => sum + cycles, 0); - sections = sections.map(([cycles, section]) => [cycles, section.fast(cycles)]); - return stepcat(...sections).slow(total); + const total = sections.reduce((sum, [cycles]) => sum + cycles, 0) + sections = sections.map(([cycles, section]) => [cycles, section.fast(cycles)]) + return stepcat(...sections).slow(total) } /** @@ -1478,39 +1490,39 @@ seqPLoop([0, 2, "bd(3,8)"], .sound() */ export function seqPLoop(...parts) { - let total = Fraction(0); - const pats = []; + let total = Fraction(0) + const pats = [] for (let part of parts) { if (part.length == 2) { - part.unshift(total); + part.unshift(total) } - total = part[1]; + total = part[1] } return stack( ...parts.map(([start, stop, pat]) => - pure(reify(pat)).compress(Fraction(start).div(total), Fraction(stop).div(total)), - ), + pure(reify(pat)).compress(Fraction(start).div(total), Fraction(stop).div(total)) + ) ) .slow(total) - .innerJoin(); // or resetJoin or restartJoin ?? + .innerJoin() // or resetJoin or restartJoin ?? } export function fastcat(...pats) { - let result = slowcat(...pats); + let result = slowcat(...pats) if (pats.length > 1) { - result = result._fast(pats.length); - result._steps = pats.length; + result = result._fast(pats.length) + result._steps = pats.length } if (pats.length == 1 && pats[0].__steps_source) { - pats._steps = pats[0]._steps; + pats._steps = pats[0]._steps } - return result; + return result } /** See `fastcat` */ export function sequence(...pats) { - return fastcat(...pats); + return fastcat(...pats) } /** Like **cat**, but the items are crammed into one cycle. @@ -1527,60 +1539,60 @@ export function sequence(...pats) { */ export function seq(...pats) { - return fastcat(...pats); + return fastcat(...pats) } function _sequenceCount(x) { if (Array.isArray(x)) { if (x.length == 0) { - return [silence, 0]; + return [silence, 0] } if (x.length == 1) { - return _sequenceCount(x[0]); + return _sequenceCount(x[0]) } - return [fastcat(...x.map((a) => _sequenceCount(a)[0])), x.length]; + return [fastcat(...x.map((a) => _sequenceCount(a)[0])), x.length] } - return [reify(x), 1]; + return [reify(x), 1] } -export const mask = curry((a, b) => reify(b).mask(a)); -export const struct = curry((a, b) => reify(b).struct(a)); -export const superimpose = curry((a, b) => reify(b).superimpose(...a)); -export const withValue = curry((a, b) => reify(b).withValue(a)); +export const mask = curry((a, b) => reify(b).mask(a)) +export const struct = curry((a, b) => reify(b).struct(a)) +export const superimpose = curry((a, b) => reify(b).superimpose(...a)) +export const withValue = curry((a, b) => reify(b).withValue(a)) -export const bind = curry((a, b) => reify(b).bind(a)); -export const innerBind = curry((a, b) => reify(b).innerBind(a)); -export const outerBind = curry((a, b) => reify(b).outerBind(a)); -export const squeezeBind = curry((a, b) => reify(b).squeezeBind(a)); -export const stepBind = curry((a, b) => reify(b).stepBind(a)); -export const polyBind = curry((a, b) => reify(b).polyBind(a)); +export const bind = curry((a, b) => reify(b).bind(a)) +export const innerBind = curry((a, b) => reify(b).innerBind(a)) +export const outerBind = curry((a, b) => reify(b).outerBind(a)) +export const squeezeBind = curry((a, b) => reify(b).squeezeBind(a)) +export const stepBind = curry((a, b) => reify(b).stepBind(a)) +export const polyBind = curry((a, b) => reify(b).polyBind(a)) // operators -export const set = curry((a, b) => reify(b).set(a)); -export const keep = curry((a, b) => reify(b).keep(a)); -export const keepif = curry((a, b) => reify(b).keepif(a)); -export const add = curry((a, b) => reify(b).add(a)); -export const sub = curry((a, b) => reify(b).sub(a)); -export const mul = curry((a, b) => reify(b).mul(a)); -export const div = curry((a, b) => reify(b).div(a)); -export const mod = curry((a, b) => reify(b).mod(a)); -export const pow = curry((a, b) => reify(b).pow(a)); -export const band = curry((a, b) => reify(b).band(a)); -export const bor = curry((a, b) => reify(b).bor(a)); -export const bxor = curry((a, b) => reify(b).bxor(a)); -export const blshift = curry((a, b) => reify(b).blshift(a)); -export const brshift = curry((a, b) => reify(b).brshift(a)); -export const lt = curry((a, b) => reify(b).lt(a)); -export const gt = curry((a, b) => reify(b).gt(a)); -export const lte = curry((a, b) => reify(b).lte(a)); -export const gte = curry((a, b) => reify(b).gte(a)); -export const eq = curry((a, b) => reify(b).eq(a)); -export const eqt = curry((a, b) => reify(b).eqt(a)); -export const ne = curry((a, b) => reify(b).ne(a)); -export const net = curry((a, b) => reify(b).net(a)); -export const and = curry((a, b) => reify(b).and(a)); -export const or = curry((a, b) => reify(b).or(a)); -export const func = curry((a, b) => reify(b).func(a)); +export const set = curry((a, b) => reify(b).set(a)) +export const keep = curry((a, b) => reify(b).keep(a)) +export const keepif = curry((a, b) => reify(b).keepif(a)) +export const add = curry((a, b) => reify(b).add(a)) +export const sub = curry((a, b) => reify(b).sub(a)) +export const mul = curry((a, b) => reify(b).mul(a)) +export const div = curry((a, b) => reify(b).div(a)) +export const mod = curry((a, b) => reify(b).mod(a)) +export const pow = curry((a, b) => reify(b).pow(a)) +export const band = curry((a, b) => reify(b).band(a)) +export const bor = curry((a, b) => reify(b).bor(a)) +export const bxor = curry((a, b) => reify(b).bxor(a)) +export const blshift = curry((a, b) => reify(b).blshift(a)) +export const brshift = curry((a, b) => reify(b).brshift(a)) +export const lt = curry((a, b) => reify(b).lt(a)) +export const gt = curry((a, b) => reify(b).gt(a)) +export const lte = curry((a, b) => reify(b).lte(a)) +export const gte = curry((a, b) => reify(b).gte(a)) +export const eq = curry((a, b) => reify(b).eq(a)) +export const eqt = curry((a, b) => reify(b).eqt(a)) +export const ne = curry((a, b) => reify(b).ne(a)) +export const net = curry((a, b) => reify(b).net(a)) +export const and = curry((a, b) => reify(b).and(a)) +export const or = curry((a, b) => reify(b).or(a)) +export const func = curry((a, b) => reify(b).func(a)) /** * Registers a new pattern method. The method is added to the Pattern class + the standalone function is returned from register. @@ -1590,94 +1602,106 @@ export const func = curry((a, b) => reify(b).func(a)); * @noAutocomplete * */ -export function register(name, func, patternify = true, preserveSteps = false, join = (x) => x.innerJoin()) { +export function register( + name, + func, + patternify = true, + preserveSteps = false, + join = (x) => x.innerJoin() +) { if (Array.isArray(name)) { - const result = {}; + const result = {} for (const name_item of name) { - result[name_item] = register(name_item, func, patternify, preserveSteps, join); + result[name_item] = register(name_item, func, patternify, preserveSteps, join) } - return result; + return result } - const arity = func.length; - var pfunc; // the patternified function + const arity = func.length + var pfunc // the patternified function if (patternify) { pfunc = function (...args) { - args = args.map(reify); - const pat = args[args.length - 1]; - let result; + args = args.map(reify) + const pat = args[args.length - 1] + let result if (arity === 1) { - result = func(pat); + result = func(pat) } else { - const firstArgs = args.slice(0, -1); + const firstArgs = args.slice(0, -1) if (firstArgs.every((arg) => arg.__pure != undefined)) { - const pureArgs = firstArgs.map((arg) => arg.__pure); - const pureLocs = firstArgs.filter((arg) => arg.__pure_loc).map((arg) => arg.__pure_loc); - result = func(...pureArgs, pat); + const pureArgs = firstArgs.map((arg) => arg.__pure) + const pureLocs = firstArgs.filter((arg) => arg.__pure_loc).map((arg) => arg.__pure_loc) + result = func(...pureArgs, pat) result = result.withContext((context) => { - const locations = (context.locations || []).concat(pureLocs); - return { ...context, locations }; - }); + const locations = (context.locations || []).concat(pureLocs) + return { ...context, locations } + }) } else { - const [left, ...right] = firstArgs; + const [left, ...right] = firstArgs let mapFn = (...args) => { - return func(...args, pat); - }; - mapFn = curry(mapFn, null, arity - 1); - result = join(right.reduce((acc, p) => acc.appLeft(p), left.fmap(mapFn))); + return func(...args, pat) + } + mapFn = curry(mapFn, null, arity - 1) + result = join(right.reduce((acc, p) => acc.appLeft(p), left.fmap(mapFn))) } } if (preserveSteps) { - result._steps = pat._steps; + result._steps = pat._steps } - return result; - }; + return result + } } else { pfunc = function (...args) { - args = args.map(reify); - const result = func(...args); + args = args.map(reify) + const result = func(...args) if (preserveSteps) { - result._steps = args[args.length - 1]._steps; + result._steps = args[args.length - 1]._steps } - return result; - }; + return result + } } Pattern.prototype[name] = function (...args) { // For methods that take a single argument (plus 'this'), allow // multiple arguments but sequence them if (arity === 2 && args.length !== 1) { - args = [sequence(...args)]; + args = [sequence(...args)] } else if (arity !== args.length + 1) { - throw new Error(`.${name}() expects ${arity - 1} inputs but got ${args.length}.`); + throw new Error(`.${name}() expects ${arity - 1} inputs but got ${args.length}.`) } - args = args.map(reify); - return pfunc(...args, this); - }; + args = args.map(reify) + return pfunc(...args, this) + } if (arity > 1) { // There are patternified args, so lets make an unpatternified // version, prefixed by '_' Pattern.prototype['_' + name] = function (...args) { - const result = func(...args, this); + const result = func(...args, this) if (preserveSteps) { - result.setSteps(this._steps); + result.setSteps(this._steps) } - return result; - }; + return result + } } // toplevel functions get curried as well as patternified // because pfunc uses spread args, we need to state the arity explicitly! - return curry(pfunc, null, arity); + return curry(pfunc, null, arity) } // Like register, but defaults to stepJoin -function stepRegister(name, func, patternify = true, preserveSteps = false, join = (x) => x.stepJoin()) { - return register(name, func, patternify, preserveSteps, join); +function stepRegister( + name, + func, + patternify = true, + preserveSteps = false, + join = (x) => x.stepJoin() +) { + return register(name, func, patternify, preserveSteps, join) } ////////////////////////////////////////////////////////////////////// @@ -1693,8 +1717,8 @@ function stepRegister(name, func, patternify = true, preserveSteps = false, join * n("0.5 1.5 2.5".round()).scale("C:major") */ export const round = register('round', function (pat) { - return pat.asNumber().fmap((v) => Math.round(v)); -}); + return pat.asNumber().fmap((v) => Math.round(v)) +}) /** * Assumes a numerical pattern. Returns a new pattern with all values set to @@ -1707,8 +1731,8 @@ export const round = register('round', function (pat) { * note("42 42.1 42.5 43".floor()) */ export const floor = register('floor', function (pat) { - return pat.asNumber().fmap((v) => Math.floor(v)); -}); + return pat.asNumber().fmap((v) => Math.floor(v)) +}) /** * Assumes a numerical pattern. Returns a new pattern with all values set to @@ -1721,8 +1745,8 @@ export const floor = register('floor', function (pat) { * note("42 42.1 42.5 43".ceil()) */ export const ceil = register('ceil', function (pat) { - return pat.asNumber().fmap((v) => Math.ceil(v)); -}); + return pat.asNumber().fmap((v) => Math.ceil(v)) +}) /** * Assumes a numerical pattern, containing unipolar values in the range 0 .. * 1. Returns a new pattern with values scaled to the bipolar range -1 .. 1 @@ -1730,8 +1754,8 @@ export const ceil = register('ceil', function (pat) { * @noAutocomplete */ export const toBipolar = register('toBipolar', function (pat) { - return pat.fmap((x) => x * 2 - 1); -}); + return pat.fmap((x) => x * 2 - 1) +}) /** * Assumes a numerical pattern, containing bipolar values in the range -1 .. 1 @@ -1740,8 +1764,8 @@ export const toBipolar = register('toBipolar', function (pat) { * @noAutocomplete */ export const fromBipolar = register('fromBipolar', function (pat) { - return pat.fmap((x) => (x + 1) / 2); -}); + return pat.fmap((x) => (x + 1) / 2) +}) /** * Assumes a numerical pattern, containing unipolar values in the range 0 .. 1. @@ -1755,8 +1779,8 @@ export const fromBipolar = register('fromBipolar', function (pat) { * .cutoff(sine.range(500,4000)) */ export const range = register('range', function (min, max, pat) { - return pat.mul(max - min).add(min); -}); + return pat.mul(max - min).add(min) +}) /** * Assumes a numerical pattern, containing unipolar values in the range 0 .. 1 @@ -1770,8 +1794,8 @@ export const range = register('range', function (min, max, pat) { * .cutoff(sine.rangex(500,4000)) */ export const rangex = register('rangex', function (min, max, pat) { - return pat._range(Math.log(min), Math.log(max)).fmap(Math.exp); -}); + return pat._range(Math.log(min), Math.log(max)).fmap(Math.exp) +}) /** * Assumes a numerical pattern, containing bipolar values in the range -1 .. 1 @@ -1784,8 +1808,8 @@ export const rangex = register('rangex', function (min, max, pat) { * .cutoff(sine2.range2(500,4000)) */ export const range2 = register('range2', function (min, max, pat) { - return pat.fromBipolar()._range(min, max); -}); + return pat.fromBipolar()._range(min, max) +}) /** * Allows dividing numbers via list notation using ":". @@ -1800,11 +1824,11 @@ export const range2 = register('range2', function (min, max, pat) { export const ratio = register('ratio', (pat) => pat.fmap((v) => { if (!Array.isArray(v)) { - return v; + return v } - return v.slice(1).reduce((acc, n) => acc / n, v[0]); - }), -); + return v.slice(1).reduce((acc, n) => acc / n, v[0]) + }) +) ////////////////////////////////////////////////////////////////////// // Structural and temporal transformations @@ -1817,17 +1841,20 @@ export const ratio = register('ratio', (pat) => * ) */ export const compress = register('compress', function (b, e, pat) { - b = Fraction(b); - e = Fraction(e); + b = Fraction(b) + e = Fraction(e) if (b.gt(e) || b.gt(1) || e.gt(1) || b.lt(0) || e.lt(0)) { - return silence; + return silence } - return pat._fastGap(Fraction(1).div(e.sub(b)))._late(b); -}); + return pat._fastGap(Fraction(1).div(e.sub(b)))._late(b) +}) -export const { compressSpan, compressspan } = register(['compressSpan', 'compressspan'], function (span, pat) { - return pat._compress(span.begin, span.end); -}); +export const { compressSpan, compressspan } = register( + ['compressSpan', 'compressspan'], + function (span, pat) { + return pat._compress(span.begin, span.end) + } +) /** * speeds up a pattern like fast, but rather than it playing multiple times as fast would it instead leaves a gap in the remaining space of the cycle. For example, the following will play the sound pattern "bd sn" only once but compressed into the first half of the cycle, i.e. twice as fast. @@ -1839,32 +1866,32 @@ export const { compressSpan, compressspan } = register(['compressSpan', 'compres export const { fastGap, fastgap } = register(['fastGap', 'fastgap'], function (factor, pat) { // A bit fiddly, to drop zero-width queries at the start of the next cycle const qf = function (span) { - const cycle = span.begin.sam(); - const bpos = span.begin.sub(cycle).mul(factor).min(1); - const epos = span.end.sub(cycle).mul(factor).min(1); + const cycle = span.begin.sam() + const bpos = span.begin.sub(cycle).mul(factor).min(1) + const epos = span.end.sub(cycle).mul(factor).min(1) if (bpos >= 1) { - return undefined; + return undefined } - return new TimeSpan(cycle.add(bpos), cycle.add(epos)); - }; + return new TimeSpan(cycle.add(bpos), cycle.add(epos)) + } // Also fiddly, to maintain the right 'whole' relative to the part const ef = function (hap) { - const begin = hap.part.begin; - const end = hap.part.end; - const cycle = begin.sam(); - const beginPos = begin.sub(cycle).div(factor).min(1); - const endPos = end.sub(cycle).div(factor).min(1); - const newPart = new TimeSpan(cycle.add(beginPos), cycle.add(endPos)); + const begin = hap.part.begin + const end = hap.part.end + const cycle = begin.sam() + const beginPos = begin.sub(cycle).div(factor).min(1) + const endPos = end.sub(cycle).div(factor).min(1) + const newPart = new TimeSpan(cycle.add(beginPos), cycle.add(endPos)) const newWhole = !hap.whole ? undefined : new TimeSpan( newPart.begin.sub(begin.sub(hap.whole.begin).div(factor)), - newPart.end.add(hap.whole.end.sub(end).div(factor)), - ); - return new Hap(newWhole, newPart, hap.value, hap.context); - }; - return pat.withQuerySpanMaybe(qf).withHap(ef).splitQueries(); -}); + newPart.end.add(hap.whole.end.sub(end).div(factor)) + ) + return new Hap(newWhole, newPart, hap.value, hap.context) + } + return pat.withQuerySpanMaybe(qf).withHap(ef).splitQueries() +}) /** * Similar to `compress`, but doesn't leave gaps, and the 'focus' can be bigger than a cycle @@ -1872,29 +1899,29 @@ export const { fastGap, fastgap } = register(['fastGap', 'fastgap'], function (f * s("bd hh sd hh").focus(1/4, 3/4) */ export const focus = register('focus', function (b, e, pat) { - b = Fraction(b); - e = Fraction(e); + b = Fraction(b) + e = Fraction(e) return pat ._early(b.sam()) ._fast(Fraction(1).div(e.sub(b))) - ._late(b); -}); + ._late(b) +}) export const { focusSpan, focusspan } = register(['focusSpan', 'focusspan'], function (span, pat) { - return pat._focus(span.begin, span.end); -}); + return pat._focus(span.begin, span.end) +}) /** The ply function repeats each event the given number of times. * @example * s("bd ~ sd cp").ply("<1 2 3>") */ export const ply = register('ply', function (factor, pat) { - const result = pat.fmap((x) => pure(x)._fast(factor)).squeezeJoin(); + const result = pat.fmap((x) => pure(x)._fast(factor)).squeezeJoin() if (__steps) { - result._steps = Fraction(factor).mulmaybe(pat._steps); + result._steps = Fraction(factor).mulmaybe(pat._steps) } - return result; -}); + return result +}) /** * Speed up a pattern by the given factor. Used by "*" in mini notation. @@ -1911,15 +1938,15 @@ export const { fast, density } = register( ['fast', 'density'], function (factor, pat) { if (factor === 0) { - return silence; + return silence } - factor = Fraction(factor); - const fastQuery = pat.withQueryTime((t) => t.mul(factor)); - return fastQuery.withHapTime((t) => t.div(factor)).setSteps(pat._steps); + factor = Fraction(factor) + const fastQuery = pat.withQueryTime((t) => t.mul(factor)) + return fastQuery.withHapTime((t) => t.div(factor)).setSteps(pat._steps) }, true, - true, -); + true +) /** * Both speeds up the pattern (like 'fast') and the sample playback (like 'speed'). @@ -1927,8 +1954,8 @@ export const { fast, density } = register( * s("bd sd:2").hurry("<1 2 4 3>").slow(1.5) */ export const hurry = register('hurry', function (r, pat) { - return pat._fast(r).mul(pure({ speed: r })); -}); + return pat._fast(r).mul(pure({ speed: r })) +}) /** * Slow down a pattern over the given number of cycles. Like the "/" operator in mini notation. @@ -1943,10 +1970,10 @@ export const hurry = register('hurry', function (r, pat) { */ export const { slow, sparsity } = register(['slow', 'sparsity'], function (factor, pat) { if (factor === 0) { - return silence; + return silence } - return pat._fast(Fraction(1).div(factor)); -}); + return pat._fast(Fraction(1).div(factor)) +}) /** * Carries out an operation 'inside' a cycle. @@ -1955,8 +1982,8 @@ export const { slow, sparsity } = register(['slow', 'sparsity'], function (facto * // "0 1 2 3 4 3 2 1".slow(4).rev().fast(4).scale('C major').note() */ export const inside = register('inside', function (factor, f, pat) { - return f(pat._slow(factor))._fast(factor); -}); + return f(pat._slow(factor))._fast(factor) +}) /** * Carries out an operation 'outside' a cycle. @@ -1965,8 +1992,8 @@ export const inside = register('inside', function (factor, f, pat) { * // "<[0 1] 2 [3 4] 5>".fast(4).rev().slow(4).scale('C major').note() */ export const outside = register('outside', function (factor, f, pat) { - return f(pat._fast(factor))._slow(factor); -}); + return f(pat._fast(factor))._slow(factor) +}) /** * Applies the given function every n cycles, starting from the last cycle. @@ -1979,10 +2006,10 @@ export const outside = register('outside', function (factor, f, pat) { * note("c3 d3 e3 g3").lastOf(4, x=>x.rev()) */ export const lastOf = register('lastOf', function (n, func, pat) { - const pats = Array(n - 1).fill(pat); - pats.push(func(pat)); - return slowcatPrime(...pats); -}); + const pats = Array(n - 1).fill(pat) + pats.push(func(pat)) + return slowcatPrime(...pats) +}) /** * Applies the given function every n cycles, starting from the first cycle. @@ -2006,10 +2033,10 @@ export const lastOf = register('lastOf', function (n, func, pat) { * note("c3 d3 e3 g3").every(4, x=>x.rev()) */ export const { firstOf, every } = register(['firstOf', 'every'], function (n, func, pat) { - const pats = Array(n - 1).fill(pat); - pats.unshift(func(pat)); - return slowcatPrime(...pats); -}); + const pats = Array(n - 1).fill(pat) + pats.unshift(func(pat)) + return slowcatPrime(...pats) +}) /** * Like layer, but with a single function: @@ -2020,8 +2047,8 @@ export const { firstOf, every } = register(['firstOf', 'every'], function (n, fu */ // TODO: remove or dedupe with layer? export const apply = register('apply', function (func, pat) { - return func(pat); -}); + return func(pat) +}) /** * Plays the pattern at the given cycles per minute. @@ -2031,8 +2058,8 @@ export const apply = register('apply', function (func, pat) { */ // this is redefined in repl.mjs, using the current cps as divisor export const cpm = register('cpm', function (cpm, pat) { - return pat._fast(cpm / 60 / 1); -}); + return pat._fast(cpm / 60 / 1) +}) /** * Nudge a pattern to start earlier in time. Equivalent of Tidal's <~ operator @@ -2047,12 +2074,12 @@ export const cpm = register('cpm', function (cpm, pat) { export const early = register( 'early', function (offset, pat) { - offset = Fraction(offset); - return pat.withQueryTime((t) => t.add(offset)).withHapTime((t) => t.sub(offset)); + offset = Fraction(offset) + return pat.withQueryTime((t) => t.add(offset)).withHapTime((t) => t.sub(offset)) }, true, - true, -); + true +) /** * Nudge a pattern to start later in time. Equivalent of Tidal's ~> operator @@ -2067,12 +2094,12 @@ export const early = register( export const late = register( 'late', function (offset, pat) { - offset = Fraction(offset); - return pat._early(Fraction(0).sub(offset)); + offset = Fraction(offset) + return pat._early(Fraction(0).sub(offset)) }, true, - true, -); + true +) /** * Plays a portion of a pattern, specified by the beginning and end of a time span. The new resulting pattern is played over the time period of the original pattern: @@ -2082,23 +2109,23 @@ export const late = register( * // s("hh*3 [sd bd]*2") // equivalent */ export const zoom = register('zoom', function (s, e, pat) { - e = Fraction(e); - s = Fraction(s); + e = Fraction(e) + s = Fraction(s) if (s.gte(e)) { - return nothing; + return nothing } - const d = e.sub(s); - const steps = __steps ? pat._steps?.mulmaybe(d) : undefined; + const d = e.sub(s) + const steps = __steps ? pat._steps?.mulmaybe(d) : undefined return pat .withQuerySpan((span) => span.withCycle((t) => t.mul(d).add(s))) .withHapSpan((span) => span.withCycle((t) => t.sub(s).div(d))) .splitQueries() - .setSteps(steps); -}); + .setSteps(steps) +}) export const { zoomArc, zoomarc } = register(['zoomArc', 'zoomarc'], function (a, pat) { - return pat.zoom(a.begin, a.end); -}); + return pat.zoom(a.begin, a.end) +}) /** * Splits a pattern into the given number of slices, and plays them according to a pattern of slice numbers. @@ -2118,15 +2145,15 @@ export const bite = register( (npat, ipat, pat) => { return ipat .fmap((i) => (n) => { - const a = Fraction(i).div(n).mod(1); - const b = a.add(Fraction(1).div(n)); - return pat.zoom(a, b); + const a = Fraction(i).div(n).mod(1) + const b = a.add(Fraction(1).div(n)) + return pat.zoom(a, b) }) .appLeft(npat) - .squeezeJoin(); + .squeezeJoin() }, - false, -); + false +) /** * Selects the given fraction of the pattern and repeats that part to fill the remainder of the cycle. @@ -2138,15 +2165,15 @@ export const linger = register( 'linger', function (t, pat) { if (t == 0) { - return silence; + return silence } else if (t < 0) { - return pat._zoom(t.add(1), 1)._slow(t); + return pat._zoom(t.add(1), 1)._slow(t) } - return pat._zoom(0, t)._slow(t); + return pat._zoom(0, t)._slow(t) }, true, - true, -); + true +) /** * Samples the pattern at a rate of n events per cycle. Useful for turning a continuous pattern into a discrete one. @@ -2157,8 +2184,8 @@ export const linger = register( * note(saw.range(40,52).segment(24)) */ export const { segment, seg } = register(['segment', 'seg'], function (rate, pat) { - return pat.struct(pure(true)._fast(rate)).setSteps(rate); -}); + return pat.struct(pure(true)._fast(rate)).setSteps(rate) +}) /** * The function `swingBy x n` breaks each cycle into `n` slices, and then delays events in the second half of each slice by the amount `x`, which is relative to the size of the (half) slice. So if `x` is 0 it does nothing, `0.5` delays for half the note duration, and 1 will wrap around to doing nothing again. The end result is a shuffle or swing-like rhythm @@ -2167,7 +2194,9 @@ export const { segment, seg } = register(['segment', 'seg'], function (rate, pat * @example * s("hh*8").swingBy(1/3, 4) */ -export const swingBy = register('swingBy', (swing, n, pat) => pat.inside(n, late(seq(0, swing / 2)))); +export const swingBy = register('swingBy', (swing, n, pat) => + pat.inside(n, late(seq(0, swing / 2))) +) /** * Shorthand for swingBy with 1/3: @@ -2176,7 +2205,7 @@ export const swingBy = register('swingBy', (swing, n, pat) => pat.inside(n, late * s("hh*8").swing(4) * // s("hh*8").swingBy(1/3, 4) */ -export const swing = register('swing', (n, pat) => pat.swingBy(1 / 3, n)); +export const swing = register('swing', (n, pat) => pat.swingBy(1 / 3, n)) /** * Swaps 1s and 0s in a binary pattern. @@ -2189,11 +2218,11 @@ export const { invert, inv } = register( ['invert', 'inv'], function (pat) { // Swap true/false in a binary pattern - return pat.fmap((x) => !x); + return pat.fmap((x) => !x) }, true, - true, -); + true +) /** * Applies the given function whenever the given pattern is in a true state. @@ -2206,8 +2235,8 @@ export const { invert, inv } = register( * "c3 eb3 g3".when("<0 1>/2", x=>x.sub("5")).note() */ export const when = register('when', function (on, func, pat) { - return on ? func(pat) : pat; -}); + return on ? func(pat) : pat +}) /** * Superimposes the function result on top of the original pattern, delayed by the given time. @@ -2220,8 +2249,8 @@ export const when = register('when', function (on, func, pat) { * "c3 eb3 g3".off(1/8, x=>x.add(7)).note() */ export const off = register('off', function (time_pat, func, pat) { - return stack(pat, func(pat.late(time_pat))); -}); + return stack(pat, func(pat.late(time_pat))) +}) /** * Returns a new pattern where every other cycle is played once, twice as @@ -2230,8 +2259,8 @@ export const off = register('off', function (time_pat, func, pat) { * @returns Pattern */ export const brak = register('brak', function (pat) { - return pat.when(slowcat(false, true), (x) => fastcat(x, silence)._late(0.25)); -}); + return pat.when(slowcat(false, true), (x) => fastcat(x, silence)._late(0.25)) +}) /** * Reverse all haps in a pattern @@ -2246,25 +2275,25 @@ export const rev = register( 'rev', function (pat) { const query = function (state) { - const span = state.span; - const cycle = span.begin.sam(); - const next_cycle = span.begin.nextSam(); + const span = state.span + const cycle = span.begin.sam() + const next_cycle = span.begin.nextSam() const reflect = function (to_reflect) { - const reflected = to_reflect.withTime((time) => cycle.add(next_cycle.sub(time))); + const reflected = to_reflect.withTime((time) => cycle.add(next_cycle.sub(time))) // [reflected.begin, reflected.end] = [reflected.end, reflected.begin] -- didn't work - const tmp = reflected.begin; - reflected.begin = reflected.end; - reflected.end = tmp; - return reflected; - }; - const haps = pat.query(state.setSpan(reflect(span))); - return haps.map((hap) => hap.withSpan(reflect)); - }; - return new Pattern(query).splitQueries(); + const tmp = reflected.begin + reflected.begin = reflected.end + reflected.end = tmp + return reflected + } + const haps = pat.query(state.setSpan(reflect(span))) + return haps.map((hap) => hap.withSpan(reflect)) + } + return new Pattern(query).splitQueries() }, false, - true, -); + true +) /** Like press, but allows you to specify the amount by which each * event is shifted. pressBy(0.5) is the same as press, while @@ -2275,8 +2304,8 @@ export const rev = register( * ).slow(2) */ export const pressBy = register('pressBy', function (r, pat) { - return pat.fmap((x) => pure(x).compress(r, 1)).squeezeJoin(); -}); + return pat.fmap((x) => pure(x).compress(r, 1)).squeezeJoin() +}) /** * Syncopates a rhythm, by shifting each event halfway into its timespan. @@ -2286,8 +2315,8 @@ export const pressBy = register('pressBy', function (r, pat) { * ).slow(2) */ export const press = register('press', function (pat) { - return pat._pressBy(0.5); -}); + return pat._pressBy(0.5) +}) /** * Silences a pattern. @@ -2298,8 +2327,8 @@ export const press = register('press', function (pat) { * ) */ Pattern.prototype.hush = function () { - return silence; -}; + return silence +} /** * Applies `rev` to a pattern every other cycle, so that the pattern alternates between forwards and backwards. @@ -2309,11 +2338,11 @@ Pattern.prototype.hush = function () { export const palindrome = register( 'palindrome', function (pat) { - return pat.lastOf(2, rev); + return pat.lastOf(2, rev) }, true, - true, -); + true +) /** * Jux with adjustable stereo width. 0 = mono, 1 = full stereo. @@ -2323,18 +2352,22 @@ export const palindrome = register( * s("bd lt [~ ht] mt cp ~ bd hh").juxBy("<0 .5 1>/2", rev) */ export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func, pat) { - by /= 2; + by /= 2 const elem_or = function (dict, key, dflt) { if (key in dict) { - return dict[key]; + return dict[key] } - return dflt; - }; - const left = pat.withValue((val) => Object.assign({}, val, { pan: elem_or(val, 'pan', 0.5) - by })); - const right = func(pat.withValue((val) => Object.assign({}, val, { pan: elem_or(val, 'pan', 0.5) + by }))); + return dflt + } + const left = pat.withValue((val) => + Object.assign({}, val, { pan: elem_or(val, 'pan', 0.5) - by }) + ) + const right = func( + pat.withValue((val) => Object.assign({}, val, { pan: elem_or(val, 'pan', 0.5) + by })) + ) - return stack(left, right).setSteps(__steps ? lcm(left._steps, right._steps) : undefined); -}); + return stack(left, right).setSteps(__steps ? lcm(left._steps, right._steps) : undefined) +}) /** * The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel. @@ -2346,8 +2379,8 @@ export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func, * s("bd lt [~ ht] mt cp ~ bd hh").jux(iter(4)) */ export const jux = register('jux', function (func, pat) { - return pat._juxBy(1, func, pat); -}); + return pat._juxBy(1, func, pat) +}) /** * Superimpose and offset multiple times, applying the given function each time. @@ -2364,9 +2397,9 @@ export const jux = register('jux', function (func, pat) { export const { echoWith, echowith, stutWith, stutwith } = register( ['echoWith', 'echowith', 'stutWith', 'stutwith'], function (times, time, func, pat) { - return stack(...listRange(0, times - 1).map((i) => func(pat.late(Fraction(time).mul(i)), i))); - }, -); + return stack(...listRange(0, times - 1).map((i) => func(pat.late(Fraction(time).mul(i)), i))) + } +) /** * Superimpose and offset multiple times, gradually decreasing the velocity @@ -2380,8 +2413,8 @@ export const { echoWith, echowith, stutWith, stutwith } = register( * s("bd sd").echo(3, 1/6, .8) */ export const echo = register('echo', function (times, time, feedback, pat) { - return pat._echoWith(times, time, (pat, i) => pat.gain(Math.pow(feedback, i))); -}); + return pat._echoWith(times, time, (pat, i) => pat.gain(Math.pow(feedback, i))) +}) /** * Deprecated. Like echo, but the last 2 parameters are flipped. @@ -2393,16 +2426,16 @@ export const echo = register('echo', function (times, time, feedback, pat) { * s("bd sd").stut(3, .8, 1/6) */ export const stut = register('stut', function (times, feedback, time, pat) { - return pat._echoWith(times, time, (pat, i) => pat.gain(Math.pow(feedback, i))); -}); + return pat._echoWith(times, time, (pat, i) => pat.gain(Math.pow(feedback, i))) +}) export const applyN = register('applyN', function (n, func, p) { - let result = p; + let result = p for (let i = 0; i < n; i++) { - result = func(result); + result = func(result) } - return result; -}); + return result +}) /** * The plyWith function repeats each event the given number of times, applying the given function to each event.\n @@ -2418,12 +2451,12 @@ export const applyN = register('applyN', function (n, func, p) { export const plyWith = register(['plyWith', 'plywith'], function (factor, func, pat) { const result = pat .fmap((x) => cat(...listRange(0, factor - 1).map((i) => applyN(i, func, x)))._fast(factor)) - .squeezeJoin(); + .squeezeJoin() if (__steps) { - result._steps = Fraction(factor).mulmaybe(pat._steps); + result._steps = Fraction(factor).mulmaybe(pat._steps) } - return result; -}); + return result +}) /** * The plyForEach function repeats each event the given number of times, applying the given function to each event. @@ -2439,13 +2472,15 @@ export const plyWith = register(['plyWith', 'plywith'], function (factor, func, */ export const plyForEach = register(['plyForEach', 'plyforeach'], function (factor, func, pat) { const result = pat - .fmap((x) => cat(cat(pure(x), ...listRange(1, factor - 1).map((i) => func(pure(x), i))))._fast(factor)) - .squeezeJoin(); + .fmap((x) => + cat(cat(pure(x), ...listRange(1, factor - 1).map((i) => func(pure(x), i))))._fast(factor) + ) + .squeezeJoin() if (__steps) { - result._steps = Fraction(factor).mulmaybe(pat._steps); + result._steps = Fraction(factor).mulmaybe(pat._steps) } - return result; -}); + return result +}) /** * Divides a pattern into a given number of subdivisions, plays the subdivisions in order, but increments the starting subdivision each cycle. The pattern wraps to the first subdivision after the last subdivision is played. @@ -2457,22 +2492,22 @@ export const plyForEach = register(['plyForEach', 'plyforeach'], function (facto */ const _iter = function (times, pat, back = false) { - times = Fraction(times); + times = Fraction(times) return slowcat( ...listRange(0, times.sub(1)).map((i) => - back ? pat.late(Fraction(i).div(times)) : pat.early(Fraction(i).div(times)), - ), - ); -}; + back ? pat.late(Fraction(i).div(times)) : pat.early(Fraction(i).div(times)) + ) + ) +} export const iter = register( 'iter', function (times, pat) { - return _iter(times, pat, false); + return _iter(times, pat, false) }, true, - true, -); + true +) /** * Like `iter`, but plays the subdivisions in reverse order. Known as iter' in tidalcycles @@ -2486,11 +2521,11 @@ export const iter = register( export const { iterBack, iterback } = register( ['iterBack', 'iterback'], function (times, pat) { - return _iter(times, pat, true); + return _iter(times, pat, true) }, true, - true, -); + true +) /** * Repeats each cycle the given number of times. @@ -2504,16 +2539,18 @@ export const { repeatCycles } = register( 'repeatCycles', function (n, pat) { return new Pattern(function (state) { - const cycle = state.span.begin.sam(); - const source_cycle = cycle.div(n).sam(); - const delta = cycle.sub(source_cycle); - state = state.withSpan((span) => span.withTime((spant) => spant.sub(delta))); - return pat.query(state).map((hap) => hap.withSpan((span) => span.withTime((spant) => spant.add(delta)))); - }).splitQueries(); + const cycle = state.span.begin.sam() + const source_cycle = cycle.div(n).sam() + const delta = cycle.sub(source_cycle) + state = state.withSpan((span) => span.withTime((spant) => spant.sub(delta))) + return pat + .query(state) + .map((hap) => hap.withSpan((span) => span.withTime((spant) => spant.add(delta)))) + }).splitQueries() }, true, - true, -); + true +) /** * Divides a pattern into a given number of parts, then cycles through those parts in turn, applying the given function to each part in turn (one part per cycle). @@ -2526,25 +2563,25 @@ export const { repeatCycles } = register( * .scale("A:minor").note() */ const _chunk = function (n, func, pat, back = false, fast = false) { - const binary = Array(n - 1).fill(false); - binary.unshift(true); + const binary = Array(n - 1).fill(false) + binary.unshift(true) // Invert the 'back' because we want to shift the pattern forwards, // and so time backwards - const binary_pat = _iter(n, sequence(...binary), !back); + const binary_pat = _iter(n, sequence(...binary), !back) if (!fast) { - pat = pat.repeatCycles(n); + pat = pat.repeatCycles(n) } - return pat.when(binary_pat, func); -}; + return pat.when(binary_pat, func) +} export const { chunk, slowchunk, slowChunk } = register( ['chunk', 'slowchunk', 'slowChunk'], function (n, func, pat) { - return _chunk(n, func, pat, false, false); + return _chunk(n, func, pat, false, false) }, true, - true, -); + true +) /** * Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles @@ -2559,11 +2596,11 @@ export const { chunk, slowchunk, slowChunk } = register( export const { chunkBack, chunkback } = register( ['chunkBack', 'chunkback'], function (n, func, pat) { - return _chunk(n, func, pat, true); + return _chunk(n, func, pat, true) }, true, - true, -); + true +) /** * Like `chunk`, but the cycles of the source pattern aren't repeated @@ -2580,11 +2617,11 @@ export const { chunkBack, chunkback } = register( export const { fastchunk, fastChunk } = register( ['fastchunk', 'fastChunk'], function (n, func, pat) { - return _chunk(n, func, pat, false, true); + return _chunk(n, func, pat, false, true) }, true, - true, -); + true +) /** * Like `chunk`, but the function is applied to a looped subcycle of the source pattern. @@ -2595,9 +2632,12 @@ export const { fastchunk, fastChunk } = register( * sound("bd sd ht lt bd - cp lt").chunkInto(4, hurry(2)) * .bank("tr909") */ -export const { chunkinto, chunkInto } = register(['chunkinto', 'chunkInto'], function (n, func, pat) { - return pat.into(fastcat(true, ...Array(n - 1).fill(false))._iterback(n), func); -}); +export const { chunkinto, chunkInto } = register( + ['chunkinto', 'chunkInto'], + function (n, func, pat) { + return pat.into(fastcat(true, ...Array(n - 1).fill(false))._iterback(n), func) + } +) /** * Like `chunkInto`, but moves backwards through the chunks. @@ -2608,25 +2648,28 @@ export const { chunkinto, chunkInto } = register(['chunkinto', 'chunkInto'], fun * sound("bd sd ht lt bd - cp lt").chunkInto(4, hurry(2)) * .bank("tr909") */ -export const { chunkbackinto, chunkBackInto } = register(['chunkbackinto', 'chunkBackInto'], function (n, func, pat) { - return pat.into( - fastcat(true, ...Array(n - 1).fill(false)) - ._iter(n) - ._early(1), - func, - ); -}); +export const { chunkbackinto, chunkBackInto } = register( + ['chunkbackinto', 'chunkBackInto'], + function (n, func, pat) { + return pat.into( + fastcat(true, ...Array(n - 1).fill(false)) + ._iter(n) + ._early(1), + func + ) + } +) // TODO - redefine elsewhere in terms of mask export const bypass = register( 'bypass', function (on, pat) { - on = Boolean(parseInt(on)); - return on ? silence : pat; + on = Boolean(parseInt(on)) + return on ? silence : pat }, true, - true, -); + true +) /** * Loops the pattern inside an `offset` for `cycles`. @@ -2645,16 +2688,16 @@ export const bypass = register( * s("bd!16?").ribbon(29,.5) */ export const { ribbon, rib } = register(['ribbon', 'rib'], (offset, cycles, pat) => - pat.early(offset).restart(pure(1).slow(cycles)), -); + pat.early(offset).restart(pure(1).slow(cycles)) +) export const hsla = register('hsla', (h, s, l, a, pat) => { - return pat.color(`hsla(${h}turn,${s * 100}%,${l * 100}%,${a})`); -}); + return pat.color(`hsla(${h}turn,${s * 100}%,${l * 100}%,${a})`) +}) export const hsl = register('hsl', (h, s, l, pat) => { - return pat.color(`hsl(${h}turn,${s * 100}%,${l * 100}%)`); -}); + return pat.color(`hsl(${h}turn,${s * 100}%,${l * 100}%)`) +}) /** * Tags each Hap with an identifier. Good for filtering. The function populates Hap.context.tags (Array). @@ -2663,8 +2706,8 @@ export const hsl = register('hsl', (h, s, l, pat) => { * @param {string} tag anything unique */ Pattern.prototype.tag = function (tag) { - return this.withContext((ctx) => ({ ...ctx, tags: (ctx.tags || []).concat([tag]) })); -}; + return this.withContext((ctx) => ({ ...ctx, tags: (ctx.tags || []).concat([tag]) })) +} /** * Filters haps using the given function @@ -2673,7 +2716,7 @@ Pattern.prototype.tag = function (tag) { * @example * s("hh!7 oh").filter(hap => hap.value.s==='hh') */ -export const filter = register('filter', (test, pat) => pat.withHaps((haps) => haps.filter(test))); +export const filter = register('filter', (test, pat) => pat.withHaps((haps) => haps.filter(test))) /** * Filters haps by their begin time @@ -2681,7 +2724,9 @@ export const filter = register('filter', (test, pat) => pat.withHaps((haps) => h * @noAutocomplete * @param {Function} test function to test Hap.whole.begin */ -export const filterWhen = register('filterWhen', (test, pat) => pat.filter((h) => test(h.whole.begin))); +export const filterWhen = register('filterWhen', (test, pat) => + pat.filter((h) => test(h.whole.begin)) +) /** * Use within to apply a function to only a part of a pattern. @@ -2693,67 +2738,73 @@ export const filterWhen = register('filterWhen', (test, pat) => pat.filter((h) = export const within = register('within', (a, b, fn, pat) => stack( fn(pat.filterWhen((t) => t.cyclePos() >= a && t.cyclePos() <= b)), - pat.filterWhen((t) => t.cyclePos() < a || t.cyclePos() > b), - ), -); + pat.filterWhen((t) => t.cyclePos() < a || t.cyclePos() > b) + ) +) ////////////////////////////////////////////////////////////////////// // Stepwise functions Pattern.prototype.stepJoin = function () { - const pp = this; - const first_t = stepcat(..._retime(_slices(pp.queryArc(0, 1))))._steps; + const pp = this + const first_t = stepcat(..._retime(_slices(pp.queryArc(0, 1))))._steps const q = function (state) { - const shifted = pp.early(state.span.begin.sam()); - const haps = shifted.query(state.setSpan(new TimeSpan(Fraction(0), Fraction(1)))); - const pat = stepcat(..._retime(_slices(haps))); - return pat.query(state); - }; - return new Pattern(q, first_t); -}; + const shifted = pp.early(state.span.begin.sam()) + const haps = shifted.query(state.setSpan(new TimeSpan(Fraction(0), Fraction(1)))) + const pat = stepcat(..._retime(_slices(haps))) + return pat.query(state) + } + return new Pattern(q, first_t) +} Pattern.prototype.stepBind = function (func) { - return this.fmap(func).stepJoin(); -}; + return this.fmap(func).stepJoin() +} export function _retime(timedHaps) { - const occupied_perc = timedHaps.filter((t, pat) => pat.hasSteps).reduce((a, b) => a.add(b), Fraction(0)); + const occupied_perc = timedHaps + .filter((t, pat) => pat.hasSteps) + .reduce((a, b) => a.add(b), Fraction(0)) const occupied_steps = removeUndefineds(timedHaps.map((t, pat) => pat._steps)).reduce( (a, b) => a.add(b), - Fraction(0), - ); - const total_steps = occupied_perc.eq(0) ? undefined : occupied_steps.div(occupied_perc); + Fraction(0) + ) + const total_steps = occupied_perc.eq(0) ? undefined : occupied_steps.div(occupied_perc) function adjust(dur, pat) { if (pat._steps === undefined) { - return [dur.mulmaybe(total_steps), pat]; + return [dur.mulmaybe(total_steps), pat] } - return [pat._steps, pat]; + return [pat._steps, pat] } - return timedHaps.map((x) => adjust(...x)); + return timedHaps.map((x) => adjust(...x)) } export function _slices(haps) { // slices evs = map (\s -> ((snd s - fst s), stack $ map value $ fit s evs)) // $ pairs $ sort $ nubOrd $ 0:1:concatMap (\ev -> start (part ev):stop (part ev):[]) evs - const breakpoints = flatten(haps.map((hap) => [hap.part.begin, hap.part.end])); - const unique = uniqsortr([Fraction(0), Fraction(1), ...breakpoints]); - const slicespans = pairs(unique); + const breakpoints = flatten(haps.map((hap) => [hap.part.begin, hap.part.end])) + const unique = uniqsortr([Fraction(0), Fraction(1), ...breakpoints]) + const slicespans = pairs(unique) return slicespans.map((s) => [ s[1].sub(s[0]), - stack(..._fitslice(new TimeSpan(...s), haps).map((x) => x.value.withHap((h) => h.setContext(h.combineContext(x))))), - ]); + stack( + ..._fitslice(new TimeSpan(...s), haps).map((x) => + x.value.withHap((h) => h.setContext(h.combineContext(x))) + ) + ), + ]) } export function _fitslice(span, haps) { - return removeUndefineds(haps.map((hap) => _match(span, hap))); + return removeUndefineds(haps.map((hap) => _match(span, hap))) } export function _match(span, hap_p) { - const subspan = span.intersection(hap_p.part); + const subspan = span.intersection(hap_p.part) if (subspan == undefined) { - return undefined; + return undefined } - return new Hap(hap_p.whole, subspan, hap_p.value, hap_p.context); + return new Hap(hap_p.whole, subspan, hap_p.value, hap_p.context) } /** @@ -2766,35 +2817,35 @@ export function _match(span, hap_p) { */ export const pace = register('pace', function (targetSteps, pat) { if (pat._steps === undefined) { - return pat; + return pat } if (pat._steps.eq(Fraction(0))) { // avoid divide by zero.. - return nothing; + return nothing } - return pat._fast(Fraction(targetSteps).div(pat._steps)).setSteps(targetSteps); -}); + return pat._fast(Fraction(targetSteps).div(pat._steps)).setSteps(targetSteps) +}) export function _polymeterListSteps(steps, ...args) { - const seqs = args.map((a) => _sequenceCount(a)); + const seqs = args.map((a) => _sequenceCount(a)) if (seqs.length == 0) { - return silence; + return silence } if (steps == 0) { - steps = seqs[0][1]; + steps = seqs[0][1] } - const pats = []; + const pats = [] for (const seq of seqs) { if (seq[1] == 0) { - continue; + continue } if (steps == seq[1]) { - pats.push(seq[0]); + pats.push(seq[0]) } else { - pats.push(seq[0]._fast(Fraction(steps).div(Fraction(seq[1])))); + pats.push(seq[0]._fast(Fraction(steps).div(Fraction(seq[1])))) } } - return stack(...pats); + return stack(...pats) } /** @@ -2810,23 +2861,23 @@ export function _polymeterListSteps(steps, ...args) { export function polymeter(...args) { if (Array.isArray(args[0])) { // Support old behaviour - return _polymeterListSteps(0, ...args); + return _polymeterListSteps(0, ...args) } // TODO currently ignoring arguments without steps... - args = args.filter((arg) => arg.hasSteps); + args = args.filter((arg) => arg.hasSteps) if (args.length == 0) { - return silence; + return silence } - const steps = lcm(...args.map((x) => x._steps)); + const steps = lcm(...args.map((x) => x._steps)) if (steps.eq(Fraction(0))) { - return nothing; + return nothing } - const result = stack(...args.map((x) => x.pace(steps))); - result._steps = steps; - return result; + const result = stack(...args.map((x) => x.pace(steps))) + result._steps = steps + return result } /** 'Concatenates' patterns like `fastcat`, but proportional to a number of steps per cycle. @@ -2844,44 +2895,44 @@ export function polymeter(...args) { */ export function stepcat(...timepats) { if (timepats.length === 0) { - return nothing; + return nothing } - const findsteps = (x) => (Array.isArray(x) ? x : [x._steps ?? 1, x]); - timepats = timepats.map(findsteps); + const findsteps = (x) => (Array.isArray(x) ? x : [x._steps ?? 1, x]) + timepats = timepats.map(findsteps) if (timepats.find((x) => x[0] === undefined)) { - const times = timepats.map((a) => a[0]).filter((x) => x !== undefined); + const times = timepats.map((a) => a[0]).filter((x) => x !== undefined) if (times.length === 0) { - return fastcat(...timepats.map((x) => x[1])); + return fastcat(...timepats.map((x) => x[1])) } if (times.length === timepats.length) { - return nothing; + return nothing } - const avg = times.reduce((a, b) => a.add(b), Fraction(0)).div(times.length); + const avg = times.reduce((a, b) => a.add(b), Fraction(0)).div(times.length) for (let timepat of timepats) { if (timepat[0] === undefined) { - timepat[0] = avg; + timepat[0] = avg } } } if (timepats.length == 1) { - const result = reify(timepats[0][1]); - return result.withSteps((_) => timepats[0][0]); + const result = reify(timepats[0][1]) + return result.withSteps((_) => timepats[0][0]) } - const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0)); - let begin = Fraction(0); - const pats = []; + const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0)) + let begin = Fraction(0) + const pats = [] for (const [time, pat] of timepats) { if (Fraction(time).eq(0)) { - continue; + continue } - const end = begin.add(time); - pats.push(reify(pat)._compress(begin.div(total), end.div(total))); - begin = end; + const end = begin.add(time) + pats.push(reify(pat)._compress(begin.div(total), end.div(total))) + begin = end } - const result = stack(...pats); - result._steps = total; - return result; + const result = stack(...pats) + result._steps = total + return result } /** @@ -2896,19 +2947,19 @@ export function stepcat(...timepats) { * // The same as "bd cp bd mt bd".sound() */ export function stepalt(...groups) { - groups = groups.map((a) => (Array.isArray(a) ? a.map(reify) : [reify(a)])); + groups = groups.map((a) => (Array.isArray(a) ? a.map(reify) : [reify(a)])) - const cycles = lcm(...groups.map((x) => Fraction(x.length))); + const cycles = lcm(...groups.map((x) => Fraction(x.length))) - let result = []; + let result = [] for (let cycle = 0; cycle < cycles; ++cycle) { - result.push(...groups.map((x) => (x.length == 0 ? silence : x[cycle % x.length]))); + result.push(...groups.map((x) => (x.length == 0 ? silence : x[cycle % x.length]))) } - result = result.filter((x) => x.hasSteps && x._steps > 0); - const steps = result.reduce((a, b) => a.add(b._steps), Fraction(0)); - result = stepcat(...result); - result._steps = steps; - return result; + result = result.filter((x) => x.hasSteps && x._steps > 0) + const steps = result.reduce((a, b) => a.add(b._steps), Fraction(0)) + result = stepcat(...result) + result._steps = steps + return result } /** @@ -2929,31 +2980,31 @@ export function stepalt(...groups) { */ export const take = stepRegister('take', function (i, pat) { if (!pat.hasSteps) { - return nothing; + return nothing } if (pat._steps.lte(0)) { - return nothing; + return nothing } - i = Fraction(i); + i = Fraction(i) if (i.eq(0)) { - return nothing; + return nothing } - const flip = i < 0; + const flip = i < 0 if (flip) { - i = i.abs(); + i = i.abs() } - const frac = i.div(pat._steps); + const frac = i.div(pat._steps) if (frac.lte(0)) { - return nothing; + return nothing } if (frac.gte(1)) { - return pat; + return pat } if (flip) { - return pat.zoom(Fraction(1).sub(frac), 1); + return pat.zoom(Fraction(1).sub(frac), 1) } - return pat.zoom(0, frac); -}); + return pat.zoom(0, frac) +}) /** * *Experimental* @@ -2972,15 +3023,15 @@ export const take = stepRegister('take', function (i, pat) { */ export const drop = stepRegister('drop', function (i, pat) { if (!pat.hasSteps) { - return nothing; + return nothing } - i = Fraction(i); + i = Fraction(i) if (i.lt(0)) { - return pat.take(pat._steps.add(i)); + return pat.take(pat._steps.add(i)) } - return pat.take(Fraction(0).sub(pat._steps.sub(i))); -}); + return pat.take(Fraction(0).sub(pat._steps.sub(i))) +}) /** * *Experimental* @@ -2995,8 +3046,8 @@ export const drop = stepRegister('drop', function (i, pat) { * ).pace(8) */ export const extend = stepRegister('extend', function (factor, pat) { - return pat.fast(factor).expand(factor); -}); + return pat.fast(factor).expand(factor) +}) /** * *Experimental* @@ -3013,8 +3064,8 @@ export const extend = stepRegister('extend', function (factor, pat) { * ).pace(8) */ export const replicate = stepRegister('replicate', function (factor, pat) { - return pat.repeatCycles(factor).fast(factor).expand(factor); -}); + return pat.repeatCycles(factor).fast(factor).expand(factor) +}) /** * *Experimental* @@ -3024,8 +3075,8 @@ export const replicate = stepRegister('replicate', function (factor, pat) { * sound("tha dhi thom nam").bank("mridangam").expand("3 2 1 1 2 3").pace(8) */ export const expand = stepRegister('expand', function (factor, pat) { - return pat.withSteps((t) => t.mul(Fraction(factor))); -}); + return pat.withSteps((t) => t.mul(Fraction(factor))) +}) /** * *Experimental* @@ -3035,49 +3086,49 @@ export const expand = stepRegister('expand', function (factor, pat) { * sound("tha dhi thom nam").bank("mridangam").contract("3 2 1 1 2 3").pace(8) */ export const contract = stepRegister('contract', function (factor, pat) { - return pat.withSteps((t) => t.div(Fraction(factor))); -}); + return pat.withSteps((t) => t.div(Fraction(factor))) +}) Pattern.prototype.shrinklist = function (amount) { - const pat = this; + const pat = this if (!pat.hasSteps) { - return [pat]; + return [pat] } - let [amountv, times] = Array.isArray(amount) ? amount : [amount, pat._steps]; - amountv = Fraction(amountv); + let [amountv, times] = Array.isArray(amount) ? amount : [amount, pat._steps] + amountv = Fraction(amountv) if (times === 0 || amountv === 0) { - return [pat]; + return [pat] } - const fromstart = amountv > 0; - const ranges = []; + const fromstart = amountv > 0 + const ranges = [] if (fromstart) { - const seg = Fraction(1).div(pat._steps).mul(amountv); + const seg = Fraction(1).div(pat._steps).mul(amountv) for (let i = 0; i < times; ++i) { - const s = seg.mul(i); + const s = seg.mul(i) if (s.gt(1)) { - break; + break } - ranges.push([s, 1]); + ranges.push([s, 1]) } } else { - amountv = Fraction(0).sub(amountv); - const seg = Fraction(1).div(pat._steps).mul(amountv); + amountv = Fraction(0).sub(amountv) + const seg = Fraction(1).div(pat._steps).mul(amountv) for (let i = 0; i < times; ++i) { - const e = Fraction(1).sub(seg.mul(i)); + const e = Fraction(1).sub(seg.mul(i)) if (e.lt(0)) { - break; + break } - ranges.push([Fraction(0), e]); + ranges.push([Fraction(0), e]) } } - return ranges.map((x) => pat.zoom(...x)); -}; + return ranges.map((x) => pat.zoom(...x)) +} -export const shrinklist = (amount, pat) => pat.shrinklist(amount); +export const shrinklist = (amount, pat) => pat.shrinklist(amount) /** * *Experimental* @@ -3104,19 +3155,19 @@ export const shrink = register( 'shrink', function (amount, pat) { if (!pat.hasSteps) { - return nothing; + return nothing } - const list = pat.shrinklist(amount); - const result = stepcat(...list); + const list = pat.shrinklist(amount) + const result = stepcat(...list) // TODO is this calculation needed? - result._steps = list.reduce((a, b) => a.add(b._steps), Fraction(0)); - return result; + result._steps = list.reduce((a, b) => a.add(b._steps), Fraction(0)) + return result }, true, false, - (x) => x.stepJoin(), -); + (x) => x.stepJoin() +) /** * *Experimental* @@ -3142,20 +3193,20 @@ export const grow = register( 'grow', function (amount, pat) { if (!pat.hasSteps) { - return nothing; + return nothing } - const list = pat.shrinklist(Fraction(0).sub(amount)); - list.reverse(); - const result = stepcat(...list); + const list = pat.shrinklist(Fraction(0).sub(amount)) + list.reverse() + const result = stepcat(...list) // TODO is this calculation needed? - result._steps = list.reduce((a, b) => a.add(b._steps), Fraction(0)); - return result; + result._steps = list.reduce((a, b) => a.add(b._steps), Fraction(0)) + return result }, true, false, - (x) => x.stepJoin(), -); + (x) => x.stepJoin() +) /** * *Experimental* @@ -3171,18 +3222,22 @@ export const grow = register( .pace(8) */ export const tour = function (pat, ...many) { - return pat.tour(...many); -}; + return pat.tour(...many) +} Pattern.prototype.tour = function (...many) { return stepcat( ...[].concat( - ...many.map((x, i) => [...many.slice(0, many.length - i), this, ...many.slice(many.length - i)]), + ...many.map((x, i) => [ + ...many.slice(0, many.length - i), + this, + ...many.slice(many.length - i), + ]), this, - ...many, - ), - ); -}; + ...many + ) + ) +} /** * *Experimental* @@ -3197,41 +3252,41 @@ Pattern.prototype.tour = function (...many) { .pace(8) */ export const zip = function (...pats) { - pats = pats.filter((pat) => pat.hasSteps); - const zipped = slowcat(...pats.map((pat) => pat._slow(pat._steps))); - const steps = lcm(...pats.map((x) => x._steps)); - return zipped._fast(steps).setSteps(steps); -}; + pats = pats.filter((pat) => pat.hasSteps) + const zipped = slowcat(...pats.map((pat) => pat._slow(pat._steps))) + const steps = lcm(...pats.map((x) => x._steps)) + return zipped._fast(steps).setSteps(steps) +} /** Aliases for `stepcat` */ -export const timecat = stepcat; -export const timeCat = stepcat; +export const timecat = stepcat +export const timeCat = stepcat // Deprecated stepwise aliases -export const s_cat = stepcat; -export const s_alt = stepalt; -export const s_polymeter = polymeter; -Pattern.prototype.s_polymeter = Pattern.prototype.polymeter; -export const s_taper = shrink; -Pattern.prototype.s_taper = Pattern.prototype.shrink; -export const s_taperlist = shrinklist; -Pattern.prototype.s_taperlist = Pattern.prototype.shrinklist; -export const s_add = take; -Pattern.prototype.s_add = Pattern.prototype.take; -export const s_sub = drop; -Pattern.prototype.s_sub = Pattern.prototype.drop; -export const s_expand = expand; -Pattern.prototype.s_expand = Pattern.prototype.expand; -export const s_extend = extend; -Pattern.prototype.s_extend = Pattern.prototype.extend; -export const s_contract = contract; -Pattern.prototype.s_contract = Pattern.prototype.contract; -export const s_tour = tour; -Pattern.prototype.s_tour = Pattern.prototype.tour; -export const s_zip = zip; -Pattern.prototype.s_zip = Pattern.prototype.zip; -export const steps = pace; -Pattern.prototype.steps = Pattern.prototype.pace; +export const s_cat = stepcat +export const s_alt = stepalt +export const s_polymeter = polymeter +Pattern.prototype.s_polymeter = Pattern.prototype.polymeter +export const s_taper = shrink +Pattern.prototype.s_taper = Pattern.prototype.shrink +export const s_taperlist = shrinklist +Pattern.prototype.s_taperlist = Pattern.prototype.shrinklist +export const s_add = take +Pattern.prototype.s_add = Pattern.prototype.take +export const s_sub = drop +Pattern.prototype.s_sub = Pattern.prototype.drop +export const s_expand = expand +Pattern.prototype.s_expand = Pattern.prototype.expand +export const s_extend = extend +Pattern.prototype.s_extend = Pattern.prototype.extend +export const s_contract = contract +Pattern.prototype.s_contract = Pattern.prototype.contract +export const s_tour = tour +Pattern.prototype.s_tour = Pattern.prototype.tour +export const s_zip = zip +Pattern.prototype.s_zip = Pattern.prototype.zip +export const steps = pace +Pattern.prototype.steps = Pattern.prototype.pace ////////////////////////////////////////////////////////////////////// // Control-related functions, i.e. ones that manipulate patterns of @@ -3252,21 +3307,21 @@ Pattern.prototype.steps = Pattern.prototype.pace; * */ export const chop = register('chop', function (n, pat) { - const slices = Array.from({ length: n }, (x, i) => i); - const slice_objects = slices.map((i) => ({ begin: i / n, end: (i + 1) / n })); + const slices = Array.from({ length: n }, (x, i) => i) + const slice_objects = slices.map((i) => ({ begin: i / n, end: (i + 1) / n })) const merge = function (a, b) { if ('begin' in a && 'end' in a && a.begin !== undefined && a.end !== undefined) { - const d = a.end - a.begin; - b = { begin: a.begin + b.begin * d, end: a.begin + b.end * d }; + const d = a.end - a.begin + b = { begin: a.begin + b.begin * d, end: a.begin + b.end * d } } // return a; - return Object.assign({}, a, b); - }; + return Object.assign({}, a, b) + } const func = function (o) { - return sequence(slice_objects.map((slice_o) => merge(o, slice_o))); - }; - return pat.squeezeBind(func).setSteps(__steps ? Fraction(n).mulmaybe(pat._steps) : undefined); -}); + return sequence(slice_objects.map((slice_o) => merge(o, slice_o))) + } + return pat.squeezeBind(func).setSteps(__steps ? Fraction(n).mulmaybe(pat._steps) : undefined) +}) /** * Cuts each sample into the given number of parts, triggering progressive portions of each sample at each loop. @@ -3277,14 +3332,14 @@ export const chop = register('chop', function (n, pat) { * s("numbers:0 numbers:1 numbers:2").striate(6).slow(3) */ export const striate = register('striate', function (n, pat) { - const slices = Array.from({ length: n }, (x, i) => i); - const slice_objects = slices.map((i) => ({ begin: i / n, end: (i + 1) / n })); - const slicePat = slowcat(...slice_objects); + const slices = Array.from({ length: n }, (x, i) => i) + const slice_objects = slices.map((i) => ({ begin: i / n, end: (i + 1) / n })) + const slicePat = slowcat(...slice_objects) return pat .set(slicePat) ._fast(n) - .setSteps(__steps ? Fraction(n).mulmaybe(pat._steps) : undefined); -}); + .setSteps(__steps ? Fraction(n).mulmaybe(pat._steps) : undefined) +}) /** * Makes the sample fit the given number of cycles by changing the speed. @@ -3300,8 +3355,8 @@ const _loopAt = function (factor, pat, cps = 0.5) { return pat .speed((1 / factor) * cps) .unit('c') - .slow(factor); -}; + .slow(factor) +} /** * Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers. @@ -3325,17 +3380,17 @@ export const slice = register( ipat.outerBind((i) => opat.outerBind((o) => { // If it's not an object, assume it's a string and make it a 's' control parameter - o = o instanceof Object ? o : { s: o }; - const begin = Array.isArray(n) ? n[i] : i / n; - const end = Array.isArray(n) ? n[i + 1] : (i + 1) / n; - return pure({ begin, end, _slices: n, ...o }); - }), - ), + o = o instanceof Object ? o : { s: o } + const begin = Array.isArray(n) ? n[i] : i / n + const end = Array.isArray(n) ? n[i + 1] : (i + 1) / n + return pure({ begin, end, _slices: n, ...o }) + }) + ) ) - .setSteps(ipat._steps); + .setSteps(ipat._steps) }, - false, // turns off auto-patternification -); + false // turns off auto-patternification +) /** * @@ -3349,12 +3404,12 @@ export const slice = register( */ Pattern.prototype.onTriggerTime = function (func) { return this.onTrigger((hap, currentTime, _cps, targetTime) => { - const diff = targetTime - currentTime; + const diff = targetTime - currentTime window.setTimeout(() => { - func(hap); - }, diff * 1000); - }, false); -}; + func(hap) + }, diff * 1000) + }, false) +} /** * Works the same as slice, but changes the playback speed of each slice to match the duration of its step. @@ -3368,11 +3423,11 @@ Pattern.prototype.onTriggerTime = function (func) { export const splice = register( 'splice', function (npat, ipat, opat) { - const sliced = slice(npat, ipat, opat); + const sliced = slice(npat, ipat, opat) return new Pattern((state) => { // TODO - default cps to 0.5 - const cps = state.controls._cps || 1; - const haps = sliced.query(state); + const cps = state.controls._cps || 1 + const haps = sliced.query(state) return haps.map((hap) => hap.withValue((v) => ({ ...{ @@ -3380,17 +3435,17 @@ export const splice = register( unit: 'c', }, ...v, - })), - ); - }).setSteps(ipat._steps); + })) + ) + }).setSteps(ipat._steps) }, - false, // turns off auto-patternification -); + false // turns off auto-patternification +) export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) { - const steps = pat._steps ? pat._steps.div(factor) : undefined; - return new Pattern((state) => _loopAt(factor, pat, state.controls._cps).query(state), steps); -}); + const steps = pat._steps ? pat._steps.div(factor) : undefined + return new Pattern((state) => _loopAt(factor, pat, state.controls._cps).query(state), steps) +}) /** * Makes the sample fit its event duration. Good for rhythmical loops like drum breaks. @@ -3404,16 +3459,16 @@ export const fit = register('fit', (pat) => pat.withHaps((haps, state) => haps.map((hap) => hap.withValue((v) => { - const slicedur = ('end' in v ? v.end : 1) - ('begin' in v ? v.begin : 0); + const slicedur = ('end' in v ? v.end : 1) - ('begin' in v ? v.begin : 0) return { ...v, speed: ((state.controls._cps || 1) / hap.whole.duration) * slicedur, unit: 'c', - }; - }), - ), - ), -); + } + }) + ) + ) +) /** * Makes the sample fit the given number of cycles and cps value, by @@ -3428,17 +3483,20 @@ export const fit = register('fit', (pat) => * s("rhodes").loopAtCps(4,1.5).cps(1.5) */ // TODO - global cps clock -export const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], function (factor, cps, pat) { - return _loopAt(factor, pat, cps); -}); +export const { loopAtCps, loopatcps } = register( + ['loopAtCps', 'loopatcps'], + function (factor, cps, pat) { + return _loopAt(factor, pat, cps) + } +) /** exposes a custom value at query time. basically allows mutating state without evaluation */ export const ref = (accessor) => pure(1) .withValue(() => reify(accessor())) - .innerJoin(); + .innerJoin() -let fadeGain = (p) => (p < 0.5 ? 1 : 1 - (p - 0.5) / 0.5); +let fadeGain = (p) => (p < 0.5 ? 1 : 1 - (p - 0.5) / 0.5) /** * Cross-fades between left and right from 0 to 1: @@ -3451,18 +3509,18 @@ let fadeGain = (p) => (p < 0.5 ? 1 : 1 - (p - 0.5) / 0.5); * xfade(s("bd*2"), "<0 .25 .5 .75 1>", s("hh*8")) */ export let xfade = (a, pos, b) => { - pos = reify(pos); - a = reify(a); - b = reify(b); - let gaina = pos.fmap((v) => ({ gain: fadeGain(v) })); - let gainb = pos.fmap((v) => ({ gain: fadeGain(1 - v) })); - return stack(a.mul(gaina), b.mul(gainb)); -}; + pos = reify(pos) + a = reify(a) + b = reify(b) + let gaina = pos.fmap((v) => ({ gain: fadeGain(v) })) + let gainb = pos.fmap((v) => ({ gain: fadeGain(1 - v) })) + return stack(a.mul(gaina), b.mul(gainb)) +} // the prototype version is actually flipped so left/right makes sense Pattern.prototype.xfade = function (pos, b) { - return xfade(this, pos, b); -}; + return xfade(this, pos, b) +} /** * creates a structure pattern from divisions of a cycle @@ -3474,59 +3532,59 @@ Pattern.prototype.xfade = function (pos, b) { * s("sd").beat("4,12", 16) */ const __beat = (join) => (t, div, pat) => { - t = Fraction(t).mod(div); - div = Fraction(div); - const b = t.div(div); - const e = t.add(1).div(div); - return join(pat.fmap((x) => pure(x)._compress(b, e))); -}; + t = Fraction(t).mod(div) + div = Fraction(div) + const b = t.div(div) + const e = t.add(1).div(div) + return join(pat.fmap((x) => pure(x)._compress(b, e))) +} export const { beat } = register( ['beat'], - __beat((x) => x.innerJoin()), -); + __beat((x) => x.innerJoin()) +) export const _morph = (from, to, by) => { - by = Fraction(by); - const dur = Fraction(1).div(from.length); + by = Fraction(by) + const dur = Fraction(1).div(from.length) const positions = (list) => { - const result = []; + const result = [] for (const [pos, value] of list.entries()) { if (value) { - result.push([Fraction(pos).div(list.length), value]); + result.push([Fraction(pos).div(list.length), value]) } } - return result; - }; + return result + } const arcs = zipWith( ([posa, valuea], [posb, valueb]) => { - const b = by.mul(posb - posa).add(posa); - const e = b.add(dur); - return new TimeSpan(b, e); + const b = by.mul(posb - posa).add(posa) + const e = b.add(dur) + return new TimeSpan(b, e) }, positions(from), - positions(to), - ); + positions(to) + ) function query(state) { - const cycle = state.span.begin.sam(); - const cycleArc = state.span.cycleArc(); - const result = []; + const cycle = state.span.begin.sam() + const cycleArc = state.span.cycleArc() + const result = [] for (const whole of arcs) { - const part = whole.intersection(cycleArc); + const part = whole.intersection(cycleArc) if (part !== undefined) { result.push( new Hap( whole.withTime((x) => x.add(cycle)), part.withTime((x) => x.add(cycle)), - true, - ), - ); + true + ) + ) } } - return result; + return result } - return new Pattern(query).splitQueries(); -}; + return new Pattern(query).splitQueries() +} /** * Takes two binary rhythms represented as lists of 1s and 0s, and a number @@ -3546,11 +3604,13 @@ export const _morph = (from, to, by) => { * ) */ export const morph = (frompat, topat, bypat) => { - frompat = reify(frompat); - topat = reify(topat); - bypat = reify(bypat); - return frompat.innerBind((from) => topat.innerBind((to) => bypat.innerBind((by) => _morph(from, to, by)))); -}; + frompat = reify(frompat) + topat = reify(topat) + bypat = reify(bypat) + return frompat.innerBind((from) => + topat.innerBind((to) => bypat.innerBind((by) => _morph(from, to, by))) + ) +} /** * Soft-clipping distortion @@ -3616,11 +3676,21 @@ export const morph = (frompat, topat, bypat) => { * @param {number | Pattern} volume linear postgain of the distortion * */ -const distAlgoNames = ['scurve', 'soft', 'hard', 'cubic', 'diode', 'asym', 'fold', 'sinefold', 'chebyshev']; +const distAlgoNames = [ + 'scurve', + 'soft', + 'hard', + 'cubic', + 'diode', + 'asym', + 'fold', + 'sinefold', + 'chebyshev', +] for (const name of distAlgoNames) { // Add aliases for distortion algorithms Pattern.prototype[name] = function (args) { - const argsPat = reify(args).fmap((v) => (Array.isArray(v) ? [...v, name] : [v, 1, name])); - return this.distort(argsPat); - }; + const argsPat = reify(args).fmap((v) => (Array.isArray(v) ? [...v, name] : [v, 1, name])) + return this.distort(argsPat) + } } diff --git a/src/strudel/core/pick.mjs b/src/strudel/core/pick.mjs index 206fa61..b75b714 100644 --- a/src/strudel/core/pick.mjs +++ b/src/strudel/core/pick.mjs @@ -4,27 +4,27 @@ Copyright (C) 2024 Strudel contributors - see . */ -import { Pattern, reify, silence, register } from './pattern.mjs'; +import { Pattern, reify, silence, register } from './pattern.mjs' -import { _mod, clamp, objectMap } from './util.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; + const array = Array.isArray(lookup) + const len = Object.keys(lookup).length - lookup = objectMap(lookup, reify); + lookup = objectMap(lookup, reify) if (len === 0) { - return silence; + return silence } return pat.fmap((i) => { - let key = i; + let key = i if (array) { - key = modulo ? Math.round(key) % len : clamp(Math.round(key), 0, lookup.length - 1); + key = modulo ? Math.round(key) % len : clamp(Math.round(key), 0, lookup.length - 1) } - return lookup[key]; - }); -}; + return lookup[key] + }) +} /** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). * Similar to `inhabit`, but maintains the structure of the original patterns. @@ -44,14 +44,14 @@ const _pick = function (lookup, pat, modulo = true) { export const pick = function (lookup, pat) { // backward compatibility - the args used to be flipped if (Array.isArray(pat)) { - [pat, lookup] = [lookup, pat]; + ;[pat, lookup] = [lookup, pat] } - return __pick(lookup, pat); -}; + return __pick(lookup, pat) +} const __pick = register('pick', function (lookup, pat) { - return _pick(lookup, pat, false).innerJoin(); -}); + 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. @@ -63,8 +63,8 @@ const __pick = register('pick', function (lookup, pat) { */ export const pickmod = register('pickmod', function (lookup, pat) { - return _pick(lookup, pat, true).innerJoin(); -}); + 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 @@ -78,8 +78,8 @@ export const pickmod = register('pickmod', function (lookup, pat) { * .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)); -}); + 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. @@ -89,8 +89,8 @@ export const pickF = register('pickF', function (lookup, funcs, pat) { * @returns {Pattern} */ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) { - return pat.apply(pickmod(lookup, funcs)); -}); + return pat.apply(pickmod(lookup, funcs)) +}) /** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin. * @param {Pattern} pat @@ -98,8 +98,8 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) { * @returns {Pattern} */ export const pickOut = register('pickOut', function (lookup, pat) { - return _pick(lookup, pat, false).outerJoin(); -}); + 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. @@ -108,8 +108,8 @@ export const pickOut = register('pickOut', function (lookup, pat) { * @returns {Pattern} */ export const pickmodOut = register('pickmodOut', function (lookup, pat) { - return _pick(lookup, pat, true).outerJoin(); -}); + return _pick(lookup, pat, true).outerJoin() +}) /** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered. * @param {Pattern} pat @@ -117,8 +117,8 @@ export const pickmodOut = register('pickmodOut', function (lookup, pat) { * @returns {Pattern} */ export const pickRestart = register('pickRestart', function (lookup, pat) { - return _pick(lookup, pat, false).restartJoin(); -}); + 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. @@ -134,8 +134,8 @@ export const pickRestart = register('pickRestart', function (lookup, pat) { }).scale("C:major").s("piano") */ export const pickmodRestart = register('pickmodRestart', function (lookup, pat) { - return _pick(lookup, pat, true).restartJoin(); -}); + return _pick(lookup, pat, true).restartJoin() +}) /** * Similar to `pick`, but the choosen pattern is reset when its index is triggered. * @param {Pattern} pat @@ -143,8 +143,8 @@ export const pickmodRestart = register('pickmodRestart', function (lookup, pat) * @returns {Pattern} */ export const pickReset = register('pickReset', function (lookup, pat) { - return _pick(lookup, pat, false).resetJoin(); -}); + 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. @@ -153,8 +153,8 @@ export const pickReset = register('pickReset', function (lookup, pat) { * @returns {Pattern} */ export const pickmodReset = register('pickmodReset', function (lookup, pat) { - return _pick(lookup, pat, true).resetJoin(); -}); + 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. @@ -170,9 +170,12 @@ export const pickmodReset = register('pickmodReset', function (lookup, pat) { * @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(); -}); +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. @@ -185,9 +188,12 @@ export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], fun * @returns {Pattern} */ -export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSqueeze'], function (lookup, pat) { - return _pick(lookup, pat, true).squeezeJoin(); -}); +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 @@ -200,14 +206,14 @@ export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSq */ export const squeeze = (pat, xs) => { - xs = xs.map(reify); + xs = xs.map(reify) if (xs.length == 0) { - return silence; + return silence } return pat .fmap((i) => { - const key = _mod(Math.round(i), xs.length); - return xs[key]; + const key = _mod(Math.round(i), xs.length) + return xs[key] }) - .squeezeJoin(); -}; + .squeezeJoin() +} diff --git a/src/strudel/core/repl.mjs b/src/strudel/core/repl.mjs index 171697e..66e770f 100644 --- a/src/strudel/core/repl.mjs +++ b/src/strudel/core/repl.mjs @@ -1,10 +1,10 @@ -import { NeoCyclist } from './neocyclist.mjs'; -import { Cyclist } from './cyclist.mjs'; -import { evaluate as _evaluate } from './evaluate.mjs'; -import { errorLogger, logger } from './logger.mjs'; -import { setTime } from './time.mjs'; -import { evalScope } from './evaluate.mjs'; -import { register, Pattern, isPattern, silence, stack } from './pattern.mjs'; +import { NeoCyclist } from './neocyclist.mjs' +import { Cyclist } from './cyclist.mjs' +import { evaluate as _evaluate } from './evaluate.mjs' +import { errorLogger, logger } from './logger.mjs' +import { setTime } from './time.mjs' +import { evalScope } from './evaluate.mjs' +import { register, Pattern, isPattern, silence, stack } from './pattern.mjs' export function repl({ defaultOutput, @@ -33,70 +33,72 @@ export function repl({ widgets: [], pending: false, started: false, - }; + } const transpilerOptions = { id, - }; + } const updateState = (update) => { - Object.assign(state, update); - state.isDirty = state.code !== state.activeCode; - state.error = state.evalError || state.schedulerError; - onUpdateState?.(state); - }; + Object.assign(state, update) + state.isDirty = state.code !== state.activeCode + state.error = state.evalError || state.schedulerError + onUpdateState?.(state) + } const schedulerOptions = { onTrigger: getTrigger({ defaultOutput, getTime }), getTime, onToggle: (started) => { - updateState({ started }); - onToggle?.(started); + updateState({ started }) + onToggle?.(started) }, setInterval, clearInterval, beforeStart, - }; + } // NeoCyclist uses a shared worker to communicate between instances, which is not supported on mobile chrome const scheduler = - sync && typeof SharedWorker != 'undefined' ? new NeoCyclist(schedulerOptions) : new Cyclist(schedulerOptions); - let pPatterns = {}; - let anonymousIndex = 0; - let allTransform; - let eachTransform; + sync && typeof SharedWorker != 'undefined' + ? new NeoCyclist(schedulerOptions) + : new Cyclist(schedulerOptions) + let pPatterns = {} + let anonymousIndex = 0 + let allTransform + let eachTransform const hush = function () { - pPatterns = {}; - anonymousIndex = 0; - allTransform = undefined; - eachTransform = undefined; - return silence; - }; + pPatterns = {} + anonymousIndex = 0 + allTransform = undefined + eachTransform = undefined + return silence + } // helper to get a patternified pure value out function unpure(pat) { if (pat._Pattern) { - return pat.__pure; + return pat.__pure } - return pat; + return pat } const setPattern = async (pattern, autostart = true) => { - pattern = editPattern?.(pattern) || pattern; - await scheduler.setPattern(pattern, autostart); - return pattern; - }; - setTime(() => scheduler.now()); // TODO: refactor? + pattern = editPattern?.(pattern) || pattern + await scheduler.setPattern(pattern, autostart) + return pattern + } + setTime(() => scheduler.now()) // TODO: refactor? - const stop = () => scheduler.stop(); - const start = () => scheduler.start(); - const pause = () => scheduler.pause(); - const toggle = () => scheduler.toggle(); + const stop = () => scheduler.stop() + const start = () => scheduler.start() + const pause = () => scheduler.pause() + const toggle = () => scheduler.toggle() const setCps = (cps) => { - scheduler.setCps(unpure(cps)); - return silence; - }; + scheduler.setCps(unpure(cps)) + return silence + } /** * Changes the global tempo to the given cycles per minute @@ -109,9 +111,9 @@ export function repl({ * $: s("bd*4,[- sd]*2").bank('tr707') */ const setCpm = (cpm) => { - scheduler.setCps(unpure(cpm) / 60); - return silence; - }; + scheduler.setCps(unpure(cpm) / 60) + return silence + } // TODO - not documented as jsdoc examples as the test framework doesn't simulate enough context for `each` and `all`.. @@ -128,11 +130,11 @@ export function repl({ * all(x => x.pianoroll()) * ``` */ - let allTransforms = []; + let allTransforms = [] const all = function (transform) { - allTransforms.push(transform); - return silence; - }; + allTransforms.push(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") @@ -141,50 +143,50 @@ export function repl({ * ``` */ const each = function (transform) { - eachTransform = transform; - return silence; - }; + eachTransform = transform + return silence + } // set pattern methods that use this repl via closure const injectPatternMethods = () => { Pattern.prototype.p = function (id) { if (typeof id === 'string' && (id.startsWith('_') || id.endsWith('_'))) { // allows muting a pattern x with x_ or _x - return silence; + return silence } if (id === '$') { // allows adding anonymous patterns with $: - id = `$${anonymousIndex}`; - anonymousIndex++; + id = `$${anonymousIndex}` + anonymousIndex++ } - pPatterns[id] = this; - return this; - }; + pPatterns[id] = this + return this + } Pattern.prototype.q = function (id) { - return silence; - }; + return silence + } try { for (let i = 1; i < 10; ++i) { Object.defineProperty(Pattern.prototype, `d${i}`, { get() { - return this.p(i); + return this.p(i) }, configurable: true, - }); + }) Object.defineProperty(Pattern.prototype, `p${i}`, { get() { - return this.p(i); + return this.p(i) }, configurable: true, - }); - Pattern.prototype[`q${i}`] = silence; + }) + Pattern.prototype[`q${i}`] = silence } } catch (err) { - console.warn('injectPatternMethods: error:', err); + console.warn('injectPatternMethods: error:', err) } const cpm = register('cpm', function (cpm, pat) { - return pat._fast(cpm / 60 / scheduler.cps); - }); + return pat._fast(cpm / 60 / scheduler.cps) + }) return evalScope({ all, each, @@ -194,50 +196,52 @@ export function repl({ setcps: setCps, setCpm, setcpm: setCpm, - }); - }; + }) + } const evaluate = async (code, autostart = true, shouldHush = true) => { if (!code) { - throw new Error('no code to evaluate'); + throw new Error('no code to evaluate') } try { - updateState({ code, pending: true }); - await injectPatternMethods(); - setTime(() => scheduler.now()); // TODO: refactor? - await beforeEval?.({ code }); - allTransforms = []; // reset all transforms - shouldHush && hush(); + updateState({ code, pending: true }) + await injectPatternMethods() + setTime(() => scheduler.now()) // TODO: refactor? + await beforeEval?.({ code }) + allTransforms = [] // reset all transforms + shouldHush && hush() if (mondo) { - code = `mondolang\`${code}\``; + code = `mondolang\`${code}\`` } - let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions); + let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions) if (Object.keys(pPatterns).length) { - let patterns = []; + let patterns = [] for (const [key, value] of Object.entries(pPatterns)) { - patterns.push(value.withState((state) => state.setControls({ id: key }))); + patterns.push(value.withState((state) => state.setControls({ id: key }))) } if (eachTransform) { // Explicit lambda so only element (not index and array) are passed - patterns = patterns.map((x) => eachTransform(x)); + patterns = patterns.map((x) => eachTransform(x)) } - pattern = stack(...patterns); + pattern = stack(...patterns) } else if (eachTransform) { - pattern = eachTransform(pattern); + pattern = eachTransform(pattern) } if (allTransforms.length) { for (let i in allTransforms) { - pattern = allTransforms[i](pattern); + pattern = allTransforms[i](pattern) } } if (!isPattern(pattern)) { - const message = `got "${typeof evaluated}" instead of pattern`; - throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.')); + const message = `got "${typeof evaluated}" instead of pattern` + throw new Error( + message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.') + ) } - logger(`[eval] code updated`); - pattern = await setPattern(pattern, autostart); + logger(`[eval] code updated`) + pattern = await setPattern(pattern, autostart) updateState({ miniLocations: meta?.miniLocations || [], widgets: meta?.widgets || [], @@ -246,18 +250,18 @@ export function repl({ evalError: undefined, schedulerError: undefined, pending: false, - }); - afterEval?.({ code, pattern, meta }); - return pattern; + }) + afterEval?.({ code, pattern, meta }) + return pattern } catch (err) { - logger(`[eval] error: ${err.message}`, 'error'); - console.error(err); - updateState({ evalError: err, pending: false }); - onEvalError?.(err); + logger(`[eval] error: ${err.message}`, 'error') + console.error(err) + updateState({ evalError: err, pending: false }) + onEvalError?.(err) } - }; - const setCode = (code) => updateState({ code }); - return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle, state }; + } + const setCode = (code) => updateState({ code }) + return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle, state } } export const getTrigger = @@ -267,13 +271,13 @@ export const getTrigger = // TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004 try { if (!hap.context.onTrigger || !hap.context.dominantTrigger) { - await defaultOutput(hap, deadline, duration, cps, t); + await defaultOutput(hap, deadline, duration, cps, t) } if (hap.context.onTrigger) { // call signature of output / onTrigger is different... - await hap.context.onTrigger(hap, getTime(), cps, t); + await hap.context.onTrigger(hap, getTime(), cps, t) } } catch (err) { - errorLogger(err, 'getTrigger'); + errorLogger(err, 'getTrigger') } - }; + } diff --git a/src/strudel/core/signal.mjs b/src/strudel/core/signal.mjs index 6bd6fde..f7e0443 100644 --- a/src/strudel/core/signal.mjs +++ b/src/strudel/core/signal.mjs @@ -4,21 +4,21 @@ Copyright (C) 2024 Strudel contributors - see . */ -import { Hap } from './hap.mjs'; -import { Pattern, fastcat, pure, register, reify, silence, stack, sequenceP } from './pattern.mjs'; -import Fraction from './fraction.mjs'; +import { Hap } from './hap.mjs' +import { Pattern, fastcat, pure, register, reify, silence, stack, sequenceP } from './pattern.mjs' +import Fraction from './fraction.mjs' -import { id, keyAlias, getCurrentKeyboardState } from './util.mjs'; +import { id, keyAlias, getCurrentKeyboardState } from './util.mjs' export function steady(value) { // A continuous value - return new Pattern((state) => [new Hap(undefined, state.span, value)]); + return new Pattern((state) => [new Hap(undefined, state.span, value)]) } export const signal = (func) => { - const query = (state) => [new Hap(undefined, state.span, func(state.span.begin))]; - return new Pattern(query); -}; + const query = (state) => [new Hap(undefined, state.span, func(state.span.begin))] + return new Pattern(query) +} /** * A sawtooth signal between 0 and 1. @@ -32,14 +32,14 @@ export const signal = (func) => { * .scale('C major') * */ -export const saw = signal((t) => t % 1); +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(); +export const saw2 = saw.toBipolar() /** * A sawtooth signal between 1 and 0 (like `saw`, but flipped). @@ -53,21 +53,21 @@ export const saw2 = saw.toBipolar(); * .scale('C major') * */ -export const isaw = signal((t) => 1 - (t % 1)); +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(); +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)); +export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t)) /** * A sine signal between 0 and 1. @@ -77,7 +77,7 @@ export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t)); * .scale("C:minor") * */ -export const sine = sine2.fromBipolar(); +export const sine = sine2.fromBipolar() /** * A cosine signal between 0 and 1. @@ -88,14 +88,14 @@ export const sine = sine2.fromBipolar(); * .scale("C:minor") * */ -export const cosine = sine._early(Fraction(1).div(4)); +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)); +export const cosine2 = sine2._early(Fraction(1).div(4)) /** * A square signal between 0 and 1. @@ -104,14 +104,14 @@ export const cosine2 = sine2._early(Fraction(1).div(4)); * n(square.segment(4).range(0,7)).scale("C:minor") * */ -export const square = signal((t) => Math.floor((t * 2) % 2)); +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(); +export const square2 = square.toBipolar() /** * A triangle signal between 0 and 1. @@ -121,14 +121,14 @@ export const square2 = square.toBipolar(); * n(tri.segment(8).range(0,7)).scale("C:minor") * */ -export const tri = fastcat(saw, isaw); +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); +export const tri2 = fastcat(saw2, isaw2) /** * An inverted triangle signal between 1 and 0 (like `tri`, but flipped). @@ -138,21 +138,21 @@ export const tri2 = fastcat(saw2, isaw2); * n(itri.segment(8).range(0,7)).scale("C:minor") * */ -export const itri = fastcat(isaw, saw); +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); +export const itri2 = fastcat(isaw2, saw2) /** * A signal representing the cycle time. * * @return {Pattern} */ -export const time = signal(id); +export const time = signal(id) /** * The mouse's x position value ranges from 0 to 1. @@ -172,48 +172,48 @@ export const time = signal(id); * */ let _mouseY = 0, - _mouseX = 0; + _mouseX = 0 if (typeof window !== 'undefined') { //document.onmousemove = (e) => { document.addEventListener('mousemove', (e) => { - _mouseY = e.clientY / document.body.clientHeight; - _mouseX = e.clientX / document.body.clientWidth; - }); + _mouseY = e.clientY / document.body.clientHeight + _mouseX = e.clientX / document.body.clientWidth + }) } -export const mousey = signal(() => _mouseY); -export const mouseY = signal(() => _mouseY); -export const mousex = signal(() => _mouseX); -export const mouseX = signal(() => _mouseX); +export const mousey = signal(() => _mouseY) +export const mouseY = signal(() => _mouseY) +export const mousex = signal(() => _mouseX) +export const mouseX = signal(() => _mouseX) // random signals const xorwise = (x) => { - const a = (x << 13) ^ x; - const b = (a >> 17) ^ a; - return (b << 5) ^ b; -}; + const a = (x << 13) ^ x + const b = (a >> 17) ^ a + return (b << 5) ^ b +} // stretch 300 cycles over the range of [0,2**29 == 536870912) then apply the xorshift algorithm -const _frac = (x) => x - Math.trunc(x); +const _frac = (x) => x - Math.trunc(x) -const timeToIntSeed = (x) => xorwise(Math.trunc(_frac(x / 300) * 536870912)); +const timeToIntSeed = (x) => xorwise(Math.trunc(_frac(x / 300) * 536870912)) -const intSeedToRand = (x) => (x % 536870912) / 536870912; +const intSeedToRand = (x) => (x % 536870912) / 536870912 -const timeToRand = (x) => Math.abs(intSeedToRand(timeToIntSeed(x))); +const timeToRand = (x) => Math.abs(intSeedToRand(timeToIntSeed(x))) const timeToRandsPrime = (seed, n) => { - const result = []; + const result = [] // eslint-disable-next-line for (let i = 0; i < n; ++i) { - result.push(intSeedToRand(seed)); - seed = xorwise(seed); + result.push(intSeedToRand(seed)) + seed = xorwise(seed) } - return result; -}; + return result +} -const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n); +const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n) /** * @@ -225,7 +225,7 @@ 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).round().segment(n); +export const run = (n) => saw.range(0, n).round().segment(n) /** * Creates a pattern from a binary number. @@ -237,9 +237,9 @@ export const run = (n) => saw.range(0, n).round().segment(n); * // "hh".s().struct("1 0 1") */ export const binary = (n) => { - const nBits = reify(n).log2(0).floor().add(1); - return binaryN(n, nBits); -}; + 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. @@ -252,29 +252,29 @@ export const binary = (n) => { * // "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); + 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)); -}; + 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 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); -}; + .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(); -}; + 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. @@ -286,8 +286,8 @@ const _rearrangeWith = (ipat, n, pat) => { * seq("c d e f".shuffle(4), "g").note().sound("piano") */ export const shuffle = register('shuffle', (n, pat) => { - return _rearrangeWith(randrun(n), 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`, @@ -299,8 +299,8 @@ export const shuffle = register('shuffle', (n, pat) => { * seq("c d e f".scramble(4), "g").note().sound("piano") */ export const scramble = register('scramble', (n, pat) => { - return _rearrangeWith(_irand(n)._segment(n), n, pat); -}); + return _rearrangeWith(_irand(n)._segment(n), n, pat) +}) /** * A continuous pattern of random numbers, between 0 and 1. @@ -311,13 +311,13 @@ export const scramble = register('scramble', (n, pat) => { * s("bd*4,hh*8").cutoff(rand.range(500,8000)) * */ -export const rand = signal(timeToRand); +export const rand = signal(timeToRand) /** * A continuous pattern of random numbers, between -1 and 1 */ -export const rand2 = rand.toBipolar(); +export const rand2 = rand.toBipolar() -export const _brandBy = (p) => rand.fmap((x) => x < p); +export const _brandBy = (p) => rand.fmap((x) => x < p) /** * A continuous pattern of 0 or 1 (binary random), with a probability for the value being 1 @@ -327,7 +327,7 @@ export const _brandBy = (p) => rand.fmap((x) => x < p); * @example * s("hh*10").pan(brandBy(0.2)) */ -export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin(); +export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin() /** * A continuous pattern of 0 or 1 (binary random) @@ -336,9 +336,9 @@ export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin(); * @example * s("hh*10").pan(brand) */ -export const brand = _brandBy(0.5); +export const brand = _brandBy(0.5) -export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i)); +export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i)) /** * A continuous pattern of random integers, between 0 and n-1. @@ -350,19 +350,19 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i)); * n(irand(8)).struct("x x*2 x x*3").scale("C:minor") * */ -export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin(); +export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin() export const __chooseWith = (pat, xs) => { - xs = xs.map(reify); + xs = xs.map(reify) if (xs.length == 0) { - return silence; + return silence } return pat.range(0, xs.length).fmap((i) => { - const key = Math.min(Math.max(Math.floor(i), 0), xs.length - 1); - return xs[key]; - }); -}; + const key = Math.min(Math.max(Math.floor(i), 0), xs.length - 1) + return xs[key] + }) +} /** * Choose from the list of values (or patterns of values) using the given * pattern of numbers, which should be in the range of 0..1 @@ -373,8 +373,8 @@ export const __chooseWith = (pat, xs) => { * note("c2 g2!2 d2 f1").s(chooseWith(sine.fast(2), ["sawtooth", "triangle", "bd:6"])) */ export const chooseWith = (pat, xs) => { - return __chooseWith(pat, xs).outerJoin(); -}; + return __chooseWith(pat, xs).outerJoin() +} /** * As with {chooseWith}, but the structure comes from the chosen values, rather @@ -384,8 +384,8 @@ export const chooseWith = (pat, xs) => { * @returns {Pattern} */ export const chooseInWith = (pat, xs) => { - return __chooseWith(pat, xs).innerJoin(); -}; + return __chooseWith(pat, xs).innerJoin() +} /** * Chooses randomly from the given list of elements. @@ -394,11 +394,11 @@ export const chooseInWith = (pat, xs) => { * @example * note("c2 g2!2 d2 f1").s(choose("sine", "triangle", "bd:6")) */ -export const choose = (...xs) => chooseWith(rand, xs); +export const choose = (...xs) => chooseWith(rand, xs) // todo: doc -export const chooseIn = (...xs) => chooseInWith(rand, xs); -export const chooseOut = choose; +export const chooseIn = (...xs) => chooseInWith(rand, xs) +export const chooseOut = choose /** * Chooses from the given list of values (or patterns of values), according @@ -408,8 +408,8 @@ export const chooseOut = choose; * @returns {Pattern} */ Pattern.prototype.choose = function (...xs) { - return chooseWith(this, xs); -}; + return chooseWith(this, xs) +} /** * As with choose, but the pattern that this method is called on should be @@ -418,8 +418,8 @@ Pattern.prototype.choose = function (...xs) { * @returns {Pattern} */ Pattern.prototype.choose2 = function (...xs) { - return chooseWith(this.fromBipolar(), xs); -}; + return chooseWith(this.fromBipolar(), xs) +} /** * Picks one of the elements at random each cycle. @@ -430,38 +430,40 @@ Pattern.prototype.choose2 = function (...xs) { * @example * s("bd | hh | sd").fast(8) */ -export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs); +export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs) -export const randcat = chooseCycles; +export const randcat = chooseCycles const _wchooseWith = function (pat, ...pairs) { // A list of patterns of values - const values = pairs.map((pair) => reify(pair[0])); + const values = pairs.map((pair) => reify(pair[0])) // A list of weight patterns - const weights = []; + const weights = [] - let total = pure(0); + let total = pure(0) for (const pair of pairs) { // 'add' accepts either values or patterns of values here, so no need // to explicitly reify - total = total.add(pair[1]); + total = total.add(pair[1]) // accumulate our list of weight patterns - weights.push(total); + weights.push(total) } // a pattern of lists of weights - const weightspat = sequenceP(weights); + const weightspat = sequenceP(weights) // Takes a number from 0-1, returns a pattern of patterns of values const match = function (r) { - const findpat = total.mul(r); - return weightspat.fmap((weights) => (find) => values[weights.findIndex((x) => x > find, weights)]).appLeft(findpat); - }; + const findpat = total.mul(r) + return weightspat + .fmap((weights) => (find) => values[weights.findIndex((x) => x > find, weights)]) + .appLeft(findpat) + } // This returns a pattern of patterns.. The innerJoin is in wchooseCycles - return pat.bind(match); -}; + return pat.bind(match) +} -const wchooseWith = (...args) => _wchooseWith(...args).outerJoin(); +const wchooseWith = (...args) => _wchooseWith(...args).outerJoin() /** * Chooses randomly from the given list of elements by giving a probability to each element @@ -470,7 +472,7 @@ const wchooseWith = (...args) => _wchooseWith(...args).outerJoin(); * @example * note("c2 g2!2 d2 f1").s(wchoose(["sine",10], ["triangle",1], ["bd:6",1])) */ -export const wchoose = (...pairs) => wchooseWith(rand, ...pairs); +export const wchoose = (...pairs) => wchooseWith(rand, ...pairs) /** * Picks one of the elements at random each cycle by giving a probability to each element @@ -484,39 +486,39 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs); * // 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(); +export const wchooseCycles = (...pairs) => _wchooseWith(rand.segment(1), ...pairs).innerJoin() -export const wrandcat = wchooseCycles; +export const wrandcat = wchooseCycles function _perlin(t) { - let ta = Math.floor(t); - let tb = ta + 1; - const smootherStep = (x) => 6.0 * x ** 5 - 15.0 * x ** 4 + 10.0 * x ** 3; - const interp = (x) => (a) => (b) => a + smootherStep(x) * (b - a); - const v = interp(t - ta)(timeToRand(ta))(timeToRand(tb)); - return v; + let ta = Math.floor(t) + let tb = ta + 1 + const smootherStep = (x) => 6.0 * x ** 5 - 15.0 * x ** 4 + 10.0 * x ** 3 + const interp = (x) => (a) => (b) => a + smootherStep(x) * (b - a) + const v = interp(t - ta)(timeToRand(ta))(timeToRand(tb)) + return v } export const perlinWith = (tpat) => { - return tpat.fmap(_perlin); -}; + return tpat.fmap(_perlin) +} function _berlin(t) { - const prevRidgeStartIndex = Math.floor(t); - const nextRidgeStartIndex = prevRidgeStartIndex + 1; + const prevRidgeStartIndex = Math.floor(t) + const nextRidgeStartIndex = prevRidgeStartIndex + 1 - const prevRidgeBottomPoint = timeToRand(prevRidgeStartIndex); - const nextRidgeTopPoint = timeToRand(nextRidgeStartIndex) + prevRidgeBottomPoint; + const prevRidgeBottomPoint = timeToRand(prevRidgeStartIndex) + const nextRidgeTopPoint = timeToRand(nextRidgeStartIndex) + prevRidgeBottomPoint - const currentPercent = (t - prevRidgeStartIndex) / (nextRidgeStartIndex - prevRidgeStartIndex); + const currentPercent = (t - prevRidgeStartIndex) / (nextRidgeStartIndex - prevRidgeStartIndex) const interp = (a, b, t) => { - return a + (b - a) * t; - }; - return interp(prevRidgeBottomPoint, nextRidgeTopPoint, currentPercent) / 2; + return a + (b - a) * t + } + return interp(prevRidgeBottomPoint, nextRidgeTopPoint, currentPercent) / 2 } export const berlinWith = (tpat) => { - return tpat.fmap(_berlin); -}; + return tpat.fmap(_berlin) +} /** * Generates a continuous pattern of [perlin noise](https://en.wikipedia.org/wiki/Perlin_noise), in the range 0..1. @@ -527,7 +529,7 @@ export const berlinWith = (tpat) => { * s("bd*4,hh*8").cutoff(perlin.range(500,8000)) * */ -export const perlin = perlinWith(time.fmap((v) => Number(v))); +export const perlin = perlinWith(time.fmap((v) => Number(v))) /** * Generates a continuous pattern of [berlin noise](conceived by Jame Coyne and Jade Rowland as a joke but turned out to be surprisingly cool and useful, @@ -539,14 +541,14 @@ export const perlin = perlinWith(time.fmap((v) => Number(v))); * n("0!16".add(berlin.fast(4).mul(14))).scale("d:minor") * */ -export const berlin = berlinWith(time.fmap((v) => Number(v))); +export const berlin = berlinWith(time.fmap((v) => Number(v))) export const degradeByWith = register( 'degradeByWith', (withPat, x, pat) => pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)), true, - true, -); + true +) /** * Randomly removes events from the pattern by a given amount. @@ -568,11 +570,11 @@ export const degradeByWith = register( export const degradeBy = register( 'degradeBy', function (x, pat) { - return pat._degradeByWith(rand, x); + return pat._degradeByWith(rand, x) }, true, - true, -); + true +) /** * @@ -586,7 +588,7 @@ export const degradeBy = register( * @example * s("[hh?]*8") */ -export const degrade = register('degrade', (pat) => pat._degradeBy(0.5), true, true); +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. @@ -611,12 +613,12 @@ export const undegradeBy = register( function (x, pat) { return pat._degradeByWith( rand.fmap((r) => 1 - r), - x, - ); + x + ) }, true, - true, -); + true +) /** * Inverse of `degrade`: Randomly removes 50% of events from the pattern. Shorthand for `.undegradeBy(0.5)` @@ -633,7 +635,7 @@ export const undegradeBy = register( * x => x.undegrade().pan(1) * ) */ -export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5), true, true); +export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5), true, true) /** * @@ -652,8 +654,8 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5), t export const sometimesBy = register('sometimesBy', function (patx, func, pat) { return reify(patx) .fmap((x) => stack(pat._degradeBy(x), func(pat._undegradeBy(1 - x)))) - .innerJoin(); -}); + .innerJoin() +}) /** * @@ -667,8 +669,8 @@ export const sometimesBy = register('sometimesBy', function (patx, func, pat) { * s("hh*8").sometimes(x=>x.speed("0.5")) */ export const sometimes = register('sometimes', function (func, pat) { - return pat._sometimesBy(0.5, func); -}); + return pat._sometimesBy(0.5, func) +}) /** * @@ -689,11 +691,11 @@ export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) .fmap((x) => stack( pat._degradeByWith(rand._segment(1), x), - func(pat._degradeByWith(rand.fmap((r) => 1 - r)._segment(1), 1 - x)), - ), + func(pat._degradeByWith(rand.fmap((r) => 1 - r)._segment(1), 1 - x)) + ) ) - .innerJoin(); -}); + .innerJoin() +}) /** * @@ -706,8 +708,8 @@ export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) * s("bd,hh*8").someCycles(x=>x.speed("0.5")) */ export const someCycles = register('someCycles', function (func, pat) { - return pat._someCyclesBy(0.5, func); -}); + return pat._someCyclesBy(0.5, func) +}) /** * @@ -720,8 +722,8 @@ export const someCycles = register('someCycles', function (func, pat) { * s("hh*8").often(x=>x.speed("0.5")) */ export const often = register('often', function (func, pat) { - return pat.sometimesBy(0.75, func); -}); + return pat.sometimesBy(0.75, func) +}) /** * @@ -734,8 +736,8 @@ export const often = register('often', function (func, pat) { * s("hh*8").rarely(x=>x.speed("0.5")) */ export const rarely = register('rarely', function (func, pat) { - return pat.sometimesBy(0.25, func); -}); + return pat.sometimesBy(0.25, func) +}) /** * @@ -748,8 +750,8 @@ export const rarely = register('rarely', function (func, pat) { * s("hh*8").almostNever(x=>x.speed("0.5")) */ export const almostNever = register('almostNever', function (func, pat) { - return pat.sometimesBy(0.1, func); -}); + return pat.sometimesBy(0.1, func) +}) /** * @@ -762,8 +764,8 @@ export const almostNever = register('almostNever', function (func, pat) { * s("hh*8").almostAlways(x=>x.speed("0.5")) */ export const almostAlways = register('almostAlways', function (func, pat) { - return pat.sometimesBy(0.9, func); -}); + return pat.sometimesBy(0.9, func) +}) /** * @@ -776,8 +778,8 @@ export const almostAlways = register('almostAlways', function (func, pat) { * s("hh*8").never(x=>x.speed("0.5")) */ export const never = register('never', function (_, pat) { - return pat; -}); + return pat +}) /** * @@ -790,20 +792,20 @@ export const never = register('never', function (_, pat) { * s("hh*8").always(x=>x.speed("0.5")) */ export const always = register('always', function (func, pat) { - return func(pat); -}); + return func(pat) +}) //keyname: string | Array //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]; + keyname = [keyname] } - const keyState = getCurrentKeyboardState(); + const keyState = getCurrentKeyboardState() return keyname.every((x) => { - const keyName = keyAlias.get(x) ?? x; - return keyState[keyName]; - }); + const keyName = keyAlias.get(x) ?? x + return keyState[keyName] + }) } /** @@ -819,8 +821,8 @@ export function _keyDown(keyname) { */ export const whenKey = register('whenKey', function (input, func, pat) { - return pat.when(_keyDown(input), func); -}); + return pat.when(_keyDown(input), func) +}) /** * @@ -835,5 +837,5 @@ export const whenKey = register('whenKey', function (input, func, pat) { */ export const keyDown = register('keyDown', function (pat) { - return pat.fmap(_keyDown); -}); + return pat.fmap(_keyDown) +}) diff --git a/src/strudel/core/speak.mjs b/src/strudel/core/speak.mjs index e0f6018..4e51a42 100644 --- a/src/strudel/core/speak.mjs +++ b/src/strudel/core/speak.mjs @@ -4,35 +4,35 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { register } from './index.mjs'; +import { register } from './index.mjs' -let synth; +let synth try { - synth = window?.speechSynthesis; + synth = window?.speechSynthesis } catch (err) { - console.warn('cannot use window: not in browser?'); + console.warn('cannot use window: not in browser?') } -let allVoices = synth?.getVoices(); +let allVoices = synth?.getVoices() // console.log('voices', allVoices); function triggerSpeech(words, lang, voice) { - synth.cancel(); - const utterance = new SpeechSynthesisUtterance(words); - utterance.lang = lang; - allVoices = synth.getVoices(); - const voices = allVoices.filter((v) => v.lang.includes(lang)); + synth.cancel() + const utterance = new SpeechSynthesisUtterance(words) + utterance.lang = lang + allVoices = synth.getVoices() + const voices = allVoices.filter((v) => v.lang.includes(lang)) if (typeof voice === 'number') { - utterance.voice = voices[voice % voices.length]; + utterance.voice = voices[voice % voices.length] } else if (typeof voice === 'string') { - utterance.voice = voices.find((voice) => voice.name === voice); + utterance.voice = voices.find((voice) => voice.name === voice) } // console.log(utterance.voice?.name, utterance.voice?.lang); - speechSynthesis.speak(utterance); + speechSynthesis.speak(utterance) } export const speak = register('speak', function (lang, voice, pat) { return pat.onTrigger((hap) => { - triggerSpeech(hap.value, lang, voice); - }); -}); + triggerSpeech(hap.value, lang, voice) + }) +}) diff --git a/src/strudel/core/state.mjs b/src/strudel/core/state.mjs index 8aa5819..a9c9051 100644 --- a/src/strudel/core/state.mjs +++ b/src/strudel/core/state.mjs @@ -6,23 +6,23 @@ This program is free software: you can redistribute it and/or modify it under th export class State { constructor(span, controls = {}) { - this.span = span; - this.controls = controls; + this.span = span + this.controls = controls } // Returns new State with different span setSpan(span) { - return new State(span, this.controls); + return new State(span, this.controls) } withSpan(func) { - return this.setSpan(func(this.span)); + return this.setSpan(func(this.span)) } // Returns new State with added controls. setControls(controls) { - return new State(this.span, { ...this.controls, ...controls }); + return new State(this.span, { ...this.controls, ...controls }) } } -export default State; +export default State diff --git a/src/strudel/core/time.mjs b/src/strudel/core/time.mjs index 80daaf5..4139b33 100644 --- a/src/strudel/core/time.mjs +++ b/src/strudel/core/time.mjs @@ -1,11 +1,11 @@ -let time; +let time export function getTime() { if (!time) { - throw new Error('no time set! use setTime to define a time source'); + throw new Error('no time set! use setTime to define a time source') } - return time(); + return time() } export function setTime(func) { - time = func; + time = func } diff --git a/src/strudel/core/timespan.mjs b/src/strudel/core/timespan.mjs index 446156b..af83a4f 100644 --- a/src/strudel/core/timespan.mjs +++ b/src/strudel/core/timespan.mjs @@ -4,114 +4,114 @@ Copyright (C) 2022 Strudel contributors - see . */ -import Fraction from './fraction.mjs'; +import Fraction from './fraction.mjs' export class TimeSpan { constructor(begin, end) { - this.begin = Fraction(begin); - this.end = Fraction(end); + this.begin = Fraction(begin) + this.end = Fraction(end) } get spanCycles() { - const spans = []; - var begin = this.begin; - const end = this.end; - const end_sam = end.sam(); + const spans = [] + var begin = this.begin + const end = this.end + const end_sam = end.sam() // Support zero-width timespans if (begin.equals(end)) { - return [new TimeSpan(begin, end)]; + return [new TimeSpan(begin, end)] } while (end.gt(begin)) { // If begin and end are in the same cycle, we're done. if (begin.sam().equals(end_sam)) { - spans.push(new TimeSpan(begin, this.end)); - break; + spans.push(new TimeSpan(begin, this.end)) + break } // add a timespan up to the next sam - const next_begin = begin.nextSam(); - spans.push(new TimeSpan(begin, next_begin)); + const next_begin = begin.nextSam() + spans.push(new TimeSpan(begin, next_begin)) // continue with the next cycle - begin = next_begin; + begin = next_begin } - return spans; + return spans } get duration() { - return this.end.sub(this.begin); + return this.end.sub(this.begin) } cycleArc() { // Shifts a timespan to one of equal duration that starts within cycle zero. // (Note that the output timespan probably does not start *at* Time 0 -- // that only happens when the input Arc starts at an integral Time.) - const b = this.begin.cyclePos(); - const e = b.add(this.duration); - return new TimeSpan(b, e); + const b = this.begin.cyclePos() + const e = b.add(this.duration) + return new TimeSpan(b, e) } withTime(func_time) { // Applies given function to both the begin and end time of the timespan""" - return new TimeSpan(func_time(this.begin), func_time(this.end)); + return new TimeSpan(func_time(this.begin), func_time(this.end)) } withEnd(func_time) { // Applies given function to the end time of the timespan""" - return new TimeSpan(this.begin, func_time(this.end)); + return new TimeSpan(this.begin, func_time(this.end)) } withCycle(func_time) { // Like withTime, but time is relative to relative to the cycle (i.e. the // sam of the start of the timespan) - const sam = this.begin.sam(); - const b = sam.add(func_time(this.begin.sub(sam))); - const e = sam.add(func_time(this.end.sub(sam))); - return new TimeSpan(b, e); + const sam = this.begin.sam() + const b = sam.add(func_time(this.begin.sub(sam))) + const e = sam.add(func_time(this.end.sub(sam))) + return new TimeSpan(b, e) } intersection(other) { // Intersection of two timespans, returns undefined if they don't intersect. - const intersect_begin = this.begin.max(other.begin); - const intersect_end = this.end.min(other.end); + const intersect_begin = this.begin.max(other.begin) + const intersect_end = this.end.min(other.end) if (intersect_begin.gt(intersect_end)) { - return undefined; + return undefined } if (intersect_begin.equals(intersect_end)) { // Zero-width (point) intersection - doesn't intersect if it's at the end of a // non-zero-width timespan. if (intersect_begin.equals(this.end) && this.begin.lt(this.end)) { - return undefined; + return undefined } if (intersect_begin.equals(other.end) && other.begin.lt(other.end)) { - return undefined; + return undefined } } - return new TimeSpan(intersect_begin, intersect_end); + return new TimeSpan(intersect_begin, intersect_end) } intersection_e(other) { // Like 'sect', but raises an exception if the timespans don't intersect. - const result = this.intersection(other); + const result = this.intersection(other) if (result == undefined) { - throw 'TimeSpans do not intersect'; + throw 'TimeSpans do not intersect' } - return result; + return result } midpoint() { - return this.begin.add(this.duration.div(Fraction(2))); + return this.begin.add(this.duration.div(Fraction(2))) } equals(other) { - return this.begin.equals(other.begin) && this.end.equals(other.end); + return this.begin.equals(other.begin) && this.end.equals(other.end) } show() { - return this.begin.show() + ' → ' + this.end.show(); + return this.begin.show() + ' → ' + this.end.show() } } -export default TimeSpan; +export default TimeSpan diff --git a/src/strudel/core/ui.mjs b/src/strudel/core/ui.mjs index 5a2c54a..5897776 100644 --- a/src/strudel/core/ui.mjs +++ b/src/strudel/core/ui.mjs @@ -5,28 +5,28 @@ This program is free software: you can redistribute it and/or modify it under th */ export const backgroundImage = function (src, animateOptions = {}) { - const container = document.getElementById('code'); - const bg = 'background-image:url(' + src + ');background-size:contain;'; - container.style = bg; - const { className: initialClassName } = container; + const container = document.getElementById('code') + const bg = 'background-image:url(' + src + ');background-size:contain;' + container.style = bg + const { className: initialClassName } = container const handleOption = (option, value) => { - ({ + ;({ style: () => (container.style = bg + ';' + value), className: () => (container.className = value + ' ' + initialClassName), - })[option](); - }; - const funcOptions = Object.entries(animateOptions).filter(([_, v]) => typeof v === 'function'); - const stringOptions = Object.entries(animateOptions).filter(([_, v]) => typeof v === 'string'); - stringOptions.forEach(([option, value]) => handleOption(option, value)); + })[option]() + } + const funcOptions = Object.entries(animateOptions).filter(([_, v]) => typeof v === 'function') + const stringOptions = Object.entries(animateOptions).filter(([_, v]) => typeof v === 'string') + stringOptions.forEach(([option, value]) => handleOption(option, value)) if (funcOptions.length === 0) { - return; + return } -}; +} export const cleanupUi = () => { - const container = document.getElementById('code'); + const container = document.getElementById('code') if (container) { - container.style = ''; + container.style = '' } -}; +} diff --git a/src/strudel/core/util.mjs b/src/strudel/core/util.mjs index ef3f1e9..c1a4a8b 100644 --- a/src/strudel/core/util.mjs +++ b/src/strudel/core/util.mjs @@ -4,67 +4,67 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { logger } from './logger.mjs'; +import { logger } from './logger.mjs' // returns true if the given string is a note -export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name); -export const isNote = (name) => /^[a-gA-G][#bsf]*-?[0-9]?$/.test(name); +export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name) +export const isNote = (name) => /^[a-gA-G][#bsf]*-?[0-9]?$/.test(name) export const tokenizeNote = (note) => { if (typeof note !== 'string') { - return []; + return [] } - const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)(-?[0-9]*)$/)?.slice(1) || []; + const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)(-?[0-9]*)$/)?.slice(1) || [] if (!pc) { - return []; + return [] } - return [pc, acc, oct ? Number(oct) : undefined]; -}; + return [pc, acc, oct ? Number(oct) : undefined] +} -const chromas = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }; -const accs = { '#': 1, b: -1, s: 1, f: -1 }; +const chromas = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 } +const accs = { '#': 1, b: -1, s: 1, f: -1 } // turns the given note into its midi number representation export const noteToMidi = (note, defaultOctave = 3) => { - const [pc, acc, oct = defaultOctave] = tokenizeNote(note); + const [pc, acc, oct = defaultOctave] = tokenizeNote(note) if (!pc) { - throw new Error('not a note: "' + note + '"'); + throw new Error('not a note: "' + note + '"') } - const chroma = chromas[pc.toLowerCase()]; - const offset = acc?.split('').reduce((o, char) => o + accs[char], 0) || 0; - return (Number(oct) + 1) * 12 + chroma + offset; -}; + const chroma = chromas[pc.toLowerCase()] + const offset = acc?.split('').reduce((o, char) => o + accs[char], 0) || 0 + return (Number(oct) + 1) * 12 + chroma + offset +} export const midiToFreq = (n) => { - return Math.pow(2, (n - 69) / 12) * 440; -}; + return Math.pow(2, (n - 69) / 12) * 440 +} export const freqToMidi = (freq) => { - return (12 * Math.log(freq / 440)) / Math.LN2 + 69; -}; + return (12 * Math.log(freq / 440)) / Math.LN2 + 69 +} export const valueToMidi = (value, fallbackValue) => { if (typeof value !== 'object') { - throw new Error('valueToMidi: expected object value'); + throw new Error('valueToMidi: expected object value') } - let { freq, note } = value; + let { freq, note } = value if (typeof freq === 'number') { - return freqToMidi(freq); + return freqToMidi(freq) } if (typeof note === 'string') { - return noteToMidi(note); + return noteToMidi(note) } if (typeof note === 'number') { - return note; + return note } if (!fallbackValue) { - throw new Error('valueToMidi: expected freq or note to be set'); + throw new Error('valueToMidi: expected freq or note to be set') } - return fallbackValue; -}; + return fallbackValue +} // used to schedule external event like midi and osc out export const getEventOffsetMs = (targetTimeSeconds, currentTimeSeconds) => { - return (targetTimeSeconds - currentTimeSeconds) * 1000; -}; + return (targetTimeSeconds - currentTimeSeconds) * 1000 +} /** * @deprecated does not appear to be referenced or invoked anywhere in the codebase @@ -72,148 +72,148 @@ export const getEventOffsetMs = (targetTimeSeconds, currentTimeSeconds) => { */ export const getFreq = (noteOrMidi) => { if (typeof noteOrMidi === 'number') { - return midiToFreq(noteOrMidi); + return midiToFreq(noteOrMidi) } - return midiToFreq(noteToMidi(noteOrMidi)); -}; + return midiToFreq(noteToMidi(noteOrMidi)) +} -const pcs = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B']; +const pcs = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'] /** * @deprecated only used in workshop (first-notes) * @noAutocomplete */ export const midi2note = (n) => { - const oct = Math.floor(n / 12) - 1; - const pc = pcs[n % 12]; - return pc + oct; -}; + const oct = Math.floor(n / 12) - 1 + const pc = pcs[n % 12] + return pc + oct +} // modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does) -export const _mod = (n, m) => ((n % m) + m) % m; +export const _mod = (n, m) => ((n % m) + m) % m // average numbers in an array -export const averageArray = (arr) => arr.reduce((a, b) => a + b) / arr.length; +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'); - return fallback; + logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning') + return fallback } - return value; + return value } // round to nearest int, negative numbers will output a subtracted index export const getSoundIndex = (n, numSounds) => { - return _mod(Math.round(nanFallback(n ?? 0, 0)), numSounds); -}; + return _mod(Math.round(nanFallback(n ?? 0, 0)), numSounds) +} export const getPlayableNoteValue = (hap) => { - let { value, context } = hap; - let note = value; + let { value, context } = hap + let note = value if (typeof note === 'object' && !Array.isArray(note)) { - note = note.note || note.n || note.value; + note = note.note || note.n || note.value if (note === undefined) { - throw new Error(`cannot find a playable note for ${JSON.stringify(value)}`); + throw new Error(`cannot find a playable note for ${JSON.stringify(value)}`) } } // if value is number => interpret as midi number as long as its not marked as frequency if (typeof note === 'number' && context.type !== 'frequency') { - note = midiToFreq(hap.value); + note = midiToFreq(hap.value) } else if (typeof note === 'number' && context.type === 'frequency') { - note = hap.value; // legacy workaround.. will be removed in the future + note = hap.value // legacy workaround.. will be removed in the future } else if (typeof note !== 'string' || !isNote(note)) { - throw new Error('not a note: ' + JSON.stringify(note)); + throw new Error('not a note: ' + JSON.stringify(note)) } - return note; -}; + return note +} export const getFrequency = (hap) => { - let { value, context } = hap; + let { value, context } = hap // if value is number => interpret as midi number as long as its not marked as frequency if (typeof value === 'object') { if (value.freq) { - return value.freq; + return value.freq } - return getFreq(value.note || value.n || value.value); + return getFreq(value.note || value.n || value.value) } if (typeof value === 'number' && context.type !== 'frequency') { - value = midiToFreq(hap.value); + value = midiToFreq(hap.value) } else if (typeof value === 'string' && isNote(value)) { - value = midiToFreq(noteToMidi(hap.value)); + value = midiToFreq(noteToMidi(hap.value)) } else if (typeof value !== 'number') { - throw new Error('not a note or frequency: ' + value); + throw new Error('not a note or frequency: ' + value) } - return value; -}; + return value +} // rotate array by n steps (to the left) -export const rotate = (arr, n) => arr.slice(n).concat(arr.slice(0, n)); +export const rotate = (arr, n) => arr.slice(n).concat(arr.slice(0, n)) export const pipe = (...funcs) => { return funcs.reduce( (f, g) => (...args) => f(g(...args)), - (x) => x, - ); -}; + (x) => x + ) +} -export const compose = (...funcs) => pipe(...funcs.reverse()); +export const compose = (...funcs) => pipe(...funcs.reverse()) // Removes 'None' values from given list -export const removeUndefineds = (xs) => xs.filter((x) => x != undefined); +export const removeUndefineds = (xs) => xs.filter((x) => x != undefined) // flattens by one level -export const flatten = (arr) => [].concat(...arr); +export const flatten = (arr) => [].concat(...arr) -export const id = (a) => a; -export const constant = (a, b) => a; +export const id = (a) => a +export const constant = (a, b) => a -export const listRange = (min, max) => Array.from({ length: max - min + 1 }, (_, i) => i + min); +export const listRange = (min, max) => Array.from({ length: max - min + 1 }, (_, i) => i + min) export function curry(func, overload, arity = func.length) { const fn = function curried(...args) { if (args.length >= arity) { - return func.apply(this, args); + return func.apply(this, args) } else { const partial = function (...args2) { - return curried.apply(this, args.concat(args2)); - }; - if (overload) { - overload(partial, args); + return curried.apply(this, args.concat(args2)) } - return partial; + if (overload) { + overload(partial, args) + } + return partial } - }; + } if (overload) { // overload function without args... needed for chordBass.transpose(2) - overload(fn, []); + overload(fn, []) } - return fn; + return fn } export function parseNumeral(numOrString) { - const asNumber = Number(numOrString); + const asNumber = Number(numOrString) if (!isNaN(asNumber)) { - return asNumber; + return asNumber } if (isNote(numOrString)) { - return noteToMidi(numOrString); + return noteToMidi(numOrString) } - throw new Error(`cannot parse as numeral: "${numOrString}"`); + throw new Error(`cannot parse as numeral: "${numOrString}"`) } export function mapArgs(fn, mapFn) { - return (...args) => fn(...args.map(mapFn)); + return (...args) => fn(...args.map(mapFn)) } export function numeralArgs(fn) { - return mapArgs(fn, parseNumeral); + return mapArgs(fn, parseNumeral) } export function parseFractional(numOrString) { - const asNumber = Number(numOrString); + const asNumber = Number(numOrString) if (!isNaN(asNumber)) { - return asNumber; + return asNumber } const specialValue = { pi: Math.PI, @@ -225,34 +225,47 @@ export function parseFractional(numOrString) { t: 1 / 3, f: 0.2, x: 1 / 6, - }[numOrString]; + }[numOrString] if (typeof specialValue !== 'undefined') { - return specialValue; + return specialValue } - throw new Error(`cannot parse as fractional: "${numOrString}"`); + throw new Error(`cannot parse as fractional: "${numOrString}"`) } -export const fractionalArgs = (fn) => mapArgs(fn, parseFractional); +export const fractionalArgs = (fn) => mapArgs(fn, parseFractional) export const splitAt = function (index, value) { - return [value.slice(0, index), value.slice(index)]; -}; + return [value.slice(0, index), value.slice(index)] +} // Uses the function f to combine the arrays xs, ys element-wise -export const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i])); +export const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i])) export const pairs = function (xs) { - const result = []; + const result = [] for (let i = 0; i < xs.length - 1; ++i) { - result.push([xs[i], xs[i + 1]]); + result.push([xs[i], xs[i + 1]]) } - return result; -}; + return result +} -export const clamp = (num, min, max) => Math.min(Math.max(num, min), max); +export const clamp = (num, min, max) => Math.min(Math.max(num, min), max) /* solmization, not used yet */ -const solfeggio = ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si']; /*solffegio notes*/ +const solfeggio = [ + 'Do', + 'Reb', + 'Re', + 'Mib', + 'Mi', + 'Fa', + 'Solb', + 'Sol', + 'Lab', + 'La', + 'Sib', + 'Si', +] /*solffegio notes*/ const indian = [ 'Sa', 'Re', @@ -261,8 +274,21 @@ const indian = [ 'Pa', 'Dha', 'Ni', -]; /*indian musical notes, seems like they do not use flats or sharps*/ -const german = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Hb', 'H']; /*german & dutch musical notes*/ +] /*indian musical notes, seems like they do not use flats or sharps*/ +const german = [ + 'C', + 'Db', + 'D', + 'Eb', + 'E', + 'F', + 'Gb', + 'G', + 'Ab', + 'A', + 'Hb', + 'H', +] /*german & dutch musical notes*/ const byzantine = [ 'Ni', 'Pab', @@ -276,7 +302,7 @@ const byzantine = [ 'Ke', 'Zob', 'Zo', -]; /*byzantine musical notes*/ +] /*byzantine musical notes*/ const japanese = [ 'I', 'Ro', @@ -285,9 +311,9 @@ const japanese = [ 'Ho', 'He', 'To', -]; /*traditional japanese musical notes, seems like they do not use falts or sharps*/ +] /*traditional japanese musical notes, seems like they do not use falts or sharps*/ -const english = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B']; +const english = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'] export const sol2note = (n, notation = 'letters') => { const pc = @@ -301,25 +327,25 @@ export const sol2note = (n, notation = 'letters') => { ? byzantine : notation === 'japanese' ? japanese - : english; /*if not use standard version*/ - const note = pc[n % 12]; /*calculating the midi value to the note*/ - const oct = Math.floor(n / 12) - 1; - return note + oct; -}; + : english /*if not use standard version*/ + const note = pc[n % 12] /*calculating the midi value to the note*/ + const oct = Math.floor(n / 12) - 1 + return note + oct +} // Remove duplicates from list export function uniq(a) { - var seen = {}; + var seen = {} return a.filter(function (item) { - return seen.hasOwn(item) ? false : (seen[item] = true); - }); + return seen.hasOwn(item) ? false : (seen[item] = true) + }) } // Remove duplicates from list, sorting in the process. Mutates argument! export function uniqsort(a) { return a.sort().filter(function (item, pos, ary) { - return !pos || item != ary[pos - 1]; - }); + return !pos || item != ary[pos - 1] + }) } // rational version @@ -327,46 +353,46 @@ export function uniqsortr(a) { return a .sort((x, y) => x.compare(y)) .filter(function (item, pos, ary) { - return !pos || item.ne(ary[pos - 1]); - }); + return !pos || item.ne(ary[pos - 1]) + }) } // code hashing helpers export function unicodeToBase64(text) { - const utf8Bytes = new TextEncoder().encode(text); - const base64String = btoa(String.fromCharCode(...utf8Bytes)); - return base64String; + const utf8Bytes = new TextEncoder().encode(text) + const base64String = btoa(String.fromCharCode(...utf8Bytes)) + return base64String } export function base64ToUnicode(base64String) { const utf8Bytes = new Uint8Array( atob(base64String) .split('') - .map((char) => char.charCodeAt(0)), - ); - const decodedText = new TextDecoder().decode(utf8Bytes); - return decodedText; + .map((char) => char.charCodeAt(0)) + ) + const decodedText = new TextDecoder().decode(utf8Bytes) + return decodedText } export function code2hash(code) { - return encodeURIComponent(unicodeToBase64(code)); + return encodeURIComponent(unicodeToBase64(code)) //return '#' + encodeURIComponent(btoa(code)); } export function hash2code(hash) { - return base64ToUnicode(decodeURIComponent(hash)); + return base64ToUnicode(decodeURIComponent(hash)) //return atob(decodeURIComponent(codeParam || '')); } export function objectMap(obj, fn) { if (Array.isArray(obj)) { - return obj.map(fn); + return obj.map(fn) } - return Object.fromEntries(Object.entries(obj).map(([k, v], i) => [k, fn(v, k, i)])); + return Object.fromEntries(Object.entries(obj).map(([k, v], i) => [k, fn(v, k, i)])) } export function cycleToSeconds(cycle, cps) { - return cycle / cps; + return cycle / cps } // utility for averaging two clocks together to account for drift @@ -378,62 +404,62 @@ export class ClockCollator { 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.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; - }; + this.prevOffsetTimes = [] + this.offsetTime = null + this.timeAtPrevOffsetSample = null + } } calculateOffset(currentTime) { - const targetClockTime = this.getTargetClockTime(); - const diffBetweenTimeSamples = targetClockTime - this.timeAtPrevOffsetSample; - const newOffsetTime = targetClockTime - 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(); + this.reset() } if (this.offsetTime == null) { - this.offsetTime = newOffsetTime; + this.offsetTime = newOffsetTime } - this.prevOffsetTimes.push(newOffsetTime); + this.prevOffsetTimes.push(newOffsetTime) if (this.prevOffsetTimes.length > this.weight) { - this.prevOffsetTimes.shift(); + 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); + 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; + this.offsetTime = rollingOffsetTime } } - return this.offsetTime; + return this.offsetTime } calculateTimestamp(currentTime, targetTime) { - return this.calculateOffset(currentTime) + targetTime; + return this.calculateOffset(currentTime) + targetTime } } export function getPerformanceTimeSeconds() { - return performance.now() * 0.001; + return performance.now() * 0.001 } function getUnixTimeSeconds() { - return Date.now() * 0.001; + return Date.now() * 0.001 } export const keyAlias = new Map([ @@ -445,27 +471,27 @@ export const keyAlias = new Map([ ['up', 'ArrowUp'], ['left', 'ArrowLeft'], ['right', 'ArrowRight'], -]); -let keyState; +]) +let keyState export function getCurrentKeyboardState() { if (keyState == null) { if (typeof window === 'undefined') { - return; + return } - keyState = {}; + 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 - }); + 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 - }); + keyState[event.key] = false // Mark the key as released + }) } - return { ...keyState }; // Return a shallow copy of the key state object + return { ...keyState } // Return a shallow copy of the key state object } // Floating point versions, see Fraction for rational versions @@ -495,5 +521,5 @@ export function stringifyValues(value, compact = false) { ? compact ? JSON.stringify(value).slice(1, -1).replaceAll('"', '').replaceAll(',', ' ') : JSON.stringify(value) - : value; + : value } diff --git a/src/strudel/core/value.mjs b/src/strudel/core/value.mjs index 9496405..6dfd5d2 100644 --- a/src/strudel/core/value.mjs +++ b/src/strudel/core/value.mjs @@ -4,61 +4,61 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { curry } from './util.mjs'; -import { logger } from './logger.mjs'; +import { curry } from './util.mjs' +import { logger } from './logger.mjs' export function unionWithObj(a, b, func) { if (b?.value !== undefined && Object.keys(b).length === 1) { // https://codeberg.org/uzu/strudel/issues/1026 - logger(`[warn]: Can't do arithmetic on control pattern.`); - return a; + logger(`[warn]: Can't do arithmetic on control pattern.`) + return a } - const common = Object.keys(a).filter((k) => Object.keys(b).includes(k)); - return Object.assign({}, a, b, Object.fromEntries(common.map((k) => [k, func(a[k], b[k])]))); + const common = Object.keys(a).filter((k) => Object.keys(b).includes(k)) + return Object.assign({}, a, b, Object.fromEntries(common.map((k) => [k, func(a[k], b[k])]))) } -export const mul = curry((a, b) => a * b); +export const mul = curry((a, b) => a * b) export const valued = (value) => { if (value?.constructor?.name === 'Value') { - return value; + return value } - return Value.of(value); -}; + return Value.of(value) +} export class Value { constructor(value) { - this.value = value; + this.value = value } static of(x) { - return new Value(x); + return new Value(x) } get isNothing() { - return this.value === null || this.value === undefined; + return this.value === null || this.value === undefined } map(f) { if (this.isNothing) { - return this; + return this } - return Value.of(f(this.value)); + return Value.of(f(this.value)) } mul(n) { - return this.map(mul).ap(n); + return this.map(mul).ap(n) } ap(other) { - return valued(other).map(this.value); + return valued(other).map(this.value) } unionWith(other, func) { - const type = typeof this.value; - other = valued(other); + const type = typeof this.value + other = valued(other) if (type !== typeof other.value) { - throw new Error('unionWith: both Values must have same type!'); + throw new Error('unionWith: both Values must have same type!') } if (Array.isArray(type) || type !== 'object') { - throw new Error('unionWith: expected objects'); + throw new Error('unionWith: expected objects') } - return this.map((v) => unionWithObj(v, other.value, func)); + return this.map((v) => unionWithObj(v, other.value, func)) } } -export const map = curry((f, anyFunctor) => anyFunctor.map(f)); +export const map = curry((f, anyFunctor) => anyFunctor.map(f)) diff --git a/src/strudel/core/zyklus.mjs b/src/strudel/core/zyklus.mjs index a09ace8..d3f2f26 100644 --- a/src/strudel/core/zyklus.mjs +++ b/src/strudel/core/zyklus.mjs @@ -9,46 +9,46 @@ function createClock( overlap = 0.1, // overlap between callbacks setInterval = globalThis.setInterval, clearInterval = globalThis.clearInterval, - round = true, + round = true ) { - let tick = 0; // counts callbacks - let phase = 0; // next callback time - let precision = 10 ** 4; // used to round phase - let minLatency = 0.01; - const setDuration = (setter) => (duration = setter(duration)); - overlap = overlap || interval / 2; + let tick = 0 // counts callbacks + let phase = 0 // next callback time + let precision = 10 ** 4 // used to round phase + let minLatency = 0.01 + const setDuration = (setter) => (duration = setter(duration)) + overlap = overlap || interval / 2 const onTick = () => { - const t = getTime(); - const lookahead = t + interval + overlap; // the time window for this tick + const t = getTime() + const lookahead = t + interval + overlap // the time window for this tick if (phase === 0) { - phase = t + minLatency; + phase = t + minLatency } // callback as long as we're inside the lookahead while (phase < lookahead) { - phase = round ? Math.round(phase * precision) / precision : phase; - callback(phase, duration, tick, t); // callback has to skip / handle phase < t! - phase += duration; // increment phase by duration - tick++; + phase = round ? Math.round(phase * precision) / precision : phase + callback(phase, duration, tick, t) // callback has to skip / handle phase < t! + phase += duration // increment phase by duration + tick++ } - }; - let intervalID; + } + let intervalID const start = () => { - clear(); // just in case start was called more than once - onTick(); - intervalID = setInterval(onTick, interval * 1000); - }; + clear() // just in case start was called more than once + onTick() + intervalID = setInterval(onTick, interval * 1000) + } const clear = () => { - intervalID !== undefined && clearInterval(intervalID); - intervalID = undefined; - }; - const pause = () => clear(); + intervalID !== undefined && clearInterval(intervalID) + intervalID = undefined + } + const pause = () => clear() const stop = () => { - tick = 0; - phase = 0; - clear(); - }; - const getPhase = () => phase; + tick = 0 + phase = 0 + clear() + } + const getPhase = () => phase // setCallback - return { setDuration, start, stop, pause, duration, interval, getPhase, minLatency }; + return { setDuration, start, stop, pause, duration, interval, getPhase, minLatency } } -export default createClock; +export default createClock diff --git a/src/strudel/draw/animate.mjs b/src/strudel/draw/animate.mjs index d850815..93f10b1 100644 --- a/src/strudel/draw/animate.mjs +++ b/src/strudel/draw/animate.mjs @@ -1,69 +1,78 @@ -import { Pattern, silence, register, pure, createParams } from '@strudel/core'; -import { getDrawContext } from './draw.mjs'; +import { Pattern, silence, register, pure, createParams } from '../core/index.mjs' +import { getDrawContext } from './draw.mjs' -let clearColor = '#22222210'; +let clearColor = '#22222210' Pattern.prototype.animate = function ({ callback, sync = false, smear = 0.5 } = {}) { - window.frame && cancelAnimationFrame(window.frame); - const ctx = getDrawContext(); - let { clientWidth: ww, clientHeight: wh } = ctx.canvas; - ww *= window.devicePixelRatio; - wh *= window.devicePixelRatio; - let smearPart = smear === 0 ? '99' : Number((1 - smear) * 100).toFixed(0); - smearPart = smearPart.length === 1 ? `0${smearPart}` : smearPart; - clearColor = `#200010${smearPart}`; + window.frame && cancelAnimationFrame(window.frame) + const ctx = getDrawContext() + let { clientWidth: ww, clientHeight: wh } = ctx.canvas + ww *= window.devicePixelRatio + wh *= window.devicePixelRatio + let smearPart = smear === 0 ? '99' : Number((1 - smear) * 100).toFixed(0) + smearPart = smearPart.length === 1 ? `0${smearPart}` : smearPart + clearColor = `#200010${smearPart}` const render = (t) => { - let frame; + let frame /* if (sync) { t = scheduler.now(); frame = this.queryArc(t, t); } else { */ - t = Math.round(t); - frame = this.slow(1000).queryArc(t, t); + t = Math.round(t) + frame = this.slow(1000).queryArc(t, t) // } - ctx.fillStyle = clearColor; - ctx.fillRect(0, 0, ww, wh); + ctx.fillStyle = clearColor + ctx.fillRect(0, 0, ww, wh) frame.forEach((f) => { - let { x, y, w, h, s, r, angle = 0, fill = 'darkseagreen' } = f.value; - w *= ww; - h *= wh; + let { x, y, w, h, s, r, angle = 0, fill = 'darkseagreen' } = f.value + w *= ww + h *= wh if (r !== undefined && angle !== undefined) { - const radians = angle * 2 * Math.PI; - const [cx, cy] = [(ww - w) / 2, (wh - h) / 2]; - x = cx + Math.cos(radians) * r * cx; - y = cy + Math.sin(radians) * r * cy; + const radians = angle * 2 * Math.PI + const [cx, cy] = [(ww - w) / 2, (wh - h) / 2] + x = cx + Math.cos(radians) * r * cx + y = cy + Math.sin(radians) * r * cy } else { - x *= ww - w; - y *= wh - h; + x *= ww - w + y *= wh - h } - const val = { ...f.value, x, y, w, h }; - ctx.fillStyle = fill; + const val = { ...f.value, x, y, w, h } + ctx.fillStyle = fill if (s === 'rect') { - ctx.fillRect(x, y, w, h); + ctx.fillRect(x, y, w, h) } else if (s === 'ellipse') { - ctx.beginPath(); - ctx.ellipse(x + w / 2, y + h / 2, w / 2, h / 2, 0, 0, 2 * Math.PI); - ctx.fill(); + ctx.beginPath() + ctx.ellipse(x + w / 2, y + h / 2, w / 2, h / 2, 0, 0, 2 * Math.PI) + ctx.fill() } - callback && callback(ctx, val, f); - }); - window.frame = requestAnimationFrame(render); - }; - window.frame = requestAnimationFrame(render); - return silence; -}; + callback && callback(ctx, val, f) + }) + window.frame = requestAnimationFrame(render) + } + window.frame = requestAnimationFrame(render) + return silence +} -export const { x, y, w, h, angle, r, fill, smear } = createParams('x', 'y', 'w', 'h', 'angle', 'r', 'fill', 'smear'); +export const { x, y, w, h, angle, r, fill, smear } = createParams( + 'x', + 'y', + 'w', + 'h', + 'angle', + 'r', + 'fill', + 'smear' +) export const rescale = register('rescale', function (f, pat) { - return pat.mul(x(f).w(f).y(f).h(f)); -}); + return pat.mul(x(f).w(f).y(f).h(f)) +}) export const moveXY = register('moveXY', function (dx, dy, pat) { - return pat.add(x(dx).y(dy)); -}); + return pat.add(x(dx).y(dy)) +}) export const zoomIn = register('zoomIn', function (f, pat) { - const d = pure(1).sub(f).div(2); - return pat.rescale(f).move(d, d); -}); + const d = pure(1).sub(f).div(2) + return pat.rescale(f).move(d, d) +}) diff --git a/src/strudel/draw/color.mjs b/src/strudel/draw/color.mjs index faab900..120eb93 100644 --- a/src/strudel/draw/color.mjs +++ b/src/strudel/draw/color.mjs @@ -146,30 +146,30 @@ export const colorMap = { whitesmoke: '#f5f5f5', yellow: '#ffff00', yellowgreen: '#9acd32', -}; +} export function convertColorToNumber(color) { // Convert color to lowercase for easier matching - color = color.toLowerCase(); + color = color.toLowerCase() // If the color is a hex code, convert it to a number if (color[0] === '#') { - return convertHexToNumber(color); + return convertHexToNumber(color) } // If the color is a named color, return the corresponding number if (colorMap[color] !== undefined) { - return convertHexToNumber(colorMap[color]); + return convertHexToNumber(colorMap[color]) } // If the color is not recognized, return null - return -1; + return -1 } export function convertHexToNumber(hex) { // Remove the leading '#' from the hex code - hex = hex.slice(1); + hex = hex.slice(1) // Convert the hex code to a number - return parseInt(hex, 16); + return parseInt(hex, 16) } diff --git a/src/strudel/draw/draw.mjs b/src/strudel/draw/draw.mjs index a429395..e801c2f 100644 --- a/src/strudel/draw/draw.mjs +++ b/src/strudel/draw/draw.mjs @@ -4,120 +4,127 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { Pattern, getTime, State, TimeSpan } from '@strudel/core'; +import { Pattern, getTime, State, TimeSpan } from '../core/index.mjs' export const getDrawContext = (id = 'test-canvas', options) => { - let { contextType = '2d', pixelated = false, pixelRatio = window.devicePixelRatio } = options || {}; - let canvas = document.querySelector('#' + id); + let { + contextType = '2d', + pixelated = false, + pixelRatio = window.devicePixelRatio, + } = options || {} + let canvas = document.querySelector('#' + id) if (!canvas) { - canvas = document.createElement('canvas'); - canvas.id = id; - canvas.width = window.innerWidth * pixelRatio; - canvas.height = window.innerHeight * pixelRatio; - canvas.style = 'pointer-events:none;width:100%;height:100%;position:fixed;top:0;left:0'; - pixelated && (canvas.style.imageRendering = 'pixelated'); - document.body.prepend(canvas); - let timeout; + canvas = document.createElement('canvas') + canvas.id = id + canvas.width = window.innerWidth * pixelRatio + canvas.height = window.innerHeight * pixelRatio + canvas.style = 'pointer-events:none;width:100%;height:100%;position:fixed;top:0;left:0' + pixelated && (canvas.style.imageRendering = 'pixelated') + document.body.prepend(canvas) + let timeout window.addEventListener('resize', () => { - timeout && clearTimeout(timeout); + timeout && clearTimeout(timeout) timeout = setTimeout(() => { - canvas.width = window.innerWidth * pixelRatio; - canvas.height = window.innerHeight * pixelRatio; - }, 200); - }); + canvas.width = window.innerWidth * pixelRatio + canvas.height = window.innerHeight * pixelRatio + }, 200) + }) } - return canvas.getContext(contextType, { willReadFrequently: true }); -}; + return canvas.getContext(contextType, { willReadFrequently: true }) +} -let animationFrames = {}; +let animationFrames = {} function stopAnimationFrame(id) { if (animationFrames[id] !== undefined) { - cancelAnimationFrame(animationFrames[id]); - delete animationFrames[id]; + cancelAnimationFrame(animationFrames[id]) + delete animationFrames[id] } } function stopAllAnimations(replID) { - Object.keys(animationFrames).forEach((id) => (!replID || id.startsWith(replID)) && stopAnimationFrame(id)); + Object.keys(animationFrames).forEach( + (id) => (!replID || id.startsWith(replID)) && stopAnimationFrame(id) + ) } -let memory = {}; +let memory = {} Pattern.prototype.draw = function (fn, options) { if (typeof window === 'undefined') { - return this; + return this } - let { id = 1, lookbehind = 0, lookahead = 0 } = options; - let __t = Math.max(getTime(), 0); - stopAnimationFrame(id); - lookbehind = Math.abs(lookbehind); + let { id = 1, lookbehind = 0, lookahead = 0 } = options + let __t = Math.max(getTime(), 0) + stopAnimationFrame(id) + lookbehind = Math.abs(lookbehind) // init memory, clear future haps of old pattern - memory[id] = (memory[id] || []).filter((h) => !h.isInFuture(__t)); - let newFuture = this.queryArc(__t, __t + lookahead).filter((h) => h.hasOnset()); - memory[id] = memory[id].concat(newFuture); + memory[id] = (memory[id] || []).filter((h) => !h.isInFuture(__t)) + let newFuture = this.queryArc(__t, __t + lookahead).filter((h) => h.hasOnset()) + memory[id] = memory[id].concat(newFuture) - let last; + let last const animate = () => { - const _t = getTime(); - const t = _t + lookahead; + const _t = getTime() + const t = _t + lookahead // filter out haps that are too far in the past - memory[id] = memory[id].filter((h) => h.isInNearPast(lookbehind, _t)); + memory[id] = memory[id].filter((h) => h.isInNearPast(lookbehind, _t)) // begin where we left off in last frame, but max -0.1s (inactive tab throttles to 1fps) - let begin = Math.max(last || t, t - 1 / 10); - const haps = this.queryArc(begin, t).filter((h) => h.hasOnset()); - memory[id] = memory[id].concat(haps); - last = t; // makes sure no haps are missed - fn(memory[id], _t, t, this); - animationFrames[id] = requestAnimationFrame(animate); - }; - animationFrames[id] = requestAnimationFrame(animate); - return this; -}; + let begin = Math.max(last || t, t - 1 / 10) + const haps = this.queryArc(begin, t).filter((h) => h.hasOnset()) + memory[id] = memory[id].concat(haps) + last = t // makes sure no haps are missed + fn(memory[id], _t, t, this) + animationFrames[id] = requestAnimationFrame(animate) + } + animationFrames[id] = requestAnimationFrame(animate) + return this +} export const cleanupDraw = (clearScreen = true, id) => { - const ctx = getDrawContext(); - clearScreen && ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); - stopAllAnimations(id); -}; + const ctx = getDrawContext() + clearScreen && ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height) + stopAllAnimations(id) +} Pattern.prototype.onPaint = function (painter) { return this.withState((state) => { if (!state.controls.painters) { - state.controls.painters = []; + state.controls.painters = [] } - state.controls.painters.push(painter); - }); -}; + state.controls.painters.push(painter) + }) +} Pattern.prototype.getPainters = function () { - let painters = []; - this.queryArc(0, 0, { painters }); - return painters; -}; + let painters = [] + this.queryArc(0, 0, { painters }) + return painters +} // const round = (x) => Math.round(x * 1000) / 1000; // encapsulates starting and stopping animation frames export class Framer { constructor(onFrame, onError) { - this.onFrame = onFrame; - this.onError = onError; + this.onFrame = onFrame + this.onError = onError } start() { - const self = this; + // eslint-disable-next-line @typescript-eslint/no-this-alias + const self = this let frame = requestAnimationFrame(function updateHighlights(time) { try { - self.onFrame(time); + self.onFrame(time) } catch (err) { - self.onError(err); + self.onError(err) } - frame = requestAnimationFrame(updateHighlights); - }); + frame = requestAnimationFrame(updateHighlights) + }) self.cancel = () => { - cancelAnimationFrame(frame); - }; + cancelAnimationFrame(frame) + } } stop() { if (this.cancel) { - this.cancel(); + this.cancel() } } } @@ -126,79 +133,82 @@ export class Framer { // see vite-vanilla-repl-cm6 for an example export class Drawer { constructor(onDraw, drawTime) { - this.visibleHaps = []; - this.lastFrame = null; - this.drawTime = drawTime; - this.painters = []; + this.visibleHaps = [] + this.lastFrame = null + this.drawTime = drawTime + this.painters = [] this.framer = new Framer( () => { if (!this.scheduler) { - console.warn('Drawer: no scheduler'); - return; + console.warn('Drawer: no scheduler') + return } - const lookbehind = Math.abs(this.drawTime[0]); - const lookahead = this.drawTime[1]; + const lookbehind = Math.abs(this.drawTime[0]) + const lookahead = this.drawTime[1] // calculate current frame time (think right side of screen for pianoroll) - const phase = this.scheduler.now() + lookahead; + const phase = this.scheduler.now() + lookahead // first frame just captures the phase if (this.lastFrame === null) { - this.lastFrame = phase; - return; + this.lastFrame = phase + return } // query haps from last frame till now. take last 100ms max - const haps = this.scheduler.pattern.queryArc(Math.max(this.lastFrame, phase - 1 / 10), phase); - this.lastFrame = phase; + const haps = this.scheduler.pattern.queryArc( + Math.max(this.lastFrame, phase - 1 / 10), + phase + ) + this.lastFrame = phase this.visibleHaps = (this.visibleHaps || []) // filter out haps that are too far in the past (think left edge of screen for pianoroll) .filter((h) => h.whole && h.endClipped >= phase - lookbehind - lookahead) // add new haps with onset (think right edge bars scrolling in) - .concat(haps.filter((h) => h.hasOnset())); - const time = phase - lookahead; - onDraw(this.visibleHaps, time, this, this.painters); + .concat(haps.filter((h) => h.hasOnset())) + const time = phase - lookahead + onDraw(this.visibleHaps, time, this, this.painters) }, (err) => { - console.warn('draw error', err); - }, - ); + console.warn('draw error', err) + } + ) } setDrawTime(drawTime) { - this.drawTime = drawTime; + this.drawTime = drawTime } invalidate(scheduler = this.scheduler, t) { if (!scheduler) { - return; + return } // TODO: scheduler.now() seems to move even when it's stopped, this hints at a bug... - t = t ?? scheduler.now(); - this.scheduler = scheduler; - let [_, lookahead] = this.drawTime; + t = t ?? scheduler.now() + this.scheduler = scheduler + let [_, lookahead] = this.drawTime // +0.1 = workaround for weird holes in query.. - const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1]; + const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1] // remove all future haps - this.visibleHaps = this.visibleHaps.filter((h) => h.whole?.begin < t); - this.painters = []; // will get populated by .onPaint calls attached to the pattern + this.visibleHaps = this.visibleHaps.filter((h) => h.whole?.begin < t) + this.painters = [] // will get populated by .onPaint calls attached to the pattern // query future haps - const futureHaps = scheduler.pattern.queryArc(begin, end, { painters: this.painters }); + const futureHaps = scheduler.pattern.queryArc(begin, end, { painters: this.painters }) // append future haps - this.visibleHaps = this.visibleHaps.concat(futureHaps); + this.visibleHaps = this.visibleHaps.concat(futureHaps) } start(scheduler) { - this.scheduler = scheduler; - this.invalidate(); - this.framer.start(); + this.scheduler = scheduler + this.invalidate() + this.framer.start() } stop() { if (this.framer) { - this.framer.stop(); + this.framer.stop() } } } export function getComputedPropertyValue(name) { if (typeof window === 'undefined') { - return '#fff'; + return '#fff' } - return getComputedStyle(document.documentElement).getPropertyValue(name); + return getComputedStyle(document.documentElement).getPropertyValue(name) } let theme = { @@ -210,10 +220,10 @@ let theme = { lineHighlight: '#00000050', gutterBackground: 'transparent', gutterForeground: '#8a919966', -}; +} export function getTheme() { - return theme; + return theme } export function setTheme(_theme) { - theme = _theme; + theme = _theme } diff --git a/src/strudel/draw/index.mjs b/src/strudel/draw/index.mjs index 506c615..a6912b3 100644 --- a/src/strudel/draw/index.mjs +++ b/src/strudel/draw/index.mjs @@ -1,6 +1,6 @@ -export * from './animate.mjs'; -export * from './color.mjs'; -export * from './draw.mjs'; -export * from './pianoroll.mjs'; -export * from './spiral.mjs'; -export * from './pitchwheel.mjs'; +export * from './animate.mjs' +export * from './color.mjs' +export * from './draw.mjs' +export * from './pianoroll.mjs' +export * from './spiral.mjs' +export * from './pitchwheel.mjs' diff --git a/src/strudel/draw/pianoroll.mjs b/src/strudel/draw/pianoroll.mjs index 1cf218f..34e1144 100644 --- a/src/strudel/draw/pianoroll.mjs +++ b/src/strudel/draw/pianoroll.mjs @@ -4,37 +4,37 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { Pattern, noteToMidi, freqToMidi, isPattern } from '@strudel/core'; -import { getTheme, getDrawContext } from './draw.mjs'; +import { Pattern, noteToMidi, freqToMidi, isPattern } from '../core/index.mjs' +import { getTheme, getDrawContext } from './draw.mjs' -const scale = (normalized, min, max) => normalized * (max - min) + min; +const scale = (normalized, min, max) => normalized * (max - min) + min const getValue = (e) => { - let { value } = e; + let { value } = e if (typeof e.value !== 'object') { - value = { value }; + value = { value } } - let { note, n, freq, s } = value; + let { note, n, freq, s } = value if (freq) { - return freqToMidi(freq); + return freqToMidi(freq) } - note = note ?? n; + note = note ?? n if (typeof note === 'string') { try { // TODO: n(run(32)).scale("D:minor") fails when trying to query negative time.. - return noteToMidi(note); + return noteToMidi(note) } catch (err) { // console.warn(`error converting note to midi: ${err}`); // this spams to crazy - return 0; + return 0 } } if (typeof note === 'number') { - return note; + return note } if (s) { - return '_' + s; + return '_' + s } - return value; -}; + return value +} /** * 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 @@ -77,11 +77,19 @@ const getValue = (e) => { */ Pattern.prototype.pianoroll = function (options = {}) { - let { cycles = 4, playhead = 0.5, overscan = 0, hideNegative = false, ctx = getDrawContext(), id = 1 } = 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); + 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({ @@ -89,26 +97,26 @@ Pattern.prototype.pianoroll = function (options = {}) { time, ctx, haps: haps.filter((hap) => inFrame(hap, time)), - }); + }) }, { lookbehind: from - overscan, lookahead: to + overscan, id, - }, - ); - return this; -}; + } + ) + return this +} export function pianoroll(arg) { if (isPattern(arg)) { // Single argument as a pattern // (to support `all(pianoroll)`) - return arg.pianoroll(); + return arg.pianoroll() } // Single argument with option - return function to get the pattern // (to support `all(pianoroll(options))`) - return (pat) => pat.pianoroll(arg); + return (pat) => pat.pianoroll(arg) } export function __pianoroll({ @@ -141,158 +149,159 @@ export function __pianoroll({ ctx, id, } = {}) { - const w = ctx.canvas.width; - const h = ctx.canvas.height; - let from = -cycles * playhead; - let to = cycles * (1 - playhead); + const w = ctx.canvas.width + const h = ctx.canvas.height + let from = -cycles * playhead + let to = cycles * (1 - playhead) if (id) { - haps = haps.filter((hap) => hap.hasTag(id)); + haps = haps.filter((hap) => hap.hasTag(id)) } if (timeframeProp) { - console.warn('timeframe is deprecated! use from/to instead'); - from = 0; - to = timeframeProp; + console.warn('timeframe is deprecated! use from/to instead') + from = 0 + to = timeframeProp } - const timeAxis = vertical ? h : w; - const valueAxis = vertical ? w : h; - let timeRange = vertical ? [timeAxis, 0] : [0, timeAxis]; // pixel range for time - const timeExtent = to - from; // number of seconds that fit inside the canvas frame - const valueRange = vertical ? [0, valueAxis] : [valueAxis, 0]; // pixel range for values - let valueExtent = maxMidi - minMidi + 1; // number of "slots" for values, overwritten if autorange true - let barThickness = valueAxis / valueExtent; // pixels per value, overwritten if autorange true - let foldValues = []; - flipTime && timeRange.reverse(); - flipValues && valueRange.reverse(); + const timeAxis = vertical ? h : w + const valueAxis = vertical ? w : h + let timeRange = vertical ? [timeAxis, 0] : [0, timeAxis] // pixel range for time + const timeExtent = to - from // number of seconds that fit inside the canvas frame + const valueRange = vertical ? [0, valueAxis] : [valueAxis, 0] // pixel range for values + let valueExtent = maxMidi - minMidi + 1 // number of "slots" for values, overwritten if autorange true + let barThickness = valueAxis / valueExtent // pixels per value, overwritten if autorange true + let foldValues = [] + flipTime && timeRange.reverse() + flipValues && valueRange.reverse() // onQuery const { min, max, values } = haps.reduce( ({ min, max, values }, e) => { - const v = getValue(e); + const v = getValue(e) return { min: v < min ? v : min, max: v > max ? v : max, values: values.includes(v) ? values : [...values, v], - }; + } }, - { min: Infinity, max: -Infinity, values: [] }, - ); + { min: Infinity, max: -Infinity, values: [] } + ) if (autorange) { - minMidi = min; - maxMidi = max; - valueExtent = maxMidi - minMidi + 1; + minMidi = min + maxMidi = max + valueExtent = maxMidi - minMidi + 1 } foldValues = values.sort((a, b) => typeof a === 'number' && typeof b === 'number' ? a - b : typeof a === 'number' ? 1 - : String(a).localeCompare(String(b)), - ); - barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent; - ctx.fillStyle = background; - ctx.globalAlpha = 1; // reset! + : String(a).localeCompare(String(b)) + ) + barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent + ctx.fillStyle = background + ctx.globalAlpha = 1 // reset! if (!smear) { - ctx.clearRect(0, 0, w, h); - ctx.fillRect(0, 0, w, h); + ctx.clearRect(0, 0, w, h) + ctx.fillRect(0, 0, w, h) } haps.forEach((event) => { - const isActive = event.whole.begin <= time && event.endClipped > time; - let strokeCurrent = stroke ?? (strokeActive && isActive); - let fillCurrent = (!isActive && fill) || (isActive && fillActive); + const isActive = event.whole.begin <= time && event.endClipped > time + let strokeCurrent = stroke ?? (strokeActive && isActive) + let fillCurrent = (!isActive && fill) || (isActive && fillActive) if (hideInactive && !isActive) { - return; + return } - let color = event.value?.color; - active = color || active; - inactive = colorizeInactive ? color || inactive : inactive; - color = isActive ? active : inactive; - ctx.fillStyle = fillCurrent ? color : 'transparent'; - ctx.strokeStyle = color; - const { velocity = 1, gain = 1 } = event.value || {}; - ctx.globalAlpha = velocity * gain; - const timeProgress = (event.whole.begin - (flipTime ? to : from)) / timeExtent; - const timePx = scale(timeProgress, ...timeRange); - let durationPx = scale(event.duration / timeExtent, 0, timeAxis); - const value = getValue(event); + let color = event.value?.color + active = color || active + inactive = colorizeInactive ? color || inactive : inactive + color = isActive ? active : inactive + ctx.fillStyle = fillCurrent ? color : 'transparent' + ctx.strokeStyle = color + const { velocity = 1, gain = 1 } = event.value || {} + ctx.globalAlpha = velocity * gain + const timeProgress = (event.whole.begin - (flipTime ? to : from)) / timeExtent + const timePx = scale(timeProgress, ...timeRange) + let durationPx = scale(event.duration / timeExtent, 0, timeAxis) + const value = getValue(event) const valueProgress = fold ? foldValues.indexOf(value) / foldValues.length - : (Number(value) - minMidi) / valueExtent; - const valuePx = scale(valueProgress, ...valueRange); - let margin = 0; - const offset = scale(time / timeExtent, ...timeRange); - let coords; + : (Number(value) - minMidi) / valueExtent + const valuePx = scale(valueProgress, ...valueRange) + let margin = 0 + const offset = scale(time / timeExtent, ...timeRange) + let coords if (vertical) { coords = [ valuePx + 1 - (flipValues ? barThickness : 0), // x timeAxis - offset + timePx + margin + 1 - (flipTime ? 0 : durationPx), // y barThickness - 2, // width durationPx - 2, // height - ]; + ] } else { coords = [ timePx - offset + margin + 1 - (flipTime ? durationPx : 0), // x valuePx + 1 - (flipValues ? 0 : barThickness), // y durationPx - 2, // widith barThickness - 2, // height - ]; + ] } /* const xFactor = Math.sin(performance.now() / 500) + 1; coords[0] *= xFactor; */ if (strokeCurrent) { - ctx.strokeRect(...coords); + ctx.strokeRect(...coords) } if (fillCurrent) { - ctx.fillRect(...coords); + ctx.fillRect(...coords) } //ctx.ellipse(...ellipseFromRect(...coords)) if (labels) { - const defaultLabel = event.value.note ?? event.value.s + (event.value.n ? `:${event.value.n}` : ''); - const { label: inactiveLabel, activeLabel } = event.value; - const customLabel = isActive ? activeLabel || inactiveLabel : inactiveLabel; - const label = customLabel ?? defaultLabel; - let measure = vertical ? durationPx : barThickness * 0.75; - ctx.font = `${measure}px ${fontFamily || 'monospace'}`; + const defaultLabel = + event.value.note ?? event.value.s + (event.value.n ? `:${event.value.n}` : '') + const { label: inactiveLabel, activeLabel } = event.value + const customLabel = isActive ? activeLabel || inactiveLabel : inactiveLabel + const label = customLabel ?? defaultLabel + let measure = vertical ? durationPx : barThickness * 0.75 + ctx.font = `${measure}px ${fontFamily || 'monospace'}` // font color - ctx.fillStyle = /* isActive && */ !fillCurrent ? color : 'black'; - ctx.textBaseline = 'top'; - ctx.fillText(label, ...coords); + ctx.fillStyle = /* isActive && */ !fillCurrent ? color : 'black' + ctx.textBaseline = 'top' + ctx.fillText(label, ...coords) } - }); - ctx.globalAlpha = 1; // reset! - const playheadPosition = scale(-from / timeExtent, ...timeRange); + }) + ctx.globalAlpha = 1 // reset! + const playheadPosition = scale(-from / timeExtent, ...timeRange) // draw playhead - ctx.strokeStyle = playheadColor; - ctx.beginPath(); + ctx.strokeStyle = playheadColor + ctx.beginPath() if (vertical) { - ctx.moveTo(0, playheadPosition); - ctx.lineTo(valueAxis, playheadPosition); + ctx.moveTo(0, playheadPosition) + ctx.lineTo(valueAxis, playheadPosition) } else { - ctx.moveTo(playheadPosition, 0); - ctx.lineTo(playheadPosition, valueAxis); + ctx.moveTo(playheadPosition, 0) + ctx.lineTo(playheadPosition, valueAxis) } - ctx.stroke(); - return this; + ctx.stroke() + return this } export function getDrawOptions(drawTime, options = {}) { - let [lookbehind, lookahead] = drawTime; - lookbehind = Math.abs(lookbehind); - const cycles = lookahead + lookbehind; - const playhead = cycles !== 0 ? lookbehind / cycles : 0; - return { fold: 1, ...options, cycles, playhead }; + let [lookbehind, lookahead] = drawTime + lookbehind = Math.abs(lookbehind) + const cycles = lookahead + lookbehind + const playhead = cycles !== 0 ? lookbehind / cycles : 0 + return { fold: 1, ...options, cycles, playhead } } 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)); -}; + return this.onPaint(getPunchcardPainter(options)) +} /** * Displays a vertical pianoroll with event labels. @@ -301,8 +310,15 @@ Pattern.prototype.punchcard = function (options) { * @name wordfall */ Pattern.prototype.wordfall = function (options) { - return this.punchcard({ vertical: 1, labels: 1, stroke: 0, fillActive: 1, active: 'white', ...options }); -}; + return this.punchcard({ + vertical: 1, + labels: 1, + stroke: 0, + fillActive: 1, + active: 'white', + ...options, + }) +} /* Pattern.prototype.pianoroll = function (options) { return this.onPaint((ctx, time, haps, drawTime) => @@ -311,6 +327,6 @@ Pattern.prototype.wordfall = function (options) { }; */ export function drawPianoroll(options) { - const { drawTime, ...rest } = options; - __pianoroll({ ...getDrawOptions(drawTime), ...rest }); + const { drawTime, ...rest } = options + __pianoroll({ ...getDrawOptions(drawTime), ...rest }) } diff --git a/src/strudel/draw/pitchwheel.mjs b/src/strudel/draw/pitchwheel.mjs index ba76df0..99dff9c 100644 --- a/src/strudel/draw/pitchwheel.mjs +++ b/src/strudel/draw/pitchwheel.mjs @@ -1,18 +1,18 @@ -import { Pattern, midiToFreq, getFrequency } from '@strudel/core'; -import { getTheme, getDrawContext } from './draw.mjs'; +import { Pattern, midiToFreq, getFrequency } from '../core/index.mjs' +import { getTheme, getDrawContext } from './draw.mjs' -const c = midiToFreq(36); +const c = midiToFreq(36) const circlePos = (cx, cy, radius, angle) => { - angle = angle * Math.PI * 2; - const x = Math.sin(angle) * radius + cx; - const y = Math.cos(angle) * radius + cy; - return [x, y]; -}; + angle = angle * Math.PI * 2 + const x = Math.sin(angle) * radius + cx + const y = Math.cos(angle) * radius + cy + return [x, y] +} const freq2angle = (freq, root) => { - return 0.5 - (Math.log2(freq / root) % 1); -}; + return 0.5 - (Math.log2(freq / root) % 1) +} export function pitchwheel({ haps, @@ -27,90 +27,90 @@ export function pitchwheel({ mode = 'flake', margin = 10, } = {}) { - const connectdots = mode === 'polygon'; - const centerlines = mode === 'flake'; - const w = ctx.canvas.width; - const h = ctx.canvas.height; - ctx.clearRect(0, 0, w, h); - const color = getTheme().foreground; + const connectdots = mode === 'polygon' + const centerlines = mode === 'flake' + const w = ctx.canvas.width + const h = ctx.canvas.height + ctx.clearRect(0, 0, w, h) + const color = getTheme().foreground - const size = Math.min(w, h); - const radius = size / 2 - thickness / 2 - hapRadius - margin; - const centerX = w / 2; - const centerY = h / 2; + const size = Math.min(w, h) + const radius = size / 2 - thickness / 2 - hapRadius - margin + const centerX = w / 2 + const centerY = h / 2 if (id) { - haps = haps.filter((hap) => hap.hasTag(id)); + haps = haps.filter((hap) => hap.hasTag(id)) } - ctx.strokeStyle = color; - ctx.fillStyle = color; - ctx.globalAlpha = 1; - ctx.lineWidth = thickness; + ctx.strokeStyle = color + ctx.fillStyle = color + ctx.globalAlpha = 1 + ctx.lineWidth = thickness if (circle) { - ctx.beginPath(); - ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); - ctx.stroke(); + ctx.beginPath() + ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI) + ctx.stroke() } if (edo) { Array.from({ length: edo }, (_, i) => { - const angle = freq2angle(root * Math.pow(2, i / edo), root); - const [x, y] = circlePos(centerX, centerY, radius, angle); - ctx.beginPath(); - ctx.arc(x, y, hapRadius, 0, 2 * Math.PI); - ctx.fill(); - }); - ctx.stroke(); + const angle = freq2angle(root * Math.pow(2, i / edo), root) + const [x, y] = circlePos(centerX, centerY, radius, angle) + ctx.beginPath() + ctx.arc(x, y, hapRadius, 0, 2 * Math.PI) + ctx.fill() + }) + ctx.stroke() } - let shape = []; - ctx.lineWidth = hapRadius; + let shape = [] + ctx.lineWidth = hapRadius haps.forEach((hap) => { - let freq; + let freq try { - freq = getFrequency(hap); + freq = getFrequency(hap) } catch (err) { - return; + return } - const angle = freq2angle(freq, root); - const [x, y] = circlePos(centerX, centerY, radius, angle); - const hapColor = hap.value.color || color; - ctx.strokeStyle = hapColor; - ctx.fillStyle = hapColor; - const { velocity = 1, gain = 1 } = hap.value || {}; - const alpha = velocity * gain; - ctx.globalAlpha = alpha; - shape.push([x, y, angle, hapColor, alpha]); - ctx.beginPath(); + const angle = freq2angle(freq, root) + const [x, y] = circlePos(centerX, centerY, radius, angle) + const hapColor = hap.value.color || color + ctx.strokeStyle = hapColor + ctx.fillStyle = hapColor + const { velocity = 1, gain = 1 } = hap.value || {} + const alpha = velocity * gain + ctx.globalAlpha = alpha + shape.push([x, y, angle, hapColor, alpha]) + ctx.beginPath() if (hapcircles) { - ctx.moveTo(x + hapRadius, y); - ctx.arc(x, y, hapRadius, 0, 2 * Math.PI); - ctx.fill(); + ctx.moveTo(x + hapRadius, y) + ctx.arc(x, y, hapRadius, 0, 2 * Math.PI) + ctx.fill() } if (centerlines) { - ctx.moveTo(centerX, centerY); - ctx.lineTo(x, y); + ctx.moveTo(centerX, centerY) + ctx.lineTo(x, y) } - ctx.stroke(); - }); + ctx.stroke() + }) - ctx.strokeStyle = color; - ctx.globalAlpha = 1; + ctx.strokeStyle = color + ctx.globalAlpha = 1 if (connectdots && shape.length) { - shape = shape.sort((a, b) => a[2] - b[2]); - ctx.beginPath(); - ctx.moveTo(shape[0][0], shape[0][1]); + shape = shape.sort((a, b) => a[2] - b[2]) + ctx.beginPath() + ctx.moveTo(shape[0][0], shape[0][1]) shape.forEach(([x, y, _, color, alpha]) => { - ctx.strokeStyle = color; - ctx.globalAlpha = alpha; - ctx.lineTo(x, y); - }); - ctx.lineTo(shape[0][0], shape[0][1]); - ctx.stroke(); + ctx.strokeStyle = color + ctx.globalAlpha = alpha + ctx.lineTo(x, y) + }) + ctx.lineTo(shape[0][0], shape[0][1]) + ctx.stroke() } - return; + return } /** @@ -131,7 +131,7 @@ export function pitchwheel({ * ._pitchwheel() */ Pattern.prototype.pitchwheel = function (options = {}) { - let { ctx = getDrawContext(), id = 1 } = options; + let { ctx = getDrawContext(), id = 1 } = options return this.tag(id).onPaint((_, time, haps) => pitchwheel({ ...options, @@ -139,6 +139,6 @@ Pattern.prototype.pitchwheel = function (options = {}) { ctx, haps: haps.filter((hap) => hap.isActive(time)), id, - }), - ); -}; + }) + ) +} diff --git a/src/strudel/draw/spiral.mjs b/src/strudel/draw/spiral.mjs index cebf3d3..8059255 100644 --- a/src/strudel/draw/spiral.mjs +++ b/src/strudel/draw/spiral.mjs @@ -1,13 +1,14 @@ -import { Pattern } from '@strudel/core'; -import { getTheme } from './draw.mjs'; +import { Pattern } from '../core/index.mjs' +import { getTheme } from './draw.mjs' // polar coords -> xy function fromPolar(angle, radius, cx, cy) { - const radians = ((angle - 90) * Math.PI) / 180; - return [cx + Math.cos(radians) * radius, cy + Math.sin(radians) * radius]; + const radians = ((angle - 90) * Math.PI) / 180 + return [cx + Math.cos(radians) * radius, cy + Math.sin(radians) * radius] } -const xyOnSpiral = (angle, margin, cx, cy, rotate = 0) => fromPolar((angle + rotate) * 360, margin * angle, cx, cy); // TODO: logSpiral +const xyOnSpiral = (angle, margin, cx, cy, rotate = 0) => + fromPolar((angle + rotate) * 360, margin * angle, cx, cy) // TODO: logSpiral // draw spiral / segment of spiral function spiralSegment(options) { @@ -25,29 +26,29 @@ function spiralSegment(options) { stretch = 1, fromOpacity = 1, toOpacity = 1, - } = options; - from *= stretch; - to *= stretch; - rotate *= stretch; - ctx.lineWidth = thickness; - ctx.lineCap = cap; - ctx.strokeStyle = color; - ctx.globalAlpha = fromOpacity; + } = options + from *= stretch + to *= stretch + rotate *= stretch + ctx.lineWidth = thickness + ctx.lineCap = cap + ctx.strokeStyle = color + ctx.globalAlpha = fromOpacity - ctx.beginPath(); - let [sx, sy] = xyOnSpiral(from, margin, cx, cy, rotate); - ctx.moveTo(sx, sy); + ctx.beginPath() + let [sx, sy] = xyOnSpiral(from, margin, cx, cy, rotate) + ctx.moveTo(sx, sy) - const increment = 1 / 60; - let angle = from; + const increment = 1 / 60 + let angle = from while (angle <= to) { - const [x, y] = xyOnSpiral(angle, margin, cx, cy, rotate); + const [x, y] = xyOnSpiral(angle, margin, cx, cy, rotate) //ctx.lineWidth = angle*thickness; - ctx.globalAlpha = ((angle - from) / (to - from)) * toOpacity; - ctx.lineTo(x, y); - angle += increment; + ctx.globalAlpha = ((angle - from) / (to - from)) * toOpacity + ctx.lineTo(x, y) + angle += increment } - ctx.stroke(); + ctx.stroke() } function drawSpiral(options) { @@ -72,15 +73,15 @@ function drawSpiral(options) { haps, drawTime, id, - } = options; + } = options if (id) { - haps = haps.filter((hap) => hap.hasTag(id)); + haps = haps.filter((hap) => hap.hasTag(id)) } - const [w, h] = [ctx.canvas.width, ctx.canvas.height]; - ctx.clearRect(0, 0, w * 2, h * 2); - const [cx, cy] = [w / 2, h / 2]; + const [w, h] = [ctx.canvas.width, ctx.canvas.height] + ctx.clearRect(0, 0, w * 2, h * 2) + const [cx, cy] = [w / 2, h / 2] const settings = { margin: size / stretch, cx, @@ -88,7 +89,7 @@ function drawSpiral(options) { stretch, cap, thickness, - }; + } const playhead = { ...settings, @@ -96,17 +97,17 @@ function drawSpiral(options) { from: inset - playheadLength, to: inset, color: playheadColor, - }; + } - const [min] = drawTime; - const rotate = steady * time; + const [min] = drawTime + const rotate = steady * time haps.forEach((hap) => { - const isActive = hap.whole.begin <= time && hap.endClipped > time; - const from = hap.whole.begin - time + inset; - const to = hap.endClipped - time + inset - padding; - const hapColor = hap.value?.color || activeColor; - const color = colorizeInactive || isActive ? hapColor : inactiveColor; - const opacity = fade ? 1 - Math.abs((hap.whole.begin - time) / min) : 1; + const isActive = hap.whole.begin <= time && hap.endClipped > time + const from = hap.whole.begin - time + inset + const to = hap.endClipped - time + inset - padding + const hapColor = hap.value?.color || activeColor + const color = colorizeInactive || isActive ? hapColor : inactiveColor + const opacity = fade ? 1 - Math.abs((hap.whole.begin - time) / min) : 1 spiralSegment({ ctx, ...settings, @@ -116,13 +117,13 @@ function drawSpiral(options) { color, fromOpacity: opacity, toOpacity: opacity, - }); - }); + }) + }) spiralSegment({ ctx, ...playhead, rotate, - }); + }) } /** @@ -153,5 +154,7 @@ function drawSpiral(options) { * ._spiral({ steady: .96 }) */ Pattern.prototype.spiral = function (options = {}) { - return this.onPaint((ctx, time, haps, drawTime) => drawSpiral({ ctx, time, haps, drawTime, ...options })); -}; + return this.onPaint((ctx, time, haps, drawTime) => + drawSpiral({ ctx, time, haps, drawTime, ...options }) + ) +} diff --git a/src/strudel/init.js b/src/strudel/init.js new file mode 100644 index 0000000..62b1456 --- /dev/null +++ b/src/strudel/init.js @@ -0,0 +1,45 @@ +import * as core from './core/index.mjs' +import * as mini from './mini/index.mjs' +import * as tonal from './tonal/index.mjs' +import * as webaudio from './webaudio/index.mjs' +import * as transpiler from './transpiler/index.mjs' +import * as draw from './draw/index.mjs' +import * as midi from './midi/index.mjs' +import * as soundfonts from './soundfonts/index.mjs' +import * as superdough from './superdough/index.mjs' +import * as supradough from './supradough/index.mjs' +import * as xen from './xen/index.mjs' + +import { Pattern } from './core/pattern.mjs' +// import { evalScope, setTime } from './core/clockworker.js' +import { initAudioOnFirstClick, webaudioRepl } from './webaudio/index.mjs' +// import { registerSoundfonts } from './soundfonts/index.mjs'; +import { evaluate as _evaluate } from './transpiler/index.mjs' +import { miniAllStrings } from './mini/index.mjs' + +export async function initStrudel(options = {}) { + initAudioOnFirstClick() + options.miniAllStrings !== false && miniAllStrings() + const repl = webaudioRepl({ ...options, transpiler: transpiler.transpiler }) + + await superdough.samples('github:tidalcycles/dirt-samples') + + superdough.registerSynthSounds() + soundfonts.registerSoundfonts() + + core.evalScope(core, mini, tonal, webaudio) + + setTimeout(() => repl.scheduler.now()) + + // Pattern.prototype.play = function () { + // repl.setPattern(this, true) + // return this + // } + + return repl +} + +export function recalculateMiniLocations(code) { + const { miniLocations } = transpiler.transpiler(code) + return miniLocations +} diff --git a/src/strudel/midi/index.mjs b/src/strudel/midi/index.mjs index 399227f..967a589 100644 --- a/src/strudel/midi/index.mjs +++ b/src/strudel/midi/index.mjs @@ -1,3 +1,3 @@ -import './midi.mjs'; +import './midi.mjs' -export * from './midi.mjs'; +export * from './midi.mjs' diff --git a/src/strudel/midi/midi.mjs b/src/strudel/midi/midi.mjs index ae983d2..b6be36d 100644 --- a/src/strudel/midi/midi.mjs +++ b/src/strudel/midi/midi.mjs @@ -4,121 +4,121 @@ Copyright (C) 2022 Strudel contributors - see . */ -import * as _WebMidi from 'webmidi'; -import { Pattern, getEventOffsetMs, isPattern, logger, ref } from '@strudel/core'; -import { noteToMidi, getControlName } from '@strudel/core'; -import { Note } from 'webmidi'; +import * as _WebMidi from 'webmidi' +import { Pattern, getEventOffsetMs, isPattern, logger, ref } from '../core/index.mjs' +import { noteToMidi, getControlName } from '../core/index.mjs' +import { Note } from 'webmidi' // if you use WebMidi from outside of this package, make sure to import that instance: -export const { WebMidi } = _WebMidi; +export const { WebMidi } = _WebMidi function supportsMidi() { - return typeof navigator.requestMIDIAccess === 'function'; + return typeof navigator.requestMIDIAccess === 'function' } function getMidiDeviceNamesString(devices) { - return devices.map((o) => `'${o.name}'`).join(' | '); + return devices.map((o) => `'${o.name}'`).join(' | ') } export function enableWebMidi(options = {}) { - const { onReady, onConnected, onDisconnected, onEnabled } = options; + const { onReady, onConnected, onDisconnected, onEnabled } = options if (WebMidi.enabled) { - return; + return } if (!supportsMidi()) { - throw new Error('Your Browser does not support WebMIDI.'); + throw new Error('Your Browser does not support WebMIDI.') } WebMidi.addListener('connected', () => { - onConnected?.(WebMidi); - }); + onConnected?.(WebMidi) + }) WebMidi.addListener('enabled', () => { - onEnabled?.(WebMidi); - }); + onEnabled?.(WebMidi) + }) // Reacting when a device becomes unavailable WebMidi.addListener('disconnected', (e) => { - onDisconnected?.(WebMidi, e); - }); + onDisconnected?.(WebMidi, e) + }) return new Promise((resolve, reject) => { if (WebMidi.enabled) { // if already enabled, just resolve WebMidi - resolve(WebMidi); - return; + resolve(WebMidi) + return } WebMidi.enable( (err) => { if (err) { - reject(err); + reject(err) } - onReady?.(WebMidi); - resolve(WebMidi); + onReady?.(WebMidi) + resolve(WebMidi) }, - { sysex: true }, - ); - }); + { sysex: true } + ) + }) } function getDevice(indexOrName, devices) { if (!devices.length) { - throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`); + throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`) } if (typeof indexOrName === 'number') { - return devices[indexOrName]; + return devices[indexOrName] } - const byName = (name) => devices.find((output) => output.name.includes(name)); + const byName = (name) => devices.find((output) => output.name.includes(name)) if (typeof indexOrName === 'string') { - return byName(indexOrName); + return byName(indexOrName) } // attempt to default to first IAC device if none is specified - const IACOutput = byName('IAC'); - const device = IACOutput ?? devices[0]; + const IACOutput = byName('IAC') + const device = IACOutput ?? devices[0] if (!device) { throw new Error( - `🔌 MIDI device '${device ? device : ''}' not found. Use one of ${getMidiDeviceNamesString(devices)}`, - ); + `🔌 MIDI device '${device ? device : ''}' not found. Use one of ${getMidiDeviceNamesString(devices)}` + ) } - return IACOutput ?? devices[0]; + return IACOutput ?? devices[0] } // send start/stop messages to outputs when repl starts/stops if (typeof window !== 'undefined') { window.addEventListener('message', (e) => { if (!WebMidi?.enabled) { - return; + return } if (e.data === 'strudel-stop') { - WebMidi.outputs.forEach((output) => output.sendStop()); + WebMidi.outputs.forEach((output) => output.sendStop()) } // cannot start here, since we have no timing info, see sendStart below - }); + }) } // registry for midi mappings, converting control names to cc messages -export const midicontrolMap = new Map(); +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 }; + mapping = { ccn: mapping } } - return [getControlName(key), mapping]; - }), - ); + return [getControlName(key), mapping] + }) + ) } function githubPath(base, subpath = '') { if (!base.startsWith('github:')) { - throw new Error('expected "github:" at the start of pseudoUrl'); + throw new Error('expected "github:" at the start of pseudoUrl') } - let [_, path] = base.split('github:'); - path = path.endsWith('/') ? path.slice(0, -1) : path; + 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'; + path += '/main' } - return `https://raw.githubusercontent.com/${path}/${subpath}`; + return `https://raw.githubusercontent.com/${path}/${subpath}` } /** @@ -129,10 +129,10 @@ function githubPath(base, subpath = '') { * $: lpf(sine.slow(4).segment(16)).midi(); */ export function defaultmidimap(mapping) { - midicontrolMap.set('default', unifyMapping(mapping)); + midicontrolMap.set('default', unifyMapping(mapping)) } -let loadCache = {}; +let loadCache = {} /** * Adds midimaps to the registry. Inside each midimap, control names (e.g. lpf) are mapped to cc numbers. @@ -154,126 +154,128 @@ let loadCache = {}; export async function midimaps(map) { if (typeof map === 'string') { if (map.startsWith('github:')) { - map = githubPath(map, 'midimap.json'); + map = githubPath(map, 'midimap.json') } if (!loadCache[map]) { - loadCache[map] = fetch(map).then((res) => res.json()); + loadCache[map] = fetch(map).then((res) => res.json()) } - map = await loadCache[map]; + map = await loadCache[map] } if (typeof map === 'object') { - Object.entries(map).forEach(([name, mapping]) => midicontrolMap.set(name, unifyMapping(mapping))); + 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(); +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'); + 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); + 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 }; - }); + 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'); + 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'); + 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 }); + 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'); + throw new Error('expected progNum (program change) to be a number between 0 and 127') } - device.sendProgramChange(progNum, midichan, { time: timeOffsetString }); + 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'); + 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'); + 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)'); + 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'); + throw new Error('all sysex bytes must be integers between 0 and 255') } - device.sendSysex(sysexid, sysexdata, { time: timeOffsetString }); + 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'); + 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'); + 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 }); + 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'); + throw new Error('expected midibend to be a number between -1 and 1') } - device.sendPitchBend(midibend, midichan, { time: timeOffsetString }); + 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'); + throw new Error('expected miditouch to be a number between 0 and 1') } - device.sendChannelAftertouch(miditouch, midichan, { time: timeOffsetString }); + 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'); + 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'); + 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'); + 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 }); + const midiNumber = typeof note === 'number' ? note : noteToMidi(note) + const midiNote = new Note(midiNumber, { attack: velocity, duration }) device.playNote(midiNote, midichan, { time: timeOffsetString, - }); + }) } /** @@ -291,19 +293,19 @@ Pattern.prototype.midi = function (midiport, options = {}) { throw new Error( `.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; + const { port, isController = false, ...configOptions } = midiport options = { isController, ...configOptions, ...options, // Keep any options passed separately - }; - midiport = port; + } + midiport = port } let midiConfig = { @@ -317,33 +319,33 @@ Pattern.prototype.midi = function (midiport, options = {}) { 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(midiConfig.midiport, outputs); - const otherOutputs = outputs.filter((o) => o.name !== device.name); + const device = getDevice(midiConfig.midiport, outputs) + const otherOutputs = outputs.filter((o) => o.name !== device.name) logger( `Midi enabled! Using "${device.name}". ${ otherOutputs?.length ? `Also available: ${getMidiDeviceNamesString(otherOutputs)}` : '' - }`, - ); + }` + ) }, onDisconnected: ({ outputs }) => logger(`Midi device disconnected! Available: ${getMidiDeviceNamesString(outputs)}`), - }); + }) return this.onTrigger((hap, currentTime, cps, targetTime) => { if (!WebMidi.enabled) { - logger('Midi not enabled'); - return; + logger('Midi not enabled') + return } - hap.ensureObjectValue(); + hap.ensureObjectValue() //magic number to get audio engine to line up, can probably be calculated somehow - const latencyMs = midiConfig.latencyMs; + 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}`; + const timeOffsetString = `+${getEventOffsetMs(targetTime, currentTime) + latencyMs}` // midi event values from hap with configurable defaults let { @@ -364,40 +366,42 @@ Pattern.prototype.midi = function (midiport, options = {}) { sysexdata, midimap = midiConfig.midimap, midiport = midiConfig.midiport, - } = hap.value; + } = hap.value - const device = getDevice(midiport, WebMidi.outputs); + const device = getDevice(midiport, WebMidi.outputs) if (!device) { logger( - `[midi] midiport "${midiport}" not found! available: ${WebMidi.outputs.map((output) => `'${output.name}'`).join(', ')}`, - ); - return; + `[midi] midiport "${midiport}" not found! available: ${WebMidi.outputs.map((output) => `'${output.name}'`).join(', ')}` + ) + return } - velocity = gain * velocity; + velocity = gain * velocity // 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)); + 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(', ')}`); + 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; + const duration = (hap.duration.valueOf() / cps) * 1000 - midiConfig.noteOffsetMs - sendNote(note, velocity, duration, device, midichan, timeOffsetString); + sendNote(note, velocity, duration, device, midichan, timeOffsetString) } // Handle program change if (progNum !== undefined) { - sendProgramChange(progNum, device, midichan, timeOffsetString); + sendProgramChange(progNum, device, midichan, timeOffsetString) } // Handle sysex @@ -407,61 +411,61 @@ Pattern.prototype.midi = function (midiport, options = {}) { // if sysexid is an array the first byte is 0x00 if (sysexid !== undefined && sysexdata !== undefined) { - sendSysex(sysexid, sysexdata, device, timeOffsetString); + sendSysex(sysexid, sysexdata, device, timeOffsetString) } // Handle control change if (ccv !== undefined && ccn !== undefined) { - sendCC(ccn, ccv, device, midichan, timeOffsetString); + sendCC(ccn, ccv, device, midichan, timeOffsetString) } // Handle NRPN non-registered parameter number if (nrpnn !== undefined && nrpv !== undefined) { - sendNRPN(nrpnn, nrpv, device, midichan, timeOffsetString); + sendNRPN(nrpnn, nrpv, device, midichan, timeOffsetString) } // Handle midibend if (midibend !== undefined) { - sendPitchBend(midibend, device, midichan, timeOffsetString); + sendPitchBend(midibend, device, midichan, timeOffsetString) } // Handle miditouch if (miditouch !== undefined) { - sendAftertouch(miditouch, device, midichan, timeOffsetString); + 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 }); + device.sendStart({ time: timeOffsetString }) } if (['clock', 'midiClock'].includes(midicmd)) { - device.sendClock({ time: timeOffsetString }); + device.sendClock({ time: timeOffsetString }) } else if (['start'].includes(midicmd)) { - device.sendStart({ time: timeOffsetString }); + device.sendStart({ time: timeOffsetString }) } else if (['stop'].includes(midicmd)) { - device.sendStop({ time: timeOffsetString }); + device.sendStop({ time: timeOffsetString }) } else if (['continue'].includes(midicmd)) { - device.sendContinue({ time: timeOffsetString }); + device.sendContinue({ time: timeOffsetString }) } else if (Array.isArray(midicmd)) { if (midicmd[0] === 'progNum') { - sendProgramChange(midicmd[1], device, midichan, timeOffsetString); + sendProgramChange(midicmd[1], device, midichan, timeOffsetString) } else if (midicmd[0] === 'cc') { if (midicmd.length === 2) { - sendCC(midicmd[0], midicmd[1] / 127, device, midichan, timeOffsetString); + 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); + const [_, id, data] = midicmd + sendSysex(id, data, device, timeOffsetString) } } } - }); -}; + }) +} -let listeners = {}; -const refs = {}; +let listeners = {} +const refs = {} /** * MIDI input: Opens a MIDI input port to receive MIDI control change messages. @@ -476,36 +480,36 @@ export async function midin(input) { throw new Error( `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); + 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)}`, - ); + `midiin: device "${input}" not found.. connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}` + ) } if (initial) { - const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name); + const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name) logger( `Midi enabled! Using "${device.name}". ${ otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : '' - }`, - ); + }` + ) } // ensure refs for this input are initialized if (!refs[input]) { - refs[input] = {}; + refs[input] = {} } - const cc = (cc) => ref(() => refs[input][cc] || 0); + const cc = (cc) => ref(() => refs[input][cc] || 0) - listeners[input] && device.removeListener('midimessage', listeners[input]); + listeners[input] && device.removeListener('midimessage', listeners[input]) listeners[input] = (e) => { - const cc = e.dataBytes[0]; - const v = e.dataBytes[1]; - refs[input] && (refs[input][cc] = v / 127); - }; - device.addListener('midimessage', listeners[input]); - return cc; + const cc = e.dataBytes[0] + const v = e.dataBytes[1] + refs[input] && (refs[input][cc] = v / 127) + } + device.addListener('midimessage', listeners[input]) + return cc } diff --git a/src/strudel/mini/index.mjs b/src/strudel/mini/index.mjs index d6bfae1..a29b15b 100644 --- a/src/strudel/mini/index.mjs +++ b/src/strudel/mini/index.mjs @@ -1,2 +1,2 @@ -export * from './mini.mjs'; -export * from './krill-parser.js'; +export * from './mini.mjs' +export * from './krill-parser.js' diff --git a/src/strudel/mini/krill-parser.js b/src/strudel/mini/krill-parser.js index 0d855e4..60d0d50 100644 --- a/src/strudel/mini/krill-parser.js +++ b/src/strudel/mini/krill-parser.js @@ -2,493 +2,2992 @@ // // https://peggyjs.org/ - function peg$subclass(child, parent) { - function C() { this.constructor = child; } - C.prototype = parent.prototype; - child.prototype = new C(); + function C() { + this.constructor = child + } + C.prototype = parent.prototype + child.prototype = new C() } function peg$SyntaxError(message, expected, found, location) { - var self = Error.call(this, message); + var self = Error.call(this, message) // istanbul ignore next Check is a necessary evil to support older environments if (Object.setPrototypeOf) { - Object.setPrototypeOf(self, peg$SyntaxError.prototype); + Object.setPrototypeOf(self, peg$SyntaxError.prototype) } - self.expected = expected; - self.found = found; - self.location = location; - self.name = "SyntaxError"; - return self; + self.expected = expected + self.found = found + self.location = location + self.name = 'SyntaxError' + return self } -peg$subclass(peg$SyntaxError, Error); +peg$subclass(peg$SyntaxError, Error) function peg$padEnd(str, targetLength, padString) { - padString = padString || " "; - if (str.length > targetLength) { return str; } - targetLength -= str.length; - padString += padString.repeat(targetLength); - return str + padString.slice(0, targetLength); + padString = padString || ' ' + if (str.length > targetLength) { + return str + } + targetLength -= str.length + padString += padString.repeat(targetLength) + return str + padString.slice(0, targetLength) } -peg$SyntaxError.prototype.format = function(sources) { - var str = "Error: " + this.message; +peg$SyntaxError.prototype.format = function (sources) { + var str = 'Error: ' + this.message if (this.location) { - var src = null; - var k; + var src = null + var k for (k = 0; k < sources.length; k++) { if (sources[k].source === this.location.source) { - src = sources[k].text.split(/\r\n|\n|\r/g); - break; + src = sources[k].text.split(/\r\n|\n|\r/g) + break } } - var s = this.location.start; - var offset_s = (this.location.source && (typeof this.location.source.offset === "function")) - ? this.location.source.offset(s) - : s; - var loc = this.location.source + ":" + offset_s.line + ":" + offset_s.column; + var s = this.location.start + var offset_s = + this.location.source && typeof this.location.source.offset === 'function' + ? this.location.source.offset(s) + : s + var loc = this.location.source + ':' + offset_s.line + ':' + offset_s.column if (src) { - var e = this.location.end; - var filler = peg$padEnd("", offset_s.line.toString().length, ' '); - var line = src[s.line - 1]; - var last = s.line === e.line ? e.column : line.length + 1; - var hatLen = (last - s.column) || 1; - str += "\n --> " + loc + "\n" - + filler + " |\n" - + offset_s.line + " | " + line + "\n" - + filler + " | " + peg$padEnd("", s.column - 1, ' ') - + peg$padEnd("", hatLen, "^"); + var e = this.location.end + var filler = peg$padEnd('', offset_s.line.toString().length, ' ') + var line = src[s.line - 1] + var last = s.line === e.line ? e.column : line.length + 1 + var hatLen = last - s.column || 1 + str += + '\n --> ' + + loc + + '\n' + + filler + + ' |\n' + + offset_s.line + + ' | ' + + line + + '\n' + + filler + + ' | ' + + peg$padEnd('', s.column - 1, ' ') + + peg$padEnd('', hatLen, '^') } else { - str += "\n at " + loc; + str += '\n at ' + loc } } - return str; -}; + return str +} -peg$SyntaxError.buildMessage = function(expected, found) { +peg$SyntaxError.buildMessage = function (expected, found) { var DESCRIBE_EXPECTATION_FNS = { - literal: function(expectation) { - return "\"" + literalEscape(expectation.text) + "\""; + literal: function (expectation) { + return '"' + literalEscape(expectation.text) + '"' }, - class: function(expectation) { - var escapedParts = expectation.parts.map(function(part) { + class: function (expectation) { + var escapedParts = expectation.parts.map(function (part) { return Array.isArray(part) - ? classEscape(part[0]) + "-" + classEscape(part[1]) - : classEscape(part); - }); + ? classEscape(part[0]) + '-' + classEscape(part[1]) + : classEscape(part) + }) - return "[" + (expectation.inverted ? "^" : "") + escapedParts.join("") + "]"; + return '[' + (expectation.inverted ? '^' : '') + escapedParts.join('') + ']' }, - any: function() { - return "any character"; + any: function () { + return 'any character' }, - end: function() { - return "end of input"; + end: function () { + return 'end of input' }, - other: function(expectation) { - return expectation.description; - } - }; + other: function (expectation) { + return expectation.description + }, + } function hex(ch) { - return ch.charCodeAt(0).toString(16).toUpperCase(); + return ch.charCodeAt(0).toString(16).toUpperCase() } function literalEscape(s) { return s - .replace(/\\/g, "\\\\") - .replace(/"/g, "\\\"") - .replace(/\0/g, "\\0") - .replace(/\t/g, "\\t") - .replace(/\n/g, "\\n") - .replace(/\r/g, "\\r") - .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) - .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\0/g, '\\0') + .replace(/\t/g, '\\t') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/[\x00-\x0F]/g, function (ch) { + return '\\x0' + hex(ch) + }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function (ch) { + return '\\x' + hex(ch) + }) } function classEscape(s) { return s - .replace(/\\/g, "\\\\") - .replace(/\]/g, "\\]") - .replace(/\^/g, "\\^") - .replace(/-/g, "\\-") - .replace(/\0/g, "\\0") - .replace(/\t/g, "\\t") - .replace(/\n/g, "\\n") - .replace(/\r/g, "\\r") - .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) - .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + .replace(/\\/g, '\\\\') + .replace(/\]/g, '\\]') + .replace(/\^/g, '\\^') + .replace(/-/g, '\\-') + .replace(/\0/g, '\\0') + .replace(/\t/g, '\\t') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/[\x00-\x0F]/g, function (ch) { + return '\\x0' + hex(ch) + }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function (ch) { + return '\\x' + hex(ch) + }) } function describeExpectation(expectation) { - return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); + return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation) } function describeExpected(expected) { - var descriptions = expected.map(describeExpectation); - var i, j; + var descriptions = expected.map(describeExpectation) + var i, j - descriptions.sort(); + descriptions.sort() if (descriptions.length > 0) { for (i = 1, j = 1; i < descriptions.length; i++) { if (descriptions[i - 1] !== descriptions[i]) { - descriptions[j] = descriptions[i]; - j++; + descriptions[j] = descriptions[i] + j++ } } - descriptions.length = j; + descriptions.length = j } switch (descriptions.length) { case 1: - return descriptions[0]; + return descriptions[0] case 2: - return descriptions[0] + " or " + descriptions[1]; + return descriptions[0] + ' or ' + descriptions[1] default: - return descriptions.slice(0, -1).join(", ") - + ", or " - + descriptions[descriptions.length - 1]; + return ( + descriptions.slice(0, -1).join(', ') + ', or ' + descriptions[descriptions.length - 1] + ) } } function describeFound(found) { - return found ? "\"" + literalEscape(found) + "\"" : "end of input"; + return found ? '"' + literalEscape(found) + '"' : 'end of input' } - return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; -}; + return 'Expected ' + describeExpected(expected) + ' but ' + describeFound(found) + ' found.' +} function peg$parse(input, options) { - options = options !== undefined ? options : {}; + options = options !== undefined ? options : {} - var peg$FAILED = {}; - var peg$source = options.grammarSource; + var peg$FAILED = {} + var peg$source = options.grammarSource - var peg$startRuleFunctions = { start: peg$parsestart }; - var peg$startRuleFunction = peg$parsestart; + var peg$startRuleFunctions = { start: peg$parsestart } + var peg$startRuleFunction = peg$parsestart - var peg$c0 = "."; - var peg$c1 = "-"; - var peg$c2 = "+"; - var peg$c3 = "0"; - var peg$c4 = ","; - var peg$c5 = "|"; - var peg$c6 = "["; - var peg$c7 = "]"; - var peg$c8 = "{"; - var peg$c9 = "}"; - var peg$c10 = "%"; - var peg$c11 = "<"; - var peg$c12 = ">"; - var peg$c13 = "!"; - var peg$c14 = "("; - var peg$c15 = ")"; - var peg$c16 = "/"; - var peg$c17 = "*"; - var peg$c18 = "?"; - var peg$c19 = ":"; - var peg$c20 = ".."; - var peg$c21 = "^"; - var peg$c22 = "struct"; - var peg$c23 = "target"; - var peg$c24 = "euclid"; - var peg$c25 = "slow"; - var peg$c26 = "rotL"; - var peg$c27 = "rotR"; - var peg$c28 = "fast"; - var peg$c29 = "scale"; - var peg$c30 = "//"; - var peg$c31 = "cat"; - var peg$c32 = "$"; - var peg$c33 = "setcps"; - var peg$c34 = "setbpm"; - var peg$c35 = "hush"; + var peg$c0 = '.' + var peg$c1 = '-' + var peg$c2 = '+' + var peg$c3 = '0' + var peg$c4 = ',' + var peg$c5 = '|' + var peg$c6 = '[' + var peg$c7 = ']' + var peg$c8 = '{' + var peg$c9 = '}' + var peg$c10 = '%' + var peg$c11 = '<' + var peg$c12 = '>' + var peg$c13 = '!' + var peg$c14 = '(' + var peg$c15 = ')' + var peg$c16 = '/' + var peg$c17 = '*' + var peg$c18 = '?' + var peg$c19 = ':' + var peg$c20 = '..' + var peg$c21 = '^' + var peg$c22 = 'struct' + var peg$c23 = 'target' + var peg$c24 = 'euclid' + var peg$c25 = 'slow' + var peg$c26 = 'rotL' + var peg$c27 = 'rotR' + var peg$c28 = 'fast' + var peg$c29 = 'scale' + var peg$c30 = '//' + var peg$c31 = 'cat' + var peg$c32 = '$' + var peg$c33 = 'setcps' + var peg$c34 = 'setbpm' + var peg$c35 = 'hush' - var peg$r0 = /^[1-9]/; - var peg$r1 = /^[eE]/; - var peg$r2 = /^[+\-]/; - var peg$r3 = /^[0-9]/; - var peg$r4 = /^[ \n\r\t\xA0]/; - var peg$r5 = /^["']/; - var peg$r6 = /^[#\--.0-9A-Z\^-_a-z~\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376-\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4-\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u09FC\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60-\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0CF1-\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E46\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5-\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEF\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7B9\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD-\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5-\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/; - var peg$r7 = /^[@_]/; - var peg$r8 = /^[^\n]/; - var peg$r9 = /^[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376-\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4-\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u09FC\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60-\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0CF1-\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E46\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5-\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEF\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7B9\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD-\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5-\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/; - var peg$r10 = /^[a-z\xB5\xDF-\xF6\xF8-\xFF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137-\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148-\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C-\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA-\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9-\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC-\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF-\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F-\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02AF\u0371\u0373\u0377\u037B-\u037D\u0390\u03AC-\u03CE\u03D0-\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB-\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE-\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0560-\u0588\u10D0-\u10FA\u10FD-\u10FF\u13F8-\u13FD\u1C80-\u1C88\u1D00-\u1D2B\u1D6B-\u1D77\u1D79-\u1D9A\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6-\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FC7\u1FD0-\u1FD3\u1FD6-\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6-\u1FF7\u210A\u210E-\u210F\u2113\u212F\u2134\u2139\u213C-\u213D\u2146-\u2149\u214E\u2184\u2C30-\u2C5E\u2C61\u2C65-\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73-\u2C74\u2C76-\u2C7B\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3-\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F\uA771-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7AF\uA7B5\uA7B7\uA7B9\uA7FA\uAB30-\uAB5A\uAB60-\uAB65\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A]/; - var peg$r11 = /^[\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0374\u037A\u0559\u0640\u06E5-\u06E6\u07F4-\u07F5\u07FA\u081A\u0824\u0828\u0971\u0E46\u0EC6\u10FC\u17D7\u1843\u1AA7\u1C78-\u1C7D\u1D2C-\u1D6A\u1D78\u1D9B-\u1DBF\u2071\u207F\u2090-\u209C\u2C7C-\u2C7D\u2D6F\u2E2F\u3005\u3031-\u3035\u303B\u309D-\u309E\u30FC-\u30FE\uA015\uA4F8-\uA4FD\uA60C\uA67F\uA69C-\uA69D\uA717-\uA71F\uA770\uA788\uA7F8-\uA7F9\uA9CF\uA9E6\uAA70\uAADD\uAAF3-\uAAF4\uAB5C-\uAB5F\uFF70\uFF9E-\uFF9F]/; - var peg$r12 = /^[\xAA\xBA\u01BB\u01C0-\u01C3\u0294\u05D0-\u05EA\u05EF-\u05F2\u0620-\u063F\u0641-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u0800-\u0815\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0972-\u0980\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u09FC\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60-\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0CF1-\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E45\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17DC\u1820-\u1842\u1844-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C77\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5-\u1CF6\u2135-\u2138\u2D30-\u2D67\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3006\u303C\u3041-\u3096\u309F\u30A1-\u30FA\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEF\uA000-\uA014\uA016-\uA48C\uA4D0-\uA4F7\uA500-\uA60B\uA610-\uA61F\uA62A-\uA62B\uA66E\uA6A0-\uA6E5\uA78F\uA7F7\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD-\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9E0-\uA9E4\uA9E7-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA6F\uAA71-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5-\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADC\uAAE0-\uAAEA\uAAF2\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF66-\uFF6F\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/; - var peg$r13 = /^[\u01C5\u01C8\u01CB\u01F2\u1F88-\u1F8F\u1F98-\u1F9F\u1FA8-\u1FAF\u1FBC\u1FCC\u1FFC]/; - var peg$r14 = /^[A-Z\xC0-\xD6\xD8-\xDE\u0100\u0102\u0104\u0106\u0108\u010A\u010C\u010E\u0110\u0112\u0114\u0116\u0118\u011A\u011C\u011E\u0120\u0122\u0124\u0126\u0128\u012A\u012C\u012E\u0130\u0132\u0134\u0136\u0139\u013B\u013D\u013F\u0141\u0143\u0145\u0147\u014A\u014C\u014E\u0150\u0152\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0162\u0164\u0166\u0168\u016A\u016C\u016E\u0170\u0172\u0174\u0176\u0178-\u0179\u017B\u017D\u0181-\u0182\u0184\u0186-\u0187\u0189-\u018B\u018E-\u0191\u0193-\u0194\u0196-\u0198\u019C-\u019D\u019F-\u01A0\u01A2\u01A4\u01A6-\u01A7\u01A9\u01AC\u01AE-\u01AF\u01B1-\u01B3\u01B5\u01B7-\u01B8\u01BC\u01C4\u01C7\u01CA\u01CD\u01CF\u01D1\u01D3\u01D5\u01D7\u01D9\u01DB\u01DE\u01E0\u01E2\u01E4\u01E6\u01E8\u01EA\u01EC\u01EE\u01F1\u01F4\u01F6-\u01F8\u01FA\u01FC\u01FE\u0200\u0202\u0204\u0206\u0208\u020A\u020C\u020E\u0210\u0212\u0214\u0216\u0218\u021A\u021C\u021E\u0220\u0222\u0224\u0226\u0228\u022A\u022C\u022E\u0230\u0232\u023A-\u023B\u023D-\u023E\u0241\u0243-\u0246\u0248\u024A\u024C\u024E\u0370\u0372\u0376\u037F\u0386\u0388-\u038A\u038C\u038E-\u038F\u0391-\u03A1\u03A3-\u03AB\u03CF\u03D2-\u03D4\u03D8\u03DA\u03DC\u03DE\u03E0\u03E2\u03E4\u03E6\u03E8\u03EA\u03EC\u03EE\u03F4\u03F7\u03F9-\u03FA\u03FD-\u042F\u0460\u0462\u0464\u0466\u0468\u046A\u046C\u046E\u0470\u0472\u0474\u0476\u0478\u047A\u047C\u047E\u0480\u048A\u048C\u048E\u0490\u0492\u0494\u0496\u0498\u049A\u049C\u049E\u04A0\u04A2\u04A4\u04A6\u04A8\u04AA\u04AC\u04AE\u04B0\u04B2\u04B4\u04B6\u04B8\u04BA\u04BC\u04BE\u04C0-\u04C1\u04C3\u04C5\u04C7\u04C9\u04CB\u04CD\u04D0\u04D2\u04D4\u04D6\u04D8\u04DA\u04DC\u04DE\u04E0\u04E2\u04E4\u04E6\u04E8\u04EA\u04EC\u04EE\u04F0\u04F2\u04F4\u04F6\u04F8\u04FA\u04FC\u04FE\u0500\u0502\u0504\u0506\u0508\u050A\u050C\u050E\u0510\u0512\u0514\u0516\u0518\u051A\u051C\u051E\u0520\u0522\u0524\u0526\u0528\u052A\u052C\u052E\u0531-\u0556\u10A0-\u10C5\u10C7\u10CD\u13A0-\u13F5\u1C90-\u1CBA\u1CBD-\u1CBF\u1E00\u1E02\u1E04\u1E06\u1E08\u1E0A\u1E0C\u1E0E\u1E10\u1E12\u1E14\u1E16\u1E18\u1E1A\u1E1C\u1E1E\u1E20\u1E22\u1E24\u1E26\u1E28\u1E2A\u1E2C\u1E2E\u1E30\u1E32\u1E34\u1E36\u1E38\u1E3A\u1E3C\u1E3E\u1E40\u1E42\u1E44\u1E46\u1E48\u1E4A\u1E4C\u1E4E\u1E50\u1E52\u1E54\u1E56\u1E58\u1E5A\u1E5C\u1E5E\u1E60\u1E62\u1E64\u1E66\u1E68\u1E6A\u1E6C\u1E6E\u1E70\u1E72\u1E74\u1E76\u1E78\u1E7A\u1E7C\u1E7E\u1E80\u1E82\u1E84\u1E86\u1E88\u1E8A\u1E8C\u1E8E\u1E90\u1E92\u1E94\u1E9E\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EBE\u1EC0\u1EC2\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u1EF6\u1EF8\u1EFA\u1EFC\u1EFE\u1F08-\u1F0F\u1F18-\u1F1D\u1F28-\u1F2F\u1F38-\u1F3F\u1F48-\u1F4D\u1F59\u1F5B\u1F5D\u1F5F\u1F68-\u1F6F\u1FB8-\u1FBB\u1FC8-\u1FCB\u1FD8-\u1FDB\u1FE8-\u1FEC\u1FF8-\u1FFB\u2102\u2107\u210B-\u210D\u2110-\u2112\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u2130-\u2133\u213E-\u213F\u2145\u2183\u2C00-\u2C2E\u2C60\u2C62-\u2C64\u2C67\u2C69\u2C6B\u2C6D-\u2C70\u2C72\u2C75\u2C7E-\u2C80\u2C82\u2C84\u2C86\u2C88\u2C8A\u2C8C\u2C8E\u2C90\u2C92\u2C94\u2C96\u2C98\u2C9A\u2C9C\u2C9E\u2CA0\u2CA2\u2CA4\u2CA6\u2CA8\u2CAA\u2CAC\u2CAE\u2CB0\u2CB2\u2CB4\u2CB6\u2CB8\u2CBA\u2CBC\u2CBE\u2CC0\u2CC2\u2CC4\u2CC6\u2CC8\u2CCA\u2CCC\u2CCE\u2CD0\u2CD2\u2CD4\u2CD6\u2CD8\u2CDA\u2CDC\u2CDE\u2CE0\u2CE2\u2CEB\u2CED\u2CF2\uA640\uA642\uA644\uA646\uA648\uA64A\uA64C\uA64E\uA650\uA652\uA654\uA656\uA658\uA65A\uA65C\uA65E\uA660\uA662\uA664\uA666\uA668\uA66A\uA66C\uA680\uA682\uA684\uA686\uA688\uA68A\uA68C\uA68E\uA690\uA692\uA694\uA696\uA698\uA69A\uA722\uA724\uA726\uA728\uA72A\uA72C\uA72E\uA732\uA734\uA736\uA738\uA73A\uA73C\uA73E\uA740\uA742\uA744\uA746\uA748\uA74A\uA74C\uA74E\uA750\uA752\uA754\uA756\uA758\uA75A\uA75C\uA75E\uA760\uA762\uA764\uA766\uA768\uA76A\uA76C\uA76E\uA779\uA77B\uA77D-\uA77E\uA780\uA782\uA784\uA786\uA78B\uA78D\uA790\uA792\uA796\uA798\uA79A\uA79C\uA79E\uA7A0\uA7A2\uA7A4\uA7A6\uA7A8\uA7AA-\uA7AE\uA7B0-\uA7B4\uA7B6\uA7B8\uFF21-\uFF3A]/; - var peg$r15 = /^[\u16EE-\u16F0\u2160-\u2182\u2185-\u2188\u3007\u3021-\u3029\u3038-\u303A\uA6E6-\uA6EF]/; + var peg$r0 = /^[1-9]/ + var peg$r1 = /^[eE]/ + var peg$r2 = /^[+\-]/ + var peg$r3 = /^[0-9]/ + var peg$r4 = /^[ \n\r\t\xA0]/ + var peg$r5 = /^["']/ + var peg$r6 = + /^[#\--.0-9A-Z\^-_a-z~\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376-\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4-\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u09FC\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60-\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0CF1-\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E46\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5-\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEF\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7B9\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD-\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5-\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/ + var peg$r7 = /^[@_]/ + var peg$r8 = /^[^\n]/ + var peg$r9 = + /^[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376-\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4-\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u09FC\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60-\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0CF1-\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E46\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5-\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEF\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7B9\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD-\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5-\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/ + var peg$r10 = + /^[a-z\xB5\xDF-\xF6\xF8-\xFF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137-\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148-\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C-\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA-\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9-\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC-\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF-\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F-\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02AF\u0371\u0373\u0377\u037B-\u037D\u0390\u03AC-\u03CE\u03D0-\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB-\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE-\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0560-\u0588\u10D0-\u10FA\u10FD-\u10FF\u13F8-\u13FD\u1C80-\u1C88\u1D00-\u1D2B\u1D6B-\u1D77\u1D79-\u1D9A\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6-\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FC7\u1FD0-\u1FD3\u1FD6-\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6-\u1FF7\u210A\u210E-\u210F\u2113\u212F\u2134\u2139\u213C-\u213D\u2146-\u2149\u214E\u2184\u2C30-\u2C5E\u2C61\u2C65-\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73-\u2C74\u2C76-\u2C7B\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3-\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F\uA771-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7AF\uA7B5\uA7B7\uA7B9\uA7FA\uAB30-\uAB5A\uAB60-\uAB65\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A]/ + var peg$r11 = + /^[\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0374\u037A\u0559\u0640\u06E5-\u06E6\u07F4-\u07F5\u07FA\u081A\u0824\u0828\u0971\u0E46\u0EC6\u10FC\u17D7\u1843\u1AA7\u1C78-\u1C7D\u1D2C-\u1D6A\u1D78\u1D9B-\u1DBF\u2071\u207F\u2090-\u209C\u2C7C-\u2C7D\u2D6F\u2E2F\u3005\u3031-\u3035\u303B\u309D-\u309E\u30FC-\u30FE\uA015\uA4F8-\uA4FD\uA60C\uA67F\uA69C-\uA69D\uA717-\uA71F\uA770\uA788\uA7F8-\uA7F9\uA9CF\uA9E6\uAA70\uAADD\uAAF3-\uAAF4\uAB5C-\uAB5F\uFF70\uFF9E-\uFF9F]/ + var peg$r12 = + /^[\xAA\xBA\u01BB\u01C0-\u01C3\u0294\u05D0-\u05EA\u05EF-\u05F2\u0620-\u063F\u0641-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u0800-\u0815\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0972-\u0980\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u09FC\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60-\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0CF1-\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E45\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17DC\u1820-\u1842\u1844-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C77\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5-\u1CF6\u2135-\u2138\u2D30-\u2D67\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3006\u303C\u3041-\u3096\u309F\u30A1-\u30FA\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEF\uA000-\uA014\uA016-\uA48C\uA4D0-\uA4F7\uA500-\uA60B\uA610-\uA61F\uA62A-\uA62B\uA66E\uA6A0-\uA6E5\uA78F\uA7F7\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD-\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9E0-\uA9E4\uA9E7-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA6F\uAA71-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5-\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADC\uAAE0-\uAAEA\uAAF2\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF66-\uFF6F\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/ + var peg$r13 = + /^[\u01C5\u01C8\u01CB\u01F2\u1F88-\u1F8F\u1F98-\u1F9F\u1FA8-\u1FAF\u1FBC\u1FCC\u1FFC]/ + var peg$r14 = + /^[A-Z\xC0-\xD6\xD8-\xDE\u0100\u0102\u0104\u0106\u0108\u010A\u010C\u010E\u0110\u0112\u0114\u0116\u0118\u011A\u011C\u011E\u0120\u0122\u0124\u0126\u0128\u012A\u012C\u012E\u0130\u0132\u0134\u0136\u0139\u013B\u013D\u013F\u0141\u0143\u0145\u0147\u014A\u014C\u014E\u0150\u0152\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0162\u0164\u0166\u0168\u016A\u016C\u016E\u0170\u0172\u0174\u0176\u0178-\u0179\u017B\u017D\u0181-\u0182\u0184\u0186-\u0187\u0189-\u018B\u018E-\u0191\u0193-\u0194\u0196-\u0198\u019C-\u019D\u019F-\u01A0\u01A2\u01A4\u01A6-\u01A7\u01A9\u01AC\u01AE-\u01AF\u01B1-\u01B3\u01B5\u01B7-\u01B8\u01BC\u01C4\u01C7\u01CA\u01CD\u01CF\u01D1\u01D3\u01D5\u01D7\u01D9\u01DB\u01DE\u01E0\u01E2\u01E4\u01E6\u01E8\u01EA\u01EC\u01EE\u01F1\u01F4\u01F6-\u01F8\u01FA\u01FC\u01FE\u0200\u0202\u0204\u0206\u0208\u020A\u020C\u020E\u0210\u0212\u0214\u0216\u0218\u021A\u021C\u021E\u0220\u0222\u0224\u0226\u0228\u022A\u022C\u022E\u0230\u0232\u023A-\u023B\u023D-\u023E\u0241\u0243-\u0246\u0248\u024A\u024C\u024E\u0370\u0372\u0376\u037F\u0386\u0388-\u038A\u038C\u038E-\u038F\u0391-\u03A1\u03A3-\u03AB\u03CF\u03D2-\u03D4\u03D8\u03DA\u03DC\u03DE\u03E0\u03E2\u03E4\u03E6\u03E8\u03EA\u03EC\u03EE\u03F4\u03F7\u03F9-\u03FA\u03FD-\u042F\u0460\u0462\u0464\u0466\u0468\u046A\u046C\u046E\u0470\u0472\u0474\u0476\u0478\u047A\u047C\u047E\u0480\u048A\u048C\u048E\u0490\u0492\u0494\u0496\u0498\u049A\u049C\u049E\u04A0\u04A2\u04A4\u04A6\u04A8\u04AA\u04AC\u04AE\u04B0\u04B2\u04B4\u04B6\u04B8\u04BA\u04BC\u04BE\u04C0-\u04C1\u04C3\u04C5\u04C7\u04C9\u04CB\u04CD\u04D0\u04D2\u04D4\u04D6\u04D8\u04DA\u04DC\u04DE\u04E0\u04E2\u04E4\u04E6\u04E8\u04EA\u04EC\u04EE\u04F0\u04F2\u04F4\u04F6\u04F8\u04FA\u04FC\u04FE\u0500\u0502\u0504\u0506\u0508\u050A\u050C\u050E\u0510\u0512\u0514\u0516\u0518\u051A\u051C\u051E\u0520\u0522\u0524\u0526\u0528\u052A\u052C\u052E\u0531-\u0556\u10A0-\u10C5\u10C7\u10CD\u13A0-\u13F5\u1C90-\u1CBA\u1CBD-\u1CBF\u1E00\u1E02\u1E04\u1E06\u1E08\u1E0A\u1E0C\u1E0E\u1E10\u1E12\u1E14\u1E16\u1E18\u1E1A\u1E1C\u1E1E\u1E20\u1E22\u1E24\u1E26\u1E28\u1E2A\u1E2C\u1E2E\u1E30\u1E32\u1E34\u1E36\u1E38\u1E3A\u1E3C\u1E3E\u1E40\u1E42\u1E44\u1E46\u1E48\u1E4A\u1E4C\u1E4E\u1E50\u1E52\u1E54\u1E56\u1E58\u1E5A\u1E5C\u1E5E\u1E60\u1E62\u1E64\u1E66\u1E68\u1E6A\u1E6C\u1E6E\u1E70\u1E72\u1E74\u1E76\u1E78\u1E7A\u1E7C\u1E7E\u1E80\u1E82\u1E84\u1E86\u1E88\u1E8A\u1E8C\u1E8E\u1E90\u1E92\u1E94\u1E9E\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EBE\u1EC0\u1EC2\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u1EF6\u1EF8\u1EFA\u1EFC\u1EFE\u1F08-\u1F0F\u1F18-\u1F1D\u1F28-\u1F2F\u1F38-\u1F3F\u1F48-\u1F4D\u1F59\u1F5B\u1F5D\u1F5F\u1F68-\u1F6F\u1FB8-\u1FBB\u1FC8-\u1FCB\u1FD8-\u1FDB\u1FE8-\u1FEC\u1FF8-\u1FFB\u2102\u2107\u210B-\u210D\u2110-\u2112\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u2130-\u2133\u213E-\u213F\u2145\u2183\u2C00-\u2C2E\u2C60\u2C62-\u2C64\u2C67\u2C69\u2C6B\u2C6D-\u2C70\u2C72\u2C75\u2C7E-\u2C80\u2C82\u2C84\u2C86\u2C88\u2C8A\u2C8C\u2C8E\u2C90\u2C92\u2C94\u2C96\u2C98\u2C9A\u2C9C\u2C9E\u2CA0\u2CA2\u2CA4\u2CA6\u2CA8\u2CAA\u2CAC\u2CAE\u2CB0\u2CB2\u2CB4\u2CB6\u2CB8\u2CBA\u2CBC\u2CBE\u2CC0\u2CC2\u2CC4\u2CC6\u2CC8\u2CCA\u2CCC\u2CCE\u2CD0\u2CD2\u2CD4\u2CD6\u2CD8\u2CDA\u2CDC\u2CDE\u2CE0\u2CE2\u2CEB\u2CED\u2CF2\uA640\uA642\uA644\uA646\uA648\uA64A\uA64C\uA64E\uA650\uA652\uA654\uA656\uA658\uA65A\uA65C\uA65E\uA660\uA662\uA664\uA666\uA668\uA66A\uA66C\uA680\uA682\uA684\uA686\uA688\uA68A\uA68C\uA68E\uA690\uA692\uA694\uA696\uA698\uA69A\uA722\uA724\uA726\uA728\uA72A\uA72C\uA72E\uA732\uA734\uA736\uA738\uA73A\uA73C\uA73E\uA740\uA742\uA744\uA746\uA748\uA74A\uA74C\uA74E\uA750\uA752\uA754\uA756\uA758\uA75A\uA75C\uA75E\uA760\uA762\uA764\uA766\uA768\uA76A\uA76C\uA76E\uA779\uA77B\uA77D-\uA77E\uA780\uA782\uA784\uA786\uA78B\uA78D\uA790\uA792\uA796\uA798\uA79A\uA79C\uA79E\uA7A0\uA7A2\uA7A4\uA7A6\uA7A8\uA7AA-\uA7AE\uA7B0-\uA7B4\uA7B6\uA7B8\uFF21-\uFF3A]/ + var peg$r15 = + /^[\u16EE-\u16F0\u2160-\u2182\u2185-\u2188\u3007\u3021-\u3029\u3038-\u303A\uA6E6-\uA6EF]/ - var peg$e0 = peg$otherExpectation("number"); - var peg$e1 = peg$literalExpectation(".", false); - var peg$e2 = peg$classExpectation([["1", "9"]], false, false); - var peg$e3 = peg$classExpectation(["e", "E"], false, false); - var peg$e4 = peg$classExpectation(["+", "-"], false, false); - var peg$e5 = peg$literalExpectation("-", false); - var peg$e6 = peg$literalExpectation("+", false); - var peg$e7 = peg$literalExpectation("0", false); - var peg$e8 = peg$classExpectation([["0", "9"]], false, false); - var peg$e9 = peg$otherExpectation("whitespace"); - var peg$e10 = peg$classExpectation([" ", "\n", "\r", "\t", "\xA0"], false, false); - var peg$e11 = peg$literalExpectation(",", false); - var peg$e12 = peg$literalExpectation("|", false); - var peg$e13 = peg$classExpectation(["\"", "'"], false, false); - var peg$e14 = peg$otherExpectation("a letter, a number, \"-\", \"#\", \".\", \"^\", \"_\""); - var peg$e15 = peg$classExpectation(["#", ["-", "."], ["0", "9"], ["A", "Z"], ["^", "_"], ["a", "z"], "~", "\xAA", "\xB5", "\xBA", ["\xC0", "\xD6"], ["\xD8", "\xF6"], ["\xF8", "\u02C1"], ["\u02C6", "\u02D1"], ["\u02E0", "\u02E4"], "\u02EC", "\u02EE", ["\u0370", "\u0374"], ["\u0376", "\u0377"], ["\u037A", "\u037D"], "\u037F", "\u0386", ["\u0388", "\u038A"], "\u038C", ["\u038E", "\u03A1"], ["\u03A3", "\u03F5"], ["\u03F7", "\u0481"], ["\u048A", "\u052F"], ["\u0531", "\u0556"], "\u0559", ["\u0560", "\u0588"], ["\u05D0", "\u05EA"], ["\u05EF", "\u05F2"], ["\u0620", "\u064A"], ["\u066E", "\u066F"], ["\u0671", "\u06D3"], "\u06D5", ["\u06E5", "\u06E6"], ["\u06EE", "\u06EF"], ["\u06FA", "\u06FC"], "\u06FF", "\u0710", ["\u0712", "\u072F"], ["\u074D", "\u07A5"], "\u07B1", ["\u07CA", "\u07EA"], ["\u07F4", "\u07F5"], "\u07FA", ["\u0800", "\u0815"], "\u081A", "\u0824", "\u0828", ["\u0840", "\u0858"], ["\u0860", "\u086A"], ["\u08A0", "\u08B4"], ["\u08B6", "\u08BD"], ["\u0904", "\u0939"], "\u093D", "\u0950", ["\u0958", "\u0961"], ["\u0971", "\u0980"], ["\u0985", "\u098C"], ["\u098F", "\u0990"], ["\u0993", "\u09A8"], ["\u09AA", "\u09B0"], "\u09B2", ["\u09B6", "\u09B9"], "\u09BD", "\u09CE", ["\u09DC", "\u09DD"], ["\u09DF", "\u09E1"], ["\u09F0", "\u09F1"], "\u09FC", ["\u0A05", "\u0A0A"], ["\u0A0F", "\u0A10"], ["\u0A13", "\u0A28"], ["\u0A2A", "\u0A30"], ["\u0A32", "\u0A33"], ["\u0A35", "\u0A36"], ["\u0A38", "\u0A39"], ["\u0A59", "\u0A5C"], "\u0A5E", ["\u0A72", "\u0A74"], ["\u0A85", "\u0A8D"], ["\u0A8F", "\u0A91"], ["\u0A93", "\u0AA8"], ["\u0AAA", "\u0AB0"], ["\u0AB2", "\u0AB3"], ["\u0AB5", "\u0AB9"], "\u0ABD", "\u0AD0", ["\u0AE0", "\u0AE1"], "\u0AF9", ["\u0B05", "\u0B0C"], ["\u0B0F", "\u0B10"], ["\u0B13", "\u0B28"], ["\u0B2A", "\u0B30"], ["\u0B32", "\u0B33"], ["\u0B35", "\u0B39"], "\u0B3D", ["\u0B5C", "\u0B5D"], ["\u0B5F", "\u0B61"], "\u0B71", "\u0B83", ["\u0B85", "\u0B8A"], ["\u0B8E", "\u0B90"], ["\u0B92", "\u0B95"], ["\u0B99", "\u0B9A"], "\u0B9C", ["\u0B9E", "\u0B9F"], ["\u0BA3", "\u0BA4"], ["\u0BA8", "\u0BAA"], ["\u0BAE", "\u0BB9"], "\u0BD0", ["\u0C05", "\u0C0C"], ["\u0C0E", "\u0C10"], ["\u0C12", "\u0C28"], ["\u0C2A", "\u0C39"], "\u0C3D", ["\u0C58", "\u0C5A"], ["\u0C60", "\u0C61"], "\u0C80", ["\u0C85", "\u0C8C"], ["\u0C8E", "\u0C90"], ["\u0C92", "\u0CA8"], ["\u0CAA", "\u0CB3"], ["\u0CB5", "\u0CB9"], "\u0CBD", "\u0CDE", ["\u0CE0", "\u0CE1"], ["\u0CF1", "\u0CF2"], ["\u0D05", "\u0D0C"], ["\u0D0E", "\u0D10"], ["\u0D12", "\u0D3A"], "\u0D3D", "\u0D4E", ["\u0D54", "\u0D56"], ["\u0D5F", "\u0D61"], ["\u0D7A", "\u0D7F"], ["\u0D85", "\u0D96"], ["\u0D9A", "\u0DB1"], ["\u0DB3", "\u0DBB"], "\u0DBD", ["\u0DC0", "\u0DC6"], ["\u0E01", "\u0E30"], ["\u0E32", "\u0E33"], ["\u0E40", "\u0E46"], ["\u0E81", "\u0E82"], "\u0E84", ["\u0E87", "\u0E88"], "\u0E8A", "\u0E8D", ["\u0E94", "\u0E97"], ["\u0E99", "\u0E9F"], ["\u0EA1", "\u0EA3"], "\u0EA5", "\u0EA7", ["\u0EAA", "\u0EAB"], ["\u0EAD", "\u0EB0"], ["\u0EB2", "\u0EB3"], "\u0EBD", ["\u0EC0", "\u0EC4"], "\u0EC6", ["\u0EDC", "\u0EDF"], "\u0F00", ["\u0F40", "\u0F47"], ["\u0F49", "\u0F6C"], ["\u0F88", "\u0F8C"], ["\u1000", "\u102A"], "\u103F", ["\u1050", "\u1055"], ["\u105A", "\u105D"], "\u1061", ["\u1065", "\u1066"], ["\u106E", "\u1070"], ["\u1075", "\u1081"], "\u108E", ["\u10A0", "\u10C5"], "\u10C7", "\u10CD", ["\u10D0", "\u10FA"], ["\u10FC", "\u1248"], ["\u124A", "\u124D"], ["\u1250", "\u1256"], "\u1258", ["\u125A", "\u125D"], ["\u1260", "\u1288"], ["\u128A", "\u128D"], ["\u1290", "\u12B0"], ["\u12B2", "\u12B5"], ["\u12B8", "\u12BE"], "\u12C0", ["\u12C2", "\u12C5"], ["\u12C8", "\u12D6"], ["\u12D8", "\u1310"], ["\u1312", "\u1315"], ["\u1318", "\u135A"], ["\u1380", "\u138F"], ["\u13A0", "\u13F5"], ["\u13F8", "\u13FD"], ["\u1401", "\u166C"], ["\u166F", "\u167F"], ["\u1681", "\u169A"], ["\u16A0", "\u16EA"], ["\u16EE", "\u16F8"], ["\u1700", "\u170C"], ["\u170E", "\u1711"], ["\u1720", "\u1731"], ["\u1740", "\u1751"], ["\u1760", "\u176C"], ["\u176E", "\u1770"], ["\u1780", "\u17B3"], "\u17D7", "\u17DC", ["\u1820", "\u1878"], ["\u1880", "\u1884"], ["\u1887", "\u18A8"], "\u18AA", ["\u18B0", "\u18F5"], ["\u1900", "\u191E"], ["\u1950", "\u196D"], ["\u1970", "\u1974"], ["\u1980", "\u19AB"], ["\u19B0", "\u19C9"], ["\u1A00", "\u1A16"], ["\u1A20", "\u1A54"], "\u1AA7", ["\u1B05", "\u1B33"], ["\u1B45", "\u1B4B"], ["\u1B83", "\u1BA0"], ["\u1BAE", "\u1BAF"], ["\u1BBA", "\u1BE5"], ["\u1C00", "\u1C23"], ["\u1C4D", "\u1C4F"], ["\u1C5A", "\u1C7D"], ["\u1C80", "\u1C88"], ["\u1C90", "\u1CBA"], ["\u1CBD", "\u1CBF"], ["\u1CE9", "\u1CEC"], ["\u1CEE", "\u1CF1"], ["\u1CF5", "\u1CF6"], ["\u1D00", "\u1DBF"], ["\u1E00", "\u1F15"], ["\u1F18", "\u1F1D"], ["\u1F20", "\u1F45"], ["\u1F48", "\u1F4D"], ["\u1F50", "\u1F57"], "\u1F59", "\u1F5B", "\u1F5D", ["\u1F5F", "\u1F7D"], ["\u1F80", "\u1FB4"], ["\u1FB6", "\u1FBC"], "\u1FBE", ["\u1FC2", "\u1FC4"], ["\u1FC6", "\u1FCC"], ["\u1FD0", "\u1FD3"], ["\u1FD6", "\u1FDB"], ["\u1FE0", "\u1FEC"], ["\u1FF2", "\u1FF4"], ["\u1FF6", "\u1FFC"], "\u2071", "\u207F", ["\u2090", "\u209C"], "\u2102", "\u2107", ["\u210A", "\u2113"], "\u2115", ["\u2119", "\u211D"], "\u2124", "\u2126", "\u2128", ["\u212A", "\u212D"], ["\u212F", "\u2139"], ["\u213C", "\u213F"], ["\u2145", "\u2149"], "\u214E", ["\u2160", "\u2188"], ["\u2C00", "\u2C2E"], ["\u2C30", "\u2C5E"], ["\u2C60", "\u2CE4"], ["\u2CEB", "\u2CEE"], ["\u2CF2", "\u2CF3"], ["\u2D00", "\u2D25"], "\u2D27", "\u2D2D", ["\u2D30", "\u2D67"], "\u2D6F", ["\u2D80", "\u2D96"], ["\u2DA0", "\u2DA6"], ["\u2DA8", "\u2DAE"], ["\u2DB0", "\u2DB6"], ["\u2DB8", "\u2DBE"], ["\u2DC0", "\u2DC6"], ["\u2DC8", "\u2DCE"], ["\u2DD0", "\u2DD6"], ["\u2DD8", "\u2DDE"], "\u2E2F", ["\u3005", "\u3007"], ["\u3021", "\u3029"], ["\u3031", "\u3035"], ["\u3038", "\u303C"], ["\u3041", "\u3096"], ["\u309D", "\u309F"], ["\u30A1", "\u30FA"], ["\u30FC", "\u30FF"], ["\u3105", "\u312F"], ["\u3131", "\u318E"], ["\u31A0", "\u31BA"], ["\u31F0", "\u31FF"], ["\u3400", "\u4DB5"], ["\u4E00", "\u9FEF"], ["\uA000", "\uA48C"], ["\uA4D0", "\uA4FD"], ["\uA500", "\uA60C"], ["\uA610", "\uA61F"], ["\uA62A", "\uA62B"], ["\uA640", "\uA66E"], ["\uA67F", "\uA69D"], ["\uA6A0", "\uA6EF"], ["\uA717", "\uA71F"], ["\uA722", "\uA788"], ["\uA78B", "\uA7B9"], ["\uA7F7", "\uA801"], ["\uA803", "\uA805"], ["\uA807", "\uA80A"], ["\uA80C", "\uA822"], ["\uA840", "\uA873"], ["\uA882", "\uA8B3"], ["\uA8F2", "\uA8F7"], "\uA8FB", ["\uA8FD", "\uA8FE"], ["\uA90A", "\uA925"], ["\uA930", "\uA946"], ["\uA960", "\uA97C"], ["\uA984", "\uA9B2"], "\uA9CF", ["\uA9E0", "\uA9E4"], ["\uA9E6", "\uA9EF"], ["\uA9FA", "\uA9FE"], ["\uAA00", "\uAA28"], ["\uAA40", "\uAA42"], ["\uAA44", "\uAA4B"], ["\uAA60", "\uAA76"], "\uAA7A", ["\uAA7E", "\uAAAF"], "\uAAB1", ["\uAAB5", "\uAAB6"], ["\uAAB9", "\uAABD"], "\uAAC0", "\uAAC2", ["\uAADB", "\uAADD"], ["\uAAE0", "\uAAEA"], ["\uAAF2", "\uAAF4"], ["\uAB01", "\uAB06"], ["\uAB09", "\uAB0E"], ["\uAB11", "\uAB16"], ["\uAB20", "\uAB26"], ["\uAB28", "\uAB2E"], ["\uAB30", "\uAB5A"], ["\uAB5C", "\uAB65"], ["\uAB70", "\uABE2"], ["\uAC00", "\uD7A3"], ["\uD7B0", "\uD7C6"], ["\uD7CB", "\uD7FB"], ["\uF900", "\uFA6D"], ["\uFA70", "\uFAD9"], ["\uFB00", "\uFB06"], ["\uFB13", "\uFB17"], "\uFB1D", ["\uFB1F", "\uFB28"], ["\uFB2A", "\uFB36"], ["\uFB38", "\uFB3C"], "\uFB3E", ["\uFB40", "\uFB41"], ["\uFB43", "\uFB44"], ["\uFB46", "\uFBB1"], ["\uFBD3", "\uFD3D"], ["\uFD50", "\uFD8F"], ["\uFD92", "\uFDC7"], ["\uFDF0", "\uFDFB"], ["\uFE70", "\uFE74"], ["\uFE76", "\uFEFC"], ["\uFF21", "\uFF3A"], ["\uFF41", "\uFF5A"], ["\uFF66", "\uFFBE"], ["\uFFC2", "\uFFC7"], ["\uFFCA", "\uFFCF"], ["\uFFD2", "\uFFD7"], ["\uFFDA", "\uFFDC"]], false, false); - var peg$e16 = peg$literalExpectation("[", false); - var peg$e17 = peg$literalExpectation("]", false); - var peg$e18 = peg$literalExpectation("{", false); - var peg$e19 = peg$literalExpectation("}", false); - var peg$e20 = peg$literalExpectation("%", false); - var peg$e21 = peg$literalExpectation("<", false); - var peg$e22 = peg$literalExpectation(">", false); - var peg$e23 = peg$classExpectation(["@", "_"], false, false); - var peg$e24 = peg$literalExpectation("!", false); - var peg$e25 = peg$literalExpectation("(", false); - var peg$e26 = peg$literalExpectation(")", false); - var peg$e27 = peg$literalExpectation("/", false); - var peg$e28 = peg$literalExpectation("*", false); - var peg$e29 = peg$literalExpectation("?", false); - var peg$e30 = peg$literalExpectation(":", false); - var peg$e31 = peg$literalExpectation("..", false); - var peg$e32 = peg$literalExpectation("^", false); - var peg$e33 = peg$literalExpectation("struct", false); - var peg$e34 = peg$literalExpectation("target", false); - var peg$e35 = peg$literalExpectation("euclid", false); - var peg$e36 = peg$literalExpectation("slow", false); - var peg$e37 = peg$literalExpectation("rotL", false); - var peg$e38 = peg$literalExpectation("rotR", false); - var peg$e39 = peg$literalExpectation("fast", false); - var peg$e40 = peg$literalExpectation("scale", false); - var peg$e41 = peg$literalExpectation("//", false); - var peg$e42 = peg$classExpectation(["\n"], true, false); - var peg$e43 = peg$literalExpectation("cat", false); - var peg$e44 = peg$literalExpectation("$", false); - var peg$e45 = peg$literalExpectation("setcps", false); - var peg$e46 = peg$literalExpectation("setbpm", false); - var peg$e47 = peg$literalExpectation("hush", false); - var peg$e48 = peg$classExpectation([["A", "Z"], ["a", "z"], "\xAA", "\xB5", "\xBA", ["\xC0", "\xD6"], ["\xD8", "\xF6"], ["\xF8", "\u02C1"], ["\u02C6", "\u02D1"], ["\u02E0", "\u02E4"], "\u02EC", "\u02EE", ["\u0370", "\u0374"], ["\u0376", "\u0377"], ["\u037A", "\u037D"], "\u037F", "\u0386", ["\u0388", "\u038A"], "\u038C", ["\u038E", "\u03A1"], ["\u03A3", "\u03F5"], ["\u03F7", "\u0481"], ["\u048A", "\u052F"], ["\u0531", "\u0556"], "\u0559", ["\u0560", "\u0588"], ["\u05D0", "\u05EA"], ["\u05EF", "\u05F2"], ["\u0620", "\u064A"], ["\u066E", "\u066F"], ["\u0671", "\u06D3"], "\u06D5", ["\u06E5", "\u06E6"], ["\u06EE", "\u06EF"], ["\u06FA", "\u06FC"], "\u06FF", "\u0710", ["\u0712", "\u072F"], ["\u074D", "\u07A5"], "\u07B1", ["\u07CA", "\u07EA"], ["\u07F4", "\u07F5"], "\u07FA", ["\u0800", "\u0815"], "\u081A", "\u0824", "\u0828", ["\u0840", "\u0858"], ["\u0860", "\u086A"], ["\u08A0", "\u08B4"], ["\u08B6", "\u08BD"], ["\u0904", "\u0939"], "\u093D", "\u0950", ["\u0958", "\u0961"], ["\u0971", "\u0980"], ["\u0985", "\u098C"], ["\u098F", "\u0990"], ["\u0993", "\u09A8"], ["\u09AA", "\u09B0"], "\u09B2", ["\u09B6", "\u09B9"], "\u09BD", "\u09CE", ["\u09DC", "\u09DD"], ["\u09DF", "\u09E1"], ["\u09F0", "\u09F1"], "\u09FC", ["\u0A05", "\u0A0A"], ["\u0A0F", "\u0A10"], ["\u0A13", "\u0A28"], ["\u0A2A", "\u0A30"], ["\u0A32", "\u0A33"], ["\u0A35", "\u0A36"], ["\u0A38", "\u0A39"], ["\u0A59", "\u0A5C"], "\u0A5E", ["\u0A72", "\u0A74"], ["\u0A85", "\u0A8D"], ["\u0A8F", "\u0A91"], ["\u0A93", "\u0AA8"], ["\u0AAA", "\u0AB0"], ["\u0AB2", "\u0AB3"], ["\u0AB5", "\u0AB9"], "\u0ABD", "\u0AD0", ["\u0AE0", "\u0AE1"], "\u0AF9", ["\u0B05", "\u0B0C"], ["\u0B0F", "\u0B10"], ["\u0B13", "\u0B28"], ["\u0B2A", "\u0B30"], ["\u0B32", "\u0B33"], ["\u0B35", "\u0B39"], "\u0B3D", ["\u0B5C", "\u0B5D"], ["\u0B5F", "\u0B61"], "\u0B71", "\u0B83", ["\u0B85", "\u0B8A"], ["\u0B8E", "\u0B90"], ["\u0B92", "\u0B95"], ["\u0B99", "\u0B9A"], "\u0B9C", ["\u0B9E", "\u0B9F"], ["\u0BA3", "\u0BA4"], ["\u0BA8", "\u0BAA"], ["\u0BAE", "\u0BB9"], "\u0BD0", ["\u0C05", "\u0C0C"], ["\u0C0E", "\u0C10"], ["\u0C12", "\u0C28"], ["\u0C2A", "\u0C39"], "\u0C3D", ["\u0C58", "\u0C5A"], ["\u0C60", "\u0C61"], "\u0C80", ["\u0C85", "\u0C8C"], ["\u0C8E", "\u0C90"], ["\u0C92", "\u0CA8"], ["\u0CAA", "\u0CB3"], ["\u0CB5", "\u0CB9"], "\u0CBD", "\u0CDE", ["\u0CE0", "\u0CE1"], ["\u0CF1", "\u0CF2"], ["\u0D05", "\u0D0C"], ["\u0D0E", "\u0D10"], ["\u0D12", "\u0D3A"], "\u0D3D", "\u0D4E", ["\u0D54", "\u0D56"], ["\u0D5F", "\u0D61"], ["\u0D7A", "\u0D7F"], ["\u0D85", "\u0D96"], ["\u0D9A", "\u0DB1"], ["\u0DB3", "\u0DBB"], "\u0DBD", ["\u0DC0", "\u0DC6"], ["\u0E01", "\u0E30"], ["\u0E32", "\u0E33"], ["\u0E40", "\u0E46"], ["\u0E81", "\u0E82"], "\u0E84", ["\u0E87", "\u0E88"], "\u0E8A", "\u0E8D", ["\u0E94", "\u0E97"], ["\u0E99", "\u0E9F"], ["\u0EA1", "\u0EA3"], "\u0EA5", "\u0EA7", ["\u0EAA", "\u0EAB"], ["\u0EAD", "\u0EB0"], ["\u0EB2", "\u0EB3"], "\u0EBD", ["\u0EC0", "\u0EC4"], "\u0EC6", ["\u0EDC", "\u0EDF"], "\u0F00", ["\u0F40", "\u0F47"], ["\u0F49", "\u0F6C"], ["\u0F88", "\u0F8C"], ["\u1000", "\u102A"], "\u103F", ["\u1050", "\u1055"], ["\u105A", "\u105D"], "\u1061", ["\u1065", "\u1066"], ["\u106E", "\u1070"], ["\u1075", "\u1081"], "\u108E", ["\u10A0", "\u10C5"], "\u10C7", "\u10CD", ["\u10D0", "\u10FA"], ["\u10FC", "\u1248"], ["\u124A", "\u124D"], ["\u1250", "\u1256"], "\u1258", ["\u125A", "\u125D"], ["\u1260", "\u1288"], ["\u128A", "\u128D"], ["\u1290", "\u12B0"], ["\u12B2", "\u12B5"], ["\u12B8", "\u12BE"], "\u12C0", ["\u12C2", "\u12C5"], ["\u12C8", "\u12D6"], ["\u12D8", "\u1310"], ["\u1312", "\u1315"], ["\u1318", "\u135A"], ["\u1380", "\u138F"], ["\u13A0", "\u13F5"], ["\u13F8", "\u13FD"], ["\u1401", "\u166C"], ["\u166F", "\u167F"], ["\u1681", "\u169A"], ["\u16A0", "\u16EA"], ["\u16EE", "\u16F8"], ["\u1700", "\u170C"], ["\u170E", "\u1711"], ["\u1720", "\u1731"], ["\u1740", "\u1751"], ["\u1760", "\u176C"], ["\u176E", "\u1770"], ["\u1780", "\u17B3"], "\u17D7", "\u17DC", ["\u1820", "\u1878"], ["\u1880", "\u1884"], ["\u1887", "\u18A8"], "\u18AA", ["\u18B0", "\u18F5"], ["\u1900", "\u191E"], ["\u1950", "\u196D"], ["\u1970", "\u1974"], ["\u1980", "\u19AB"], ["\u19B0", "\u19C9"], ["\u1A00", "\u1A16"], ["\u1A20", "\u1A54"], "\u1AA7", ["\u1B05", "\u1B33"], ["\u1B45", "\u1B4B"], ["\u1B83", "\u1BA0"], ["\u1BAE", "\u1BAF"], ["\u1BBA", "\u1BE5"], ["\u1C00", "\u1C23"], ["\u1C4D", "\u1C4F"], ["\u1C5A", "\u1C7D"], ["\u1C80", "\u1C88"], ["\u1C90", "\u1CBA"], ["\u1CBD", "\u1CBF"], ["\u1CE9", "\u1CEC"], ["\u1CEE", "\u1CF1"], ["\u1CF5", "\u1CF6"], ["\u1D00", "\u1DBF"], ["\u1E00", "\u1F15"], ["\u1F18", "\u1F1D"], ["\u1F20", "\u1F45"], ["\u1F48", "\u1F4D"], ["\u1F50", "\u1F57"], "\u1F59", "\u1F5B", "\u1F5D", ["\u1F5F", "\u1F7D"], ["\u1F80", "\u1FB4"], ["\u1FB6", "\u1FBC"], "\u1FBE", ["\u1FC2", "\u1FC4"], ["\u1FC6", "\u1FCC"], ["\u1FD0", "\u1FD3"], ["\u1FD6", "\u1FDB"], ["\u1FE0", "\u1FEC"], ["\u1FF2", "\u1FF4"], ["\u1FF6", "\u1FFC"], "\u2071", "\u207F", ["\u2090", "\u209C"], "\u2102", "\u2107", ["\u210A", "\u2113"], "\u2115", ["\u2119", "\u211D"], "\u2124", "\u2126", "\u2128", ["\u212A", "\u212D"], ["\u212F", "\u2139"], ["\u213C", "\u213F"], ["\u2145", "\u2149"], "\u214E", ["\u2160", "\u2188"], ["\u2C00", "\u2C2E"], ["\u2C30", "\u2C5E"], ["\u2C60", "\u2CE4"], ["\u2CEB", "\u2CEE"], ["\u2CF2", "\u2CF3"], ["\u2D00", "\u2D25"], "\u2D27", "\u2D2D", ["\u2D30", "\u2D67"], "\u2D6F", ["\u2D80", "\u2D96"], ["\u2DA0", "\u2DA6"], ["\u2DA8", "\u2DAE"], ["\u2DB0", "\u2DB6"], ["\u2DB8", "\u2DBE"], ["\u2DC0", "\u2DC6"], ["\u2DC8", "\u2DCE"], ["\u2DD0", "\u2DD6"], ["\u2DD8", "\u2DDE"], "\u2E2F", ["\u3005", "\u3007"], ["\u3021", "\u3029"], ["\u3031", "\u3035"], ["\u3038", "\u303C"], ["\u3041", "\u3096"], ["\u309D", "\u309F"], ["\u30A1", "\u30FA"], ["\u30FC", "\u30FF"], ["\u3105", "\u312F"], ["\u3131", "\u318E"], ["\u31A0", "\u31BA"], ["\u31F0", "\u31FF"], ["\u3400", "\u4DB5"], ["\u4E00", "\u9FEF"], ["\uA000", "\uA48C"], ["\uA4D0", "\uA4FD"], ["\uA500", "\uA60C"], ["\uA610", "\uA61F"], ["\uA62A", "\uA62B"], ["\uA640", "\uA66E"], ["\uA67F", "\uA69D"], ["\uA6A0", "\uA6EF"], ["\uA717", "\uA71F"], ["\uA722", "\uA788"], ["\uA78B", "\uA7B9"], ["\uA7F7", "\uA801"], ["\uA803", "\uA805"], ["\uA807", "\uA80A"], ["\uA80C", "\uA822"], ["\uA840", "\uA873"], ["\uA882", "\uA8B3"], ["\uA8F2", "\uA8F7"], "\uA8FB", ["\uA8FD", "\uA8FE"], ["\uA90A", "\uA925"], ["\uA930", "\uA946"], ["\uA960", "\uA97C"], ["\uA984", "\uA9B2"], "\uA9CF", ["\uA9E0", "\uA9E4"], ["\uA9E6", "\uA9EF"], ["\uA9FA", "\uA9FE"], ["\uAA00", "\uAA28"], ["\uAA40", "\uAA42"], ["\uAA44", "\uAA4B"], ["\uAA60", "\uAA76"], "\uAA7A", ["\uAA7E", "\uAAAF"], "\uAAB1", ["\uAAB5", "\uAAB6"], ["\uAAB9", "\uAABD"], "\uAAC0", "\uAAC2", ["\uAADB", "\uAADD"], ["\uAAE0", "\uAAEA"], ["\uAAF2", "\uAAF4"], ["\uAB01", "\uAB06"], ["\uAB09", "\uAB0E"], ["\uAB11", "\uAB16"], ["\uAB20", "\uAB26"], ["\uAB28", "\uAB2E"], ["\uAB30", "\uAB5A"], ["\uAB5C", "\uAB65"], ["\uAB70", "\uABE2"], ["\uAC00", "\uD7A3"], ["\uD7B0", "\uD7C6"], ["\uD7CB", "\uD7FB"], ["\uF900", "\uFA6D"], ["\uFA70", "\uFAD9"], ["\uFB00", "\uFB06"], ["\uFB13", "\uFB17"], "\uFB1D", ["\uFB1F", "\uFB28"], ["\uFB2A", "\uFB36"], ["\uFB38", "\uFB3C"], "\uFB3E", ["\uFB40", "\uFB41"], ["\uFB43", "\uFB44"], ["\uFB46", "\uFBB1"], ["\uFBD3", "\uFD3D"], ["\uFD50", "\uFD8F"], ["\uFD92", "\uFDC7"], ["\uFDF0", "\uFDFB"], ["\uFE70", "\uFE74"], ["\uFE76", "\uFEFC"], ["\uFF21", "\uFF3A"], ["\uFF41", "\uFF5A"], ["\uFF66", "\uFFBE"], ["\uFFC2", "\uFFC7"], ["\uFFCA", "\uFFCF"], ["\uFFD2", "\uFFD7"], ["\uFFDA", "\uFFDC"]], false, false); - var peg$e49 = peg$classExpectation([["a", "z"], "\xB5", ["\xDF", "\xF6"], ["\xF8", "\xFF"], "\u0101", "\u0103", "\u0105", "\u0107", "\u0109", "\u010B", "\u010D", "\u010F", "\u0111", "\u0113", "\u0115", "\u0117", "\u0119", "\u011B", "\u011D", "\u011F", "\u0121", "\u0123", "\u0125", "\u0127", "\u0129", "\u012B", "\u012D", "\u012F", "\u0131", "\u0133", "\u0135", ["\u0137", "\u0138"], "\u013A", "\u013C", "\u013E", "\u0140", "\u0142", "\u0144", "\u0146", ["\u0148", "\u0149"], "\u014B", "\u014D", "\u014F", "\u0151", "\u0153", "\u0155", "\u0157", "\u0159", "\u015B", "\u015D", "\u015F", "\u0161", "\u0163", "\u0165", "\u0167", "\u0169", "\u016B", "\u016D", "\u016F", "\u0171", "\u0173", "\u0175", "\u0177", "\u017A", "\u017C", ["\u017E", "\u0180"], "\u0183", "\u0185", "\u0188", ["\u018C", "\u018D"], "\u0192", "\u0195", ["\u0199", "\u019B"], "\u019E", "\u01A1", "\u01A3", "\u01A5", "\u01A8", ["\u01AA", "\u01AB"], "\u01AD", "\u01B0", "\u01B4", "\u01B6", ["\u01B9", "\u01BA"], ["\u01BD", "\u01BF"], "\u01C6", "\u01C9", "\u01CC", "\u01CE", "\u01D0", "\u01D2", "\u01D4", "\u01D6", "\u01D8", "\u01DA", ["\u01DC", "\u01DD"], "\u01DF", "\u01E1", "\u01E3", "\u01E5", "\u01E7", "\u01E9", "\u01EB", "\u01ED", ["\u01EF", "\u01F0"], "\u01F3", "\u01F5", "\u01F9", "\u01FB", "\u01FD", "\u01FF", "\u0201", "\u0203", "\u0205", "\u0207", "\u0209", "\u020B", "\u020D", "\u020F", "\u0211", "\u0213", "\u0215", "\u0217", "\u0219", "\u021B", "\u021D", "\u021F", "\u0221", "\u0223", "\u0225", "\u0227", "\u0229", "\u022B", "\u022D", "\u022F", "\u0231", ["\u0233", "\u0239"], "\u023C", ["\u023F", "\u0240"], "\u0242", "\u0247", "\u0249", "\u024B", "\u024D", ["\u024F", "\u0293"], ["\u0295", "\u02AF"], "\u0371", "\u0373", "\u0377", ["\u037B", "\u037D"], "\u0390", ["\u03AC", "\u03CE"], ["\u03D0", "\u03D1"], ["\u03D5", "\u03D7"], "\u03D9", "\u03DB", "\u03DD", "\u03DF", "\u03E1", "\u03E3", "\u03E5", "\u03E7", "\u03E9", "\u03EB", "\u03ED", ["\u03EF", "\u03F3"], "\u03F5", "\u03F8", ["\u03FB", "\u03FC"], ["\u0430", "\u045F"], "\u0461", "\u0463", "\u0465", "\u0467", "\u0469", "\u046B", "\u046D", "\u046F", "\u0471", "\u0473", "\u0475", "\u0477", "\u0479", "\u047B", "\u047D", "\u047F", "\u0481", "\u048B", "\u048D", "\u048F", "\u0491", "\u0493", "\u0495", "\u0497", "\u0499", "\u049B", "\u049D", "\u049F", "\u04A1", "\u04A3", "\u04A5", "\u04A7", "\u04A9", "\u04AB", "\u04AD", "\u04AF", "\u04B1", "\u04B3", "\u04B5", "\u04B7", "\u04B9", "\u04BB", "\u04BD", "\u04BF", "\u04C2", "\u04C4", "\u04C6", "\u04C8", "\u04CA", "\u04CC", ["\u04CE", "\u04CF"], "\u04D1", "\u04D3", "\u04D5", "\u04D7", "\u04D9", "\u04DB", "\u04DD", "\u04DF", "\u04E1", "\u04E3", "\u04E5", "\u04E7", "\u04E9", "\u04EB", "\u04ED", "\u04EF", "\u04F1", "\u04F3", "\u04F5", "\u04F7", "\u04F9", "\u04FB", "\u04FD", "\u04FF", "\u0501", "\u0503", "\u0505", "\u0507", "\u0509", "\u050B", "\u050D", "\u050F", "\u0511", "\u0513", "\u0515", "\u0517", "\u0519", "\u051B", "\u051D", "\u051F", "\u0521", "\u0523", "\u0525", "\u0527", "\u0529", "\u052B", "\u052D", "\u052F", ["\u0560", "\u0588"], ["\u10D0", "\u10FA"], ["\u10FD", "\u10FF"], ["\u13F8", "\u13FD"], ["\u1C80", "\u1C88"], ["\u1D00", "\u1D2B"], ["\u1D6B", "\u1D77"], ["\u1D79", "\u1D9A"], "\u1E01", "\u1E03", "\u1E05", "\u1E07", "\u1E09", "\u1E0B", "\u1E0D", "\u1E0F", "\u1E11", "\u1E13", "\u1E15", "\u1E17", "\u1E19", "\u1E1B", "\u1E1D", "\u1E1F", "\u1E21", "\u1E23", "\u1E25", "\u1E27", "\u1E29", "\u1E2B", "\u1E2D", "\u1E2F", "\u1E31", "\u1E33", "\u1E35", "\u1E37", "\u1E39", "\u1E3B", "\u1E3D", "\u1E3F", "\u1E41", "\u1E43", "\u1E45", "\u1E47", "\u1E49", "\u1E4B", "\u1E4D", "\u1E4F", "\u1E51", "\u1E53", "\u1E55", "\u1E57", "\u1E59", "\u1E5B", "\u1E5D", "\u1E5F", "\u1E61", "\u1E63", "\u1E65", "\u1E67", "\u1E69", "\u1E6B", "\u1E6D", "\u1E6F", "\u1E71", "\u1E73", "\u1E75", "\u1E77", "\u1E79", "\u1E7B", "\u1E7D", "\u1E7F", "\u1E81", "\u1E83", "\u1E85", "\u1E87", "\u1E89", "\u1E8B", "\u1E8D", "\u1E8F", "\u1E91", "\u1E93", ["\u1E95", "\u1E9D"], "\u1E9F", "\u1EA1", "\u1EA3", "\u1EA5", "\u1EA7", "\u1EA9", "\u1EAB", "\u1EAD", "\u1EAF", "\u1EB1", "\u1EB3", "\u1EB5", "\u1EB7", "\u1EB9", "\u1EBB", "\u1EBD", "\u1EBF", "\u1EC1", "\u1EC3", "\u1EC5", "\u1EC7", "\u1EC9", "\u1ECB", "\u1ECD", "\u1ECF", "\u1ED1", "\u1ED3", "\u1ED5", "\u1ED7", "\u1ED9", "\u1EDB", "\u1EDD", "\u1EDF", "\u1EE1", "\u1EE3", "\u1EE5", "\u1EE7", "\u1EE9", "\u1EEB", "\u1EED", "\u1EEF", "\u1EF1", "\u1EF3", "\u1EF5", "\u1EF7", "\u1EF9", "\u1EFB", "\u1EFD", ["\u1EFF", "\u1F07"], ["\u1F10", "\u1F15"], ["\u1F20", "\u1F27"], ["\u1F30", "\u1F37"], ["\u1F40", "\u1F45"], ["\u1F50", "\u1F57"], ["\u1F60", "\u1F67"], ["\u1F70", "\u1F7D"], ["\u1F80", "\u1F87"], ["\u1F90", "\u1F97"], ["\u1FA0", "\u1FA7"], ["\u1FB0", "\u1FB4"], ["\u1FB6", "\u1FB7"], "\u1FBE", ["\u1FC2", "\u1FC4"], ["\u1FC6", "\u1FC7"], ["\u1FD0", "\u1FD3"], ["\u1FD6", "\u1FD7"], ["\u1FE0", "\u1FE7"], ["\u1FF2", "\u1FF4"], ["\u1FF6", "\u1FF7"], "\u210A", ["\u210E", "\u210F"], "\u2113", "\u212F", "\u2134", "\u2139", ["\u213C", "\u213D"], ["\u2146", "\u2149"], "\u214E", "\u2184", ["\u2C30", "\u2C5E"], "\u2C61", ["\u2C65", "\u2C66"], "\u2C68", "\u2C6A", "\u2C6C", "\u2C71", ["\u2C73", "\u2C74"], ["\u2C76", "\u2C7B"], "\u2C81", "\u2C83", "\u2C85", "\u2C87", "\u2C89", "\u2C8B", "\u2C8D", "\u2C8F", "\u2C91", "\u2C93", "\u2C95", "\u2C97", "\u2C99", "\u2C9B", "\u2C9D", "\u2C9F", "\u2CA1", "\u2CA3", "\u2CA5", "\u2CA7", "\u2CA9", "\u2CAB", "\u2CAD", "\u2CAF", "\u2CB1", "\u2CB3", "\u2CB5", "\u2CB7", "\u2CB9", "\u2CBB", "\u2CBD", "\u2CBF", "\u2CC1", "\u2CC3", "\u2CC5", "\u2CC7", "\u2CC9", "\u2CCB", "\u2CCD", "\u2CCF", "\u2CD1", "\u2CD3", "\u2CD5", "\u2CD7", "\u2CD9", "\u2CDB", "\u2CDD", "\u2CDF", "\u2CE1", ["\u2CE3", "\u2CE4"], "\u2CEC", "\u2CEE", "\u2CF3", ["\u2D00", "\u2D25"], "\u2D27", "\u2D2D", "\uA641", "\uA643", "\uA645", "\uA647", "\uA649", "\uA64B", "\uA64D", "\uA64F", "\uA651", "\uA653", "\uA655", "\uA657", "\uA659", "\uA65B", "\uA65D", "\uA65F", "\uA661", "\uA663", "\uA665", "\uA667", "\uA669", "\uA66B", "\uA66D", "\uA681", "\uA683", "\uA685", "\uA687", "\uA689", "\uA68B", "\uA68D", "\uA68F", "\uA691", "\uA693", "\uA695", "\uA697", "\uA699", "\uA69B", "\uA723", "\uA725", "\uA727", "\uA729", "\uA72B", "\uA72D", ["\uA72F", "\uA731"], "\uA733", "\uA735", "\uA737", "\uA739", "\uA73B", "\uA73D", "\uA73F", "\uA741", "\uA743", "\uA745", "\uA747", "\uA749", "\uA74B", "\uA74D", "\uA74F", "\uA751", "\uA753", "\uA755", "\uA757", "\uA759", "\uA75B", "\uA75D", "\uA75F", "\uA761", "\uA763", "\uA765", "\uA767", "\uA769", "\uA76B", "\uA76D", "\uA76F", ["\uA771", "\uA778"], "\uA77A", "\uA77C", "\uA77F", "\uA781", "\uA783", "\uA785", "\uA787", "\uA78C", "\uA78E", "\uA791", ["\uA793", "\uA795"], "\uA797", "\uA799", "\uA79B", "\uA79D", "\uA79F", "\uA7A1", "\uA7A3", "\uA7A5", "\uA7A7", "\uA7A9", "\uA7AF", "\uA7B5", "\uA7B7", "\uA7B9", "\uA7FA", ["\uAB30", "\uAB5A"], ["\uAB60", "\uAB65"], ["\uAB70", "\uABBF"], ["\uFB00", "\uFB06"], ["\uFB13", "\uFB17"], ["\uFF41", "\uFF5A"]], false, false); - var peg$e50 = peg$classExpectation([["\u02B0", "\u02C1"], ["\u02C6", "\u02D1"], ["\u02E0", "\u02E4"], "\u02EC", "\u02EE", "\u0374", "\u037A", "\u0559", "\u0640", ["\u06E5", "\u06E6"], ["\u07F4", "\u07F5"], "\u07FA", "\u081A", "\u0824", "\u0828", "\u0971", "\u0E46", "\u0EC6", "\u10FC", "\u17D7", "\u1843", "\u1AA7", ["\u1C78", "\u1C7D"], ["\u1D2C", "\u1D6A"], "\u1D78", ["\u1D9B", "\u1DBF"], "\u2071", "\u207F", ["\u2090", "\u209C"], ["\u2C7C", "\u2C7D"], "\u2D6F", "\u2E2F", "\u3005", ["\u3031", "\u3035"], "\u303B", ["\u309D", "\u309E"], ["\u30FC", "\u30FE"], "\uA015", ["\uA4F8", "\uA4FD"], "\uA60C", "\uA67F", ["\uA69C", "\uA69D"], ["\uA717", "\uA71F"], "\uA770", "\uA788", ["\uA7F8", "\uA7F9"], "\uA9CF", "\uA9E6", "\uAA70", "\uAADD", ["\uAAF3", "\uAAF4"], ["\uAB5C", "\uAB5F"], "\uFF70", ["\uFF9E", "\uFF9F"]], false, false); - var peg$e51 = peg$classExpectation(["\xAA", "\xBA", "\u01BB", ["\u01C0", "\u01C3"], "\u0294", ["\u05D0", "\u05EA"], ["\u05EF", "\u05F2"], ["\u0620", "\u063F"], ["\u0641", "\u064A"], ["\u066E", "\u066F"], ["\u0671", "\u06D3"], "\u06D5", ["\u06EE", "\u06EF"], ["\u06FA", "\u06FC"], "\u06FF", "\u0710", ["\u0712", "\u072F"], ["\u074D", "\u07A5"], "\u07B1", ["\u07CA", "\u07EA"], ["\u0800", "\u0815"], ["\u0840", "\u0858"], ["\u0860", "\u086A"], ["\u08A0", "\u08B4"], ["\u08B6", "\u08BD"], ["\u0904", "\u0939"], "\u093D", "\u0950", ["\u0958", "\u0961"], ["\u0972", "\u0980"], ["\u0985", "\u098C"], ["\u098F", "\u0990"], ["\u0993", "\u09A8"], ["\u09AA", "\u09B0"], "\u09B2", ["\u09B6", "\u09B9"], "\u09BD", "\u09CE", ["\u09DC", "\u09DD"], ["\u09DF", "\u09E1"], ["\u09F0", "\u09F1"], "\u09FC", ["\u0A05", "\u0A0A"], ["\u0A0F", "\u0A10"], ["\u0A13", "\u0A28"], ["\u0A2A", "\u0A30"], ["\u0A32", "\u0A33"], ["\u0A35", "\u0A36"], ["\u0A38", "\u0A39"], ["\u0A59", "\u0A5C"], "\u0A5E", ["\u0A72", "\u0A74"], ["\u0A85", "\u0A8D"], ["\u0A8F", "\u0A91"], ["\u0A93", "\u0AA8"], ["\u0AAA", "\u0AB0"], ["\u0AB2", "\u0AB3"], ["\u0AB5", "\u0AB9"], "\u0ABD", "\u0AD0", ["\u0AE0", "\u0AE1"], "\u0AF9", ["\u0B05", "\u0B0C"], ["\u0B0F", "\u0B10"], ["\u0B13", "\u0B28"], ["\u0B2A", "\u0B30"], ["\u0B32", "\u0B33"], ["\u0B35", "\u0B39"], "\u0B3D", ["\u0B5C", "\u0B5D"], ["\u0B5F", "\u0B61"], "\u0B71", "\u0B83", ["\u0B85", "\u0B8A"], ["\u0B8E", "\u0B90"], ["\u0B92", "\u0B95"], ["\u0B99", "\u0B9A"], "\u0B9C", ["\u0B9E", "\u0B9F"], ["\u0BA3", "\u0BA4"], ["\u0BA8", "\u0BAA"], ["\u0BAE", "\u0BB9"], "\u0BD0", ["\u0C05", "\u0C0C"], ["\u0C0E", "\u0C10"], ["\u0C12", "\u0C28"], ["\u0C2A", "\u0C39"], "\u0C3D", ["\u0C58", "\u0C5A"], ["\u0C60", "\u0C61"], "\u0C80", ["\u0C85", "\u0C8C"], ["\u0C8E", "\u0C90"], ["\u0C92", "\u0CA8"], ["\u0CAA", "\u0CB3"], ["\u0CB5", "\u0CB9"], "\u0CBD", "\u0CDE", ["\u0CE0", "\u0CE1"], ["\u0CF1", "\u0CF2"], ["\u0D05", "\u0D0C"], ["\u0D0E", "\u0D10"], ["\u0D12", "\u0D3A"], "\u0D3D", "\u0D4E", ["\u0D54", "\u0D56"], ["\u0D5F", "\u0D61"], ["\u0D7A", "\u0D7F"], ["\u0D85", "\u0D96"], ["\u0D9A", "\u0DB1"], ["\u0DB3", "\u0DBB"], "\u0DBD", ["\u0DC0", "\u0DC6"], ["\u0E01", "\u0E30"], ["\u0E32", "\u0E33"], ["\u0E40", "\u0E45"], ["\u0E81", "\u0E82"], "\u0E84", ["\u0E87", "\u0E88"], "\u0E8A", "\u0E8D", ["\u0E94", "\u0E97"], ["\u0E99", "\u0E9F"], ["\u0EA1", "\u0EA3"], "\u0EA5", "\u0EA7", ["\u0EAA", "\u0EAB"], ["\u0EAD", "\u0EB0"], ["\u0EB2", "\u0EB3"], "\u0EBD", ["\u0EC0", "\u0EC4"], ["\u0EDC", "\u0EDF"], "\u0F00", ["\u0F40", "\u0F47"], ["\u0F49", "\u0F6C"], ["\u0F88", "\u0F8C"], ["\u1000", "\u102A"], "\u103F", ["\u1050", "\u1055"], ["\u105A", "\u105D"], "\u1061", ["\u1065", "\u1066"], ["\u106E", "\u1070"], ["\u1075", "\u1081"], "\u108E", ["\u1100", "\u1248"], ["\u124A", "\u124D"], ["\u1250", "\u1256"], "\u1258", ["\u125A", "\u125D"], ["\u1260", "\u1288"], ["\u128A", "\u128D"], ["\u1290", "\u12B0"], ["\u12B2", "\u12B5"], ["\u12B8", "\u12BE"], "\u12C0", ["\u12C2", "\u12C5"], ["\u12C8", "\u12D6"], ["\u12D8", "\u1310"], ["\u1312", "\u1315"], ["\u1318", "\u135A"], ["\u1380", "\u138F"], ["\u1401", "\u166C"], ["\u166F", "\u167F"], ["\u1681", "\u169A"], ["\u16A0", "\u16EA"], ["\u16F1", "\u16F8"], ["\u1700", "\u170C"], ["\u170E", "\u1711"], ["\u1720", "\u1731"], ["\u1740", "\u1751"], ["\u1760", "\u176C"], ["\u176E", "\u1770"], ["\u1780", "\u17B3"], "\u17DC", ["\u1820", "\u1842"], ["\u1844", "\u1878"], ["\u1880", "\u1884"], ["\u1887", "\u18A8"], "\u18AA", ["\u18B0", "\u18F5"], ["\u1900", "\u191E"], ["\u1950", "\u196D"], ["\u1970", "\u1974"], ["\u1980", "\u19AB"], ["\u19B0", "\u19C9"], ["\u1A00", "\u1A16"], ["\u1A20", "\u1A54"], ["\u1B05", "\u1B33"], ["\u1B45", "\u1B4B"], ["\u1B83", "\u1BA0"], ["\u1BAE", "\u1BAF"], ["\u1BBA", "\u1BE5"], ["\u1C00", "\u1C23"], ["\u1C4D", "\u1C4F"], ["\u1C5A", "\u1C77"], ["\u1CE9", "\u1CEC"], ["\u1CEE", "\u1CF1"], ["\u1CF5", "\u1CF6"], ["\u2135", "\u2138"], ["\u2D30", "\u2D67"], ["\u2D80", "\u2D96"], ["\u2DA0", "\u2DA6"], ["\u2DA8", "\u2DAE"], ["\u2DB0", "\u2DB6"], ["\u2DB8", "\u2DBE"], ["\u2DC0", "\u2DC6"], ["\u2DC8", "\u2DCE"], ["\u2DD0", "\u2DD6"], ["\u2DD8", "\u2DDE"], "\u3006", "\u303C", ["\u3041", "\u3096"], "\u309F", ["\u30A1", "\u30FA"], "\u30FF", ["\u3105", "\u312F"], ["\u3131", "\u318E"], ["\u31A0", "\u31BA"], ["\u31F0", "\u31FF"], ["\u3400", "\u4DB5"], ["\u4E00", "\u9FEF"], ["\uA000", "\uA014"], ["\uA016", "\uA48C"], ["\uA4D0", "\uA4F7"], ["\uA500", "\uA60B"], ["\uA610", "\uA61F"], ["\uA62A", "\uA62B"], "\uA66E", ["\uA6A0", "\uA6E5"], "\uA78F", "\uA7F7", ["\uA7FB", "\uA801"], ["\uA803", "\uA805"], ["\uA807", "\uA80A"], ["\uA80C", "\uA822"], ["\uA840", "\uA873"], ["\uA882", "\uA8B3"], ["\uA8F2", "\uA8F7"], "\uA8FB", ["\uA8FD", "\uA8FE"], ["\uA90A", "\uA925"], ["\uA930", "\uA946"], ["\uA960", "\uA97C"], ["\uA984", "\uA9B2"], ["\uA9E0", "\uA9E4"], ["\uA9E7", "\uA9EF"], ["\uA9FA", "\uA9FE"], ["\uAA00", "\uAA28"], ["\uAA40", "\uAA42"], ["\uAA44", "\uAA4B"], ["\uAA60", "\uAA6F"], ["\uAA71", "\uAA76"], "\uAA7A", ["\uAA7E", "\uAAAF"], "\uAAB1", ["\uAAB5", "\uAAB6"], ["\uAAB9", "\uAABD"], "\uAAC0", "\uAAC2", ["\uAADB", "\uAADC"], ["\uAAE0", "\uAAEA"], "\uAAF2", ["\uAB01", "\uAB06"], ["\uAB09", "\uAB0E"], ["\uAB11", "\uAB16"], ["\uAB20", "\uAB26"], ["\uAB28", "\uAB2E"], ["\uABC0", "\uABE2"], ["\uAC00", "\uD7A3"], ["\uD7B0", "\uD7C6"], ["\uD7CB", "\uD7FB"], ["\uF900", "\uFA6D"], ["\uFA70", "\uFAD9"], "\uFB1D", ["\uFB1F", "\uFB28"], ["\uFB2A", "\uFB36"], ["\uFB38", "\uFB3C"], "\uFB3E", ["\uFB40", "\uFB41"], ["\uFB43", "\uFB44"], ["\uFB46", "\uFBB1"], ["\uFBD3", "\uFD3D"], ["\uFD50", "\uFD8F"], ["\uFD92", "\uFDC7"], ["\uFDF0", "\uFDFB"], ["\uFE70", "\uFE74"], ["\uFE76", "\uFEFC"], ["\uFF66", "\uFF6F"], ["\uFF71", "\uFF9D"], ["\uFFA0", "\uFFBE"], ["\uFFC2", "\uFFC7"], ["\uFFCA", "\uFFCF"], ["\uFFD2", "\uFFD7"], ["\uFFDA", "\uFFDC"]], false, false); - var peg$e52 = peg$classExpectation(["\u01C5", "\u01C8", "\u01CB", "\u01F2", ["\u1F88", "\u1F8F"], ["\u1F98", "\u1F9F"], ["\u1FA8", "\u1FAF"], "\u1FBC", "\u1FCC", "\u1FFC"], false, false); - var peg$e53 = peg$classExpectation([["A", "Z"], ["\xC0", "\xD6"], ["\xD8", "\xDE"], "\u0100", "\u0102", "\u0104", "\u0106", "\u0108", "\u010A", "\u010C", "\u010E", "\u0110", "\u0112", "\u0114", "\u0116", "\u0118", "\u011A", "\u011C", "\u011E", "\u0120", "\u0122", "\u0124", "\u0126", "\u0128", "\u012A", "\u012C", "\u012E", "\u0130", "\u0132", "\u0134", "\u0136", "\u0139", "\u013B", "\u013D", "\u013F", "\u0141", "\u0143", "\u0145", "\u0147", "\u014A", "\u014C", "\u014E", "\u0150", "\u0152", "\u0154", "\u0156", "\u0158", "\u015A", "\u015C", "\u015E", "\u0160", "\u0162", "\u0164", "\u0166", "\u0168", "\u016A", "\u016C", "\u016E", "\u0170", "\u0172", "\u0174", "\u0176", ["\u0178", "\u0179"], "\u017B", "\u017D", ["\u0181", "\u0182"], "\u0184", ["\u0186", "\u0187"], ["\u0189", "\u018B"], ["\u018E", "\u0191"], ["\u0193", "\u0194"], ["\u0196", "\u0198"], ["\u019C", "\u019D"], ["\u019F", "\u01A0"], "\u01A2", "\u01A4", ["\u01A6", "\u01A7"], "\u01A9", "\u01AC", ["\u01AE", "\u01AF"], ["\u01B1", "\u01B3"], "\u01B5", ["\u01B7", "\u01B8"], "\u01BC", "\u01C4", "\u01C7", "\u01CA", "\u01CD", "\u01CF", "\u01D1", "\u01D3", "\u01D5", "\u01D7", "\u01D9", "\u01DB", "\u01DE", "\u01E0", "\u01E2", "\u01E4", "\u01E6", "\u01E8", "\u01EA", "\u01EC", "\u01EE", "\u01F1", "\u01F4", ["\u01F6", "\u01F8"], "\u01FA", "\u01FC", "\u01FE", "\u0200", "\u0202", "\u0204", "\u0206", "\u0208", "\u020A", "\u020C", "\u020E", "\u0210", "\u0212", "\u0214", "\u0216", "\u0218", "\u021A", "\u021C", "\u021E", "\u0220", "\u0222", "\u0224", "\u0226", "\u0228", "\u022A", "\u022C", "\u022E", "\u0230", "\u0232", ["\u023A", "\u023B"], ["\u023D", "\u023E"], "\u0241", ["\u0243", "\u0246"], "\u0248", "\u024A", "\u024C", "\u024E", "\u0370", "\u0372", "\u0376", "\u037F", "\u0386", ["\u0388", "\u038A"], "\u038C", ["\u038E", "\u038F"], ["\u0391", "\u03A1"], ["\u03A3", "\u03AB"], "\u03CF", ["\u03D2", "\u03D4"], "\u03D8", "\u03DA", "\u03DC", "\u03DE", "\u03E0", "\u03E2", "\u03E4", "\u03E6", "\u03E8", "\u03EA", "\u03EC", "\u03EE", "\u03F4", "\u03F7", ["\u03F9", "\u03FA"], ["\u03FD", "\u042F"], "\u0460", "\u0462", "\u0464", "\u0466", "\u0468", "\u046A", "\u046C", "\u046E", "\u0470", "\u0472", "\u0474", "\u0476", "\u0478", "\u047A", "\u047C", "\u047E", "\u0480", "\u048A", "\u048C", "\u048E", "\u0490", "\u0492", "\u0494", "\u0496", "\u0498", "\u049A", "\u049C", "\u049E", "\u04A0", "\u04A2", "\u04A4", "\u04A6", "\u04A8", "\u04AA", "\u04AC", "\u04AE", "\u04B0", "\u04B2", "\u04B4", "\u04B6", "\u04B8", "\u04BA", "\u04BC", "\u04BE", ["\u04C0", "\u04C1"], "\u04C3", "\u04C5", "\u04C7", "\u04C9", "\u04CB", "\u04CD", "\u04D0", "\u04D2", "\u04D4", "\u04D6", "\u04D8", "\u04DA", "\u04DC", "\u04DE", "\u04E0", "\u04E2", "\u04E4", "\u04E6", "\u04E8", "\u04EA", "\u04EC", "\u04EE", "\u04F0", "\u04F2", "\u04F4", "\u04F6", "\u04F8", "\u04FA", "\u04FC", "\u04FE", "\u0500", "\u0502", "\u0504", "\u0506", "\u0508", "\u050A", "\u050C", "\u050E", "\u0510", "\u0512", "\u0514", "\u0516", "\u0518", "\u051A", "\u051C", "\u051E", "\u0520", "\u0522", "\u0524", "\u0526", "\u0528", "\u052A", "\u052C", "\u052E", ["\u0531", "\u0556"], ["\u10A0", "\u10C5"], "\u10C7", "\u10CD", ["\u13A0", "\u13F5"], ["\u1C90", "\u1CBA"], ["\u1CBD", "\u1CBF"], "\u1E00", "\u1E02", "\u1E04", "\u1E06", "\u1E08", "\u1E0A", "\u1E0C", "\u1E0E", "\u1E10", "\u1E12", "\u1E14", "\u1E16", "\u1E18", "\u1E1A", "\u1E1C", "\u1E1E", "\u1E20", "\u1E22", "\u1E24", "\u1E26", "\u1E28", "\u1E2A", "\u1E2C", "\u1E2E", "\u1E30", "\u1E32", "\u1E34", "\u1E36", "\u1E38", "\u1E3A", "\u1E3C", "\u1E3E", "\u1E40", "\u1E42", "\u1E44", "\u1E46", "\u1E48", "\u1E4A", "\u1E4C", "\u1E4E", "\u1E50", "\u1E52", "\u1E54", "\u1E56", "\u1E58", "\u1E5A", "\u1E5C", "\u1E5E", "\u1E60", "\u1E62", "\u1E64", "\u1E66", "\u1E68", "\u1E6A", "\u1E6C", "\u1E6E", "\u1E70", "\u1E72", "\u1E74", "\u1E76", "\u1E78", "\u1E7A", "\u1E7C", "\u1E7E", "\u1E80", "\u1E82", "\u1E84", "\u1E86", "\u1E88", "\u1E8A", "\u1E8C", "\u1E8E", "\u1E90", "\u1E92", "\u1E94", "\u1E9E", "\u1EA0", "\u1EA2", "\u1EA4", "\u1EA6", "\u1EA8", "\u1EAA", "\u1EAC", "\u1EAE", "\u1EB0", "\u1EB2", "\u1EB4", "\u1EB6", "\u1EB8", "\u1EBA", "\u1EBC", "\u1EBE", "\u1EC0", "\u1EC2", "\u1EC4", "\u1EC6", "\u1EC8", "\u1ECA", "\u1ECC", "\u1ECE", "\u1ED0", "\u1ED2", "\u1ED4", "\u1ED6", "\u1ED8", "\u1EDA", "\u1EDC", "\u1EDE", "\u1EE0", "\u1EE2", "\u1EE4", "\u1EE6", "\u1EE8", "\u1EEA", "\u1EEC", "\u1EEE", "\u1EF0", "\u1EF2", "\u1EF4", "\u1EF6", "\u1EF8", "\u1EFA", "\u1EFC", "\u1EFE", ["\u1F08", "\u1F0F"], ["\u1F18", "\u1F1D"], ["\u1F28", "\u1F2F"], ["\u1F38", "\u1F3F"], ["\u1F48", "\u1F4D"], "\u1F59", "\u1F5B", "\u1F5D", "\u1F5F", ["\u1F68", "\u1F6F"], ["\u1FB8", "\u1FBB"], ["\u1FC8", "\u1FCB"], ["\u1FD8", "\u1FDB"], ["\u1FE8", "\u1FEC"], ["\u1FF8", "\u1FFB"], "\u2102", "\u2107", ["\u210B", "\u210D"], ["\u2110", "\u2112"], "\u2115", ["\u2119", "\u211D"], "\u2124", "\u2126", "\u2128", ["\u212A", "\u212D"], ["\u2130", "\u2133"], ["\u213E", "\u213F"], "\u2145", "\u2183", ["\u2C00", "\u2C2E"], "\u2C60", ["\u2C62", "\u2C64"], "\u2C67", "\u2C69", "\u2C6B", ["\u2C6D", "\u2C70"], "\u2C72", "\u2C75", ["\u2C7E", "\u2C80"], "\u2C82", "\u2C84", "\u2C86", "\u2C88", "\u2C8A", "\u2C8C", "\u2C8E", "\u2C90", "\u2C92", "\u2C94", "\u2C96", "\u2C98", "\u2C9A", "\u2C9C", "\u2C9E", "\u2CA0", "\u2CA2", "\u2CA4", "\u2CA6", "\u2CA8", "\u2CAA", "\u2CAC", "\u2CAE", "\u2CB0", "\u2CB2", "\u2CB4", "\u2CB6", "\u2CB8", "\u2CBA", "\u2CBC", "\u2CBE", "\u2CC0", "\u2CC2", "\u2CC4", "\u2CC6", "\u2CC8", "\u2CCA", "\u2CCC", "\u2CCE", "\u2CD0", "\u2CD2", "\u2CD4", "\u2CD6", "\u2CD8", "\u2CDA", "\u2CDC", "\u2CDE", "\u2CE0", "\u2CE2", "\u2CEB", "\u2CED", "\u2CF2", "\uA640", "\uA642", "\uA644", "\uA646", "\uA648", "\uA64A", "\uA64C", "\uA64E", "\uA650", "\uA652", "\uA654", "\uA656", "\uA658", "\uA65A", "\uA65C", "\uA65E", "\uA660", "\uA662", "\uA664", "\uA666", "\uA668", "\uA66A", "\uA66C", "\uA680", "\uA682", "\uA684", "\uA686", "\uA688", "\uA68A", "\uA68C", "\uA68E", "\uA690", "\uA692", "\uA694", "\uA696", "\uA698", "\uA69A", "\uA722", "\uA724", "\uA726", "\uA728", "\uA72A", "\uA72C", "\uA72E", "\uA732", "\uA734", "\uA736", "\uA738", "\uA73A", "\uA73C", "\uA73E", "\uA740", "\uA742", "\uA744", "\uA746", "\uA748", "\uA74A", "\uA74C", "\uA74E", "\uA750", "\uA752", "\uA754", "\uA756", "\uA758", "\uA75A", "\uA75C", "\uA75E", "\uA760", "\uA762", "\uA764", "\uA766", "\uA768", "\uA76A", "\uA76C", "\uA76E", "\uA779", "\uA77B", ["\uA77D", "\uA77E"], "\uA780", "\uA782", "\uA784", "\uA786", "\uA78B", "\uA78D", "\uA790", "\uA792", "\uA796", "\uA798", "\uA79A", "\uA79C", "\uA79E", "\uA7A0", "\uA7A2", "\uA7A4", "\uA7A6", "\uA7A8", ["\uA7AA", "\uA7AE"], ["\uA7B0", "\uA7B4"], "\uA7B6", "\uA7B8", ["\uFF21", "\uFF3A"]], false, false); - var peg$e54 = peg$classExpectation([["\u16EE", "\u16F0"], ["\u2160", "\u2182"], ["\u2185", "\u2188"], "\u3007", ["\u3021", "\u3029"], ["\u3038", "\u303A"], ["\uA6E6", "\uA6EF"]], false, false); + var peg$e0 = peg$otherExpectation('number') + var peg$e1 = peg$literalExpectation('.', false) + var peg$e2 = peg$classExpectation([['1', '9']], false, false) + var peg$e3 = peg$classExpectation(['e', 'E'], false, false) + var peg$e4 = peg$classExpectation(['+', '-'], false, false) + var peg$e5 = peg$literalExpectation('-', false) + var peg$e6 = peg$literalExpectation('+', false) + var peg$e7 = peg$literalExpectation('0', false) + var peg$e8 = peg$classExpectation([['0', '9']], false, false) + var peg$e9 = peg$otherExpectation('whitespace') + var peg$e10 = peg$classExpectation([' ', '\n', '\r', '\t', '\xA0'], false, false) + var peg$e11 = peg$literalExpectation(',', false) + var peg$e12 = peg$literalExpectation('|', false) + var peg$e13 = peg$classExpectation(['"', "'"], false, false) + var peg$e14 = peg$otherExpectation('a letter, a number, "-", "#", ".", "^", "_"') + var peg$e15 = peg$classExpectation( + [ + '#', + ['-', '.'], + ['0', '9'], + ['A', 'Z'], + ['^', '_'], + ['a', 'z'], + '~', + '\xAA', + '\xB5', + '\xBA', + ['\xC0', '\xD6'], + ['\xD8', '\xF6'], + ['\xF8', '\u02C1'], + ['\u02C6', '\u02D1'], + ['\u02E0', '\u02E4'], + '\u02EC', + '\u02EE', + ['\u0370', '\u0374'], + ['\u0376', '\u0377'], + ['\u037A', '\u037D'], + '\u037F', + '\u0386', + ['\u0388', '\u038A'], + '\u038C', + ['\u038E', '\u03A1'], + ['\u03A3', '\u03F5'], + ['\u03F7', '\u0481'], + ['\u048A', '\u052F'], + ['\u0531', '\u0556'], + '\u0559', + ['\u0560', '\u0588'], + ['\u05D0', '\u05EA'], + ['\u05EF', '\u05F2'], + ['\u0620', '\u064A'], + ['\u066E', '\u066F'], + ['\u0671', '\u06D3'], + '\u06D5', + ['\u06E5', '\u06E6'], + ['\u06EE', '\u06EF'], + ['\u06FA', '\u06FC'], + '\u06FF', + '\u0710', + ['\u0712', '\u072F'], + ['\u074D', '\u07A5'], + '\u07B1', + ['\u07CA', '\u07EA'], + ['\u07F4', '\u07F5'], + '\u07FA', + ['\u0800', '\u0815'], + '\u081A', + '\u0824', + '\u0828', + ['\u0840', '\u0858'], + ['\u0860', '\u086A'], + ['\u08A0', '\u08B4'], + ['\u08B6', '\u08BD'], + ['\u0904', '\u0939'], + '\u093D', + '\u0950', + ['\u0958', '\u0961'], + ['\u0971', '\u0980'], + ['\u0985', '\u098C'], + ['\u098F', '\u0990'], + ['\u0993', '\u09A8'], + ['\u09AA', '\u09B0'], + '\u09B2', + ['\u09B6', '\u09B9'], + '\u09BD', + '\u09CE', + ['\u09DC', '\u09DD'], + ['\u09DF', '\u09E1'], + ['\u09F0', '\u09F1'], + '\u09FC', + ['\u0A05', '\u0A0A'], + ['\u0A0F', '\u0A10'], + ['\u0A13', '\u0A28'], + ['\u0A2A', '\u0A30'], + ['\u0A32', '\u0A33'], + ['\u0A35', '\u0A36'], + ['\u0A38', '\u0A39'], + ['\u0A59', '\u0A5C'], + '\u0A5E', + ['\u0A72', '\u0A74'], + ['\u0A85', '\u0A8D'], + ['\u0A8F', '\u0A91'], + ['\u0A93', '\u0AA8'], + ['\u0AAA', '\u0AB0'], + ['\u0AB2', '\u0AB3'], + ['\u0AB5', '\u0AB9'], + '\u0ABD', + '\u0AD0', + ['\u0AE0', '\u0AE1'], + '\u0AF9', + ['\u0B05', '\u0B0C'], + ['\u0B0F', '\u0B10'], + ['\u0B13', '\u0B28'], + ['\u0B2A', '\u0B30'], + ['\u0B32', '\u0B33'], + ['\u0B35', '\u0B39'], + '\u0B3D', + ['\u0B5C', '\u0B5D'], + ['\u0B5F', '\u0B61'], + '\u0B71', + '\u0B83', + ['\u0B85', '\u0B8A'], + ['\u0B8E', '\u0B90'], + ['\u0B92', '\u0B95'], + ['\u0B99', '\u0B9A'], + '\u0B9C', + ['\u0B9E', '\u0B9F'], + ['\u0BA3', '\u0BA4'], + ['\u0BA8', '\u0BAA'], + ['\u0BAE', '\u0BB9'], + '\u0BD0', + ['\u0C05', '\u0C0C'], + ['\u0C0E', '\u0C10'], + ['\u0C12', '\u0C28'], + ['\u0C2A', '\u0C39'], + '\u0C3D', + ['\u0C58', '\u0C5A'], + ['\u0C60', '\u0C61'], + '\u0C80', + ['\u0C85', '\u0C8C'], + ['\u0C8E', '\u0C90'], + ['\u0C92', '\u0CA8'], + ['\u0CAA', '\u0CB3'], + ['\u0CB5', '\u0CB9'], + '\u0CBD', + '\u0CDE', + ['\u0CE0', '\u0CE1'], + ['\u0CF1', '\u0CF2'], + ['\u0D05', '\u0D0C'], + ['\u0D0E', '\u0D10'], + ['\u0D12', '\u0D3A'], + '\u0D3D', + '\u0D4E', + ['\u0D54', '\u0D56'], + ['\u0D5F', '\u0D61'], + ['\u0D7A', '\u0D7F'], + ['\u0D85', '\u0D96'], + ['\u0D9A', '\u0DB1'], + ['\u0DB3', '\u0DBB'], + '\u0DBD', + ['\u0DC0', '\u0DC6'], + ['\u0E01', '\u0E30'], + ['\u0E32', '\u0E33'], + ['\u0E40', '\u0E46'], + ['\u0E81', '\u0E82'], + '\u0E84', + ['\u0E87', '\u0E88'], + '\u0E8A', + '\u0E8D', + ['\u0E94', '\u0E97'], + ['\u0E99', '\u0E9F'], + ['\u0EA1', '\u0EA3'], + '\u0EA5', + '\u0EA7', + ['\u0EAA', '\u0EAB'], + ['\u0EAD', '\u0EB0'], + ['\u0EB2', '\u0EB3'], + '\u0EBD', + ['\u0EC0', '\u0EC4'], + '\u0EC6', + ['\u0EDC', '\u0EDF'], + '\u0F00', + ['\u0F40', '\u0F47'], + ['\u0F49', '\u0F6C'], + ['\u0F88', '\u0F8C'], + ['\u1000', '\u102A'], + '\u103F', + ['\u1050', '\u1055'], + ['\u105A', '\u105D'], + '\u1061', + ['\u1065', '\u1066'], + ['\u106E', '\u1070'], + ['\u1075', '\u1081'], + '\u108E', + ['\u10A0', '\u10C5'], + '\u10C7', + '\u10CD', + ['\u10D0', '\u10FA'], + ['\u10FC', '\u1248'], + ['\u124A', '\u124D'], + ['\u1250', '\u1256'], + '\u1258', + ['\u125A', '\u125D'], + ['\u1260', '\u1288'], + ['\u128A', '\u128D'], + ['\u1290', '\u12B0'], + ['\u12B2', '\u12B5'], + ['\u12B8', '\u12BE'], + '\u12C0', + ['\u12C2', '\u12C5'], + ['\u12C8', '\u12D6'], + ['\u12D8', '\u1310'], + ['\u1312', '\u1315'], + ['\u1318', '\u135A'], + ['\u1380', '\u138F'], + ['\u13A0', '\u13F5'], + ['\u13F8', '\u13FD'], + ['\u1401', '\u166C'], + ['\u166F', '\u167F'], + ['\u1681', '\u169A'], + ['\u16A0', '\u16EA'], + ['\u16EE', '\u16F8'], + ['\u1700', '\u170C'], + ['\u170E', '\u1711'], + ['\u1720', '\u1731'], + ['\u1740', '\u1751'], + ['\u1760', '\u176C'], + ['\u176E', '\u1770'], + ['\u1780', '\u17B3'], + '\u17D7', + '\u17DC', + ['\u1820', '\u1878'], + ['\u1880', '\u1884'], + ['\u1887', '\u18A8'], + '\u18AA', + ['\u18B0', '\u18F5'], + ['\u1900', '\u191E'], + ['\u1950', '\u196D'], + ['\u1970', '\u1974'], + ['\u1980', '\u19AB'], + ['\u19B0', '\u19C9'], + ['\u1A00', '\u1A16'], + ['\u1A20', '\u1A54'], + '\u1AA7', + ['\u1B05', '\u1B33'], + ['\u1B45', '\u1B4B'], + ['\u1B83', '\u1BA0'], + ['\u1BAE', '\u1BAF'], + ['\u1BBA', '\u1BE5'], + ['\u1C00', '\u1C23'], + ['\u1C4D', '\u1C4F'], + ['\u1C5A', '\u1C7D'], + ['\u1C80', '\u1C88'], + ['\u1C90', '\u1CBA'], + ['\u1CBD', '\u1CBF'], + ['\u1CE9', '\u1CEC'], + ['\u1CEE', '\u1CF1'], + ['\u1CF5', '\u1CF6'], + ['\u1D00', '\u1DBF'], + ['\u1E00', '\u1F15'], + ['\u1F18', '\u1F1D'], + ['\u1F20', '\u1F45'], + ['\u1F48', '\u1F4D'], + ['\u1F50', '\u1F57'], + '\u1F59', + '\u1F5B', + '\u1F5D', + ['\u1F5F', '\u1F7D'], + ['\u1F80', '\u1FB4'], + ['\u1FB6', '\u1FBC'], + '\u1FBE', + ['\u1FC2', '\u1FC4'], + ['\u1FC6', '\u1FCC'], + ['\u1FD0', '\u1FD3'], + ['\u1FD6', '\u1FDB'], + ['\u1FE0', '\u1FEC'], + ['\u1FF2', '\u1FF4'], + ['\u1FF6', '\u1FFC'], + '\u2071', + '\u207F', + ['\u2090', '\u209C'], + '\u2102', + '\u2107', + ['\u210A', '\u2113'], + '\u2115', + ['\u2119', '\u211D'], + '\u2124', + '\u2126', + '\u2128', + ['\u212A', '\u212D'], + ['\u212F', '\u2139'], + ['\u213C', '\u213F'], + ['\u2145', '\u2149'], + '\u214E', + ['\u2160', '\u2188'], + ['\u2C00', '\u2C2E'], + ['\u2C30', '\u2C5E'], + ['\u2C60', '\u2CE4'], + ['\u2CEB', '\u2CEE'], + ['\u2CF2', '\u2CF3'], + ['\u2D00', '\u2D25'], + '\u2D27', + '\u2D2D', + ['\u2D30', '\u2D67'], + '\u2D6F', + ['\u2D80', '\u2D96'], + ['\u2DA0', '\u2DA6'], + ['\u2DA8', '\u2DAE'], + ['\u2DB0', '\u2DB6'], + ['\u2DB8', '\u2DBE'], + ['\u2DC0', '\u2DC6'], + ['\u2DC8', '\u2DCE'], + ['\u2DD0', '\u2DD6'], + ['\u2DD8', '\u2DDE'], + '\u2E2F', + ['\u3005', '\u3007'], + ['\u3021', '\u3029'], + ['\u3031', '\u3035'], + ['\u3038', '\u303C'], + ['\u3041', '\u3096'], + ['\u309D', '\u309F'], + ['\u30A1', '\u30FA'], + ['\u30FC', '\u30FF'], + ['\u3105', '\u312F'], + ['\u3131', '\u318E'], + ['\u31A0', '\u31BA'], + ['\u31F0', '\u31FF'], + ['\u3400', '\u4DB5'], + ['\u4E00', '\u9FEF'], + ['\uA000', '\uA48C'], + ['\uA4D0', '\uA4FD'], + ['\uA500', '\uA60C'], + ['\uA610', '\uA61F'], + ['\uA62A', '\uA62B'], + ['\uA640', '\uA66E'], + ['\uA67F', '\uA69D'], + ['\uA6A0', '\uA6EF'], + ['\uA717', '\uA71F'], + ['\uA722', '\uA788'], + ['\uA78B', '\uA7B9'], + ['\uA7F7', '\uA801'], + ['\uA803', '\uA805'], + ['\uA807', '\uA80A'], + ['\uA80C', '\uA822'], + ['\uA840', '\uA873'], + ['\uA882', '\uA8B3'], + ['\uA8F2', '\uA8F7'], + '\uA8FB', + ['\uA8FD', '\uA8FE'], + ['\uA90A', '\uA925'], + ['\uA930', '\uA946'], + ['\uA960', '\uA97C'], + ['\uA984', '\uA9B2'], + '\uA9CF', + ['\uA9E0', '\uA9E4'], + ['\uA9E6', '\uA9EF'], + ['\uA9FA', '\uA9FE'], + ['\uAA00', '\uAA28'], + ['\uAA40', '\uAA42'], + ['\uAA44', '\uAA4B'], + ['\uAA60', '\uAA76'], + '\uAA7A', + ['\uAA7E', '\uAAAF'], + '\uAAB1', + ['\uAAB5', '\uAAB6'], + ['\uAAB9', '\uAABD'], + '\uAAC0', + '\uAAC2', + ['\uAADB', '\uAADD'], + ['\uAAE0', '\uAAEA'], + ['\uAAF2', '\uAAF4'], + ['\uAB01', '\uAB06'], + ['\uAB09', '\uAB0E'], + ['\uAB11', '\uAB16'], + ['\uAB20', '\uAB26'], + ['\uAB28', '\uAB2E'], + ['\uAB30', '\uAB5A'], + ['\uAB5C', '\uAB65'], + ['\uAB70', '\uABE2'], + ['\uAC00', '\uD7A3'], + ['\uD7B0', '\uD7C6'], + ['\uD7CB', '\uD7FB'], + ['\uF900', '\uFA6D'], + ['\uFA70', '\uFAD9'], + ['\uFB00', '\uFB06'], + ['\uFB13', '\uFB17'], + '\uFB1D', + ['\uFB1F', '\uFB28'], + ['\uFB2A', '\uFB36'], + ['\uFB38', '\uFB3C'], + '\uFB3E', + ['\uFB40', '\uFB41'], + ['\uFB43', '\uFB44'], + ['\uFB46', '\uFBB1'], + ['\uFBD3', '\uFD3D'], + ['\uFD50', '\uFD8F'], + ['\uFD92', '\uFDC7'], + ['\uFDF0', '\uFDFB'], + ['\uFE70', '\uFE74'], + ['\uFE76', '\uFEFC'], + ['\uFF21', '\uFF3A'], + ['\uFF41', '\uFF5A'], + ['\uFF66', '\uFFBE'], + ['\uFFC2', '\uFFC7'], + ['\uFFCA', '\uFFCF'], + ['\uFFD2', '\uFFD7'], + ['\uFFDA', '\uFFDC'], + ], + false, + false + ) + var peg$e16 = peg$literalExpectation('[', false) + var peg$e17 = peg$literalExpectation(']', false) + var peg$e18 = peg$literalExpectation('{', false) + var peg$e19 = peg$literalExpectation('}', false) + var peg$e20 = peg$literalExpectation('%', false) + var peg$e21 = peg$literalExpectation('<', false) + var peg$e22 = peg$literalExpectation('>', false) + var peg$e23 = peg$classExpectation(['@', '_'], false, false) + var peg$e24 = peg$literalExpectation('!', false) + var peg$e25 = peg$literalExpectation('(', false) + var peg$e26 = peg$literalExpectation(')', false) + var peg$e27 = peg$literalExpectation('/', false) + var peg$e28 = peg$literalExpectation('*', false) + var peg$e29 = peg$literalExpectation('?', false) + var peg$e30 = peg$literalExpectation(':', false) + var peg$e31 = peg$literalExpectation('..', false) + var peg$e32 = peg$literalExpectation('^', false) + var peg$e33 = peg$literalExpectation('struct', false) + var peg$e34 = peg$literalExpectation('target', false) + var peg$e35 = peg$literalExpectation('euclid', false) + var peg$e36 = peg$literalExpectation('slow', false) + var peg$e37 = peg$literalExpectation('rotL', false) + var peg$e38 = peg$literalExpectation('rotR', false) + var peg$e39 = peg$literalExpectation('fast', false) + var peg$e40 = peg$literalExpectation('scale', false) + var peg$e41 = peg$literalExpectation('//', false) + var peg$e42 = peg$classExpectation(['\n'], true, false) + var peg$e43 = peg$literalExpectation('cat', false) + var peg$e44 = peg$literalExpectation('$', false) + var peg$e45 = peg$literalExpectation('setcps', false) + var peg$e46 = peg$literalExpectation('setbpm', false) + var peg$e47 = peg$literalExpectation('hush', false) + var peg$e48 = peg$classExpectation( + [ + ['A', 'Z'], + ['a', 'z'], + '\xAA', + '\xB5', + '\xBA', + ['\xC0', '\xD6'], + ['\xD8', '\xF6'], + ['\xF8', '\u02C1'], + ['\u02C6', '\u02D1'], + ['\u02E0', '\u02E4'], + '\u02EC', + '\u02EE', + ['\u0370', '\u0374'], + ['\u0376', '\u0377'], + ['\u037A', '\u037D'], + '\u037F', + '\u0386', + ['\u0388', '\u038A'], + '\u038C', + ['\u038E', '\u03A1'], + ['\u03A3', '\u03F5'], + ['\u03F7', '\u0481'], + ['\u048A', '\u052F'], + ['\u0531', '\u0556'], + '\u0559', + ['\u0560', '\u0588'], + ['\u05D0', '\u05EA'], + ['\u05EF', '\u05F2'], + ['\u0620', '\u064A'], + ['\u066E', '\u066F'], + ['\u0671', '\u06D3'], + '\u06D5', + ['\u06E5', '\u06E6'], + ['\u06EE', '\u06EF'], + ['\u06FA', '\u06FC'], + '\u06FF', + '\u0710', + ['\u0712', '\u072F'], + ['\u074D', '\u07A5'], + '\u07B1', + ['\u07CA', '\u07EA'], + ['\u07F4', '\u07F5'], + '\u07FA', + ['\u0800', '\u0815'], + '\u081A', + '\u0824', + '\u0828', + ['\u0840', '\u0858'], + ['\u0860', '\u086A'], + ['\u08A0', '\u08B4'], + ['\u08B6', '\u08BD'], + ['\u0904', '\u0939'], + '\u093D', + '\u0950', + ['\u0958', '\u0961'], + ['\u0971', '\u0980'], + ['\u0985', '\u098C'], + ['\u098F', '\u0990'], + ['\u0993', '\u09A8'], + ['\u09AA', '\u09B0'], + '\u09B2', + ['\u09B6', '\u09B9'], + '\u09BD', + '\u09CE', + ['\u09DC', '\u09DD'], + ['\u09DF', '\u09E1'], + ['\u09F0', '\u09F1'], + '\u09FC', + ['\u0A05', '\u0A0A'], + ['\u0A0F', '\u0A10'], + ['\u0A13', '\u0A28'], + ['\u0A2A', '\u0A30'], + ['\u0A32', '\u0A33'], + ['\u0A35', '\u0A36'], + ['\u0A38', '\u0A39'], + ['\u0A59', '\u0A5C'], + '\u0A5E', + ['\u0A72', '\u0A74'], + ['\u0A85', '\u0A8D'], + ['\u0A8F', '\u0A91'], + ['\u0A93', '\u0AA8'], + ['\u0AAA', '\u0AB0'], + ['\u0AB2', '\u0AB3'], + ['\u0AB5', '\u0AB9'], + '\u0ABD', + '\u0AD0', + ['\u0AE0', '\u0AE1'], + '\u0AF9', + ['\u0B05', '\u0B0C'], + ['\u0B0F', '\u0B10'], + ['\u0B13', '\u0B28'], + ['\u0B2A', '\u0B30'], + ['\u0B32', '\u0B33'], + ['\u0B35', '\u0B39'], + '\u0B3D', + ['\u0B5C', '\u0B5D'], + ['\u0B5F', '\u0B61'], + '\u0B71', + '\u0B83', + ['\u0B85', '\u0B8A'], + ['\u0B8E', '\u0B90'], + ['\u0B92', '\u0B95'], + ['\u0B99', '\u0B9A'], + '\u0B9C', + ['\u0B9E', '\u0B9F'], + ['\u0BA3', '\u0BA4'], + ['\u0BA8', '\u0BAA'], + ['\u0BAE', '\u0BB9'], + '\u0BD0', + ['\u0C05', '\u0C0C'], + ['\u0C0E', '\u0C10'], + ['\u0C12', '\u0C28'], + ['\u0C2A', '\u0C39'], + '\u0C3D', + ['\u0C58', '\u0C5A'], + ['\u0C60', '\u0C61'], + '\u0C80', + ['\u0C85', '\u0C8C'], + ['\u0C8E', '\u0C90'], + ['\u0C92', '\u0CA8'], + ['\u0CAA', '\u0CB3'], + ['\u0CB5', '\u0CB9'], + '\u0CBD', + '\u0CDE', + ['\u0CE0', '\u0CE1'], + ['\u0CF1', '\u0CF2'], + ['\u0D05', '\u0D0C'], + ['\u0D0E', '\u0D10'], + ['\u0D12', '\u0D3A'], + '\u0D3D', + '\u0D4E', + ['\u0D54', '\u0D56'], + ['\u0D5F', '\u0D61'], + ['\u0D7A', '\u0D7F'], + ['\u0D85', '\u0D96'], + ['\u0D9A', '\u0DB1'], + ['\u0DB3', '\u0DBB'], + '\u0DBD', + ['\u0DC0', '\u0DC6'], + ['\u0E01', '\u0E30'], + ['\u0E32', '\u0E33'], + ['\u0E40', '\u0E46'], + ['\u0E81', '\u0E82'], + '\u0E84', + ['\u0E87', '\u0E88'], + '\u0E8A', + '\u0E8D', + ['\u0E94', '\u0E97'], + ['\u0E99', '\u0E9F'], + ['\u0EA1', '\u0EA3'], + '\u0EA5', + '\u0EA7', + ['\u0EAA', '\u0EAB'], + ['\u0EAD', '\u0EB0'], + ['\u0EB2', '\u0EB3'], + '\u0EBD', + ['\u0EC0', '\u0EC4'], + '\u0EC6', + ['\u0EDC', '\u0EDF'], + '\u0F00', + ['\u0F40', '\u0F47'], + ['\u0F49', '\u0F6C'], + ['\u0F88', '\u0F8C'], + ['\u1000', '\u102A'], + '\u103F', + ['\u1050', '\u1055'], + ['\u105A', '\u105D'], + '\u1061', + ['\u1065', '\u1066'], + ['\u106E', '\u1070'], + ['\u1075', '\u1081'], + '\u108E', + ['\u10A0', '\u10C5'], + '\u10C7', + '\u10CD', + ['\u10D0', '\u10FA'], + ['\u10FC', '\u1248'], + ['\u124A', '\u124D'], + ['\u1250', '\u1256'], + '\u1258', + ['\u125A', '\u125D'], + ['\u1260', '\u1288'], + ['\u128A', '\u128D'], + ['\u1290', '\u12B0'], + ['\u12B2', '\u12B5'], + ['\u12B8', '\u12BE'], + '\u12C0', + ['\u12C2', '\u12C5'], + ['\u12C8', '\u12D6'], + ['\u12D8', '\u1310'], + ['\u1312', '\u1315'], + ['\u1318', '\u135A'], + ['\u1380', '\u138F'], + ['\u13A0', '\u13F5'], + ['\u13F8', '\u13FD'], + ['\u1401', '\u166C'], + ['\u166F', '\u167F'], + ['\u1681', '\u169A'], + ['\u16A0', '\u16EA'], + ['\u16EE', '\u16F8'], + ['\u1700', '\u170C'], + ['\u170E', '\u1711'], + ['\u1720', '\u1731'], + ['\u1740', '\u1751'], + ['\u1760', '\u176C'], + ['\u176E', '\u1770'], + ['\u1780', '\u17B3'], + '\u17D7', + '\u17DC', + ['\u1820', '\u1878'], + ['\u1880', '\u1884'], + ['\u1887', '\u18A8'], + '\u18AA', + ['\u18B0', '\u18F5'], + ['\u1900', '\u191E'], + ['\u1950', '\u196D'], + ['\u1970', '\u1974'], + ['\u1980', '\u19AB'], + ['\u19B0', '\u19C9'], + ['\u1A00', '\u1A16'], + ['\u1A20', '\u1A54'], + '\u1AA7', + ['\u1B05', '\u1B33'], + ['\u1B45', '\u1B4B'], + ['\u1B83', '\u1BA0'], + ['\u1BAE', '\u1BAF'], + ['\u1BBA', '\u1BE5'], + ['\u1C00', '\u1C23'], + ['\u1C4D', '\u1C4F'], + ['\u1C5A', '\u1C7D'], + ['\u1C80', '\u1C88'], + ['\u1C90', '\u1CBA'], + ['\u1CBD', '\u1CBF'], + ['\u1CE9', '\u1CEC'], + ['\u1CEE', '\u1CF1'], + ['\u1CF5', '\u1CF6'], + ['\u1D00', '\u1DBF'], + ['\u1E00', '\u1F15'], + ['\u1F18', '\u1F1D'], + ['\u1F20', '\u1F45'], + ['\u1F48', '\u1F4D'], + ['\u1F50', '\u1F57'], + '\u1F59', + '\u1F5B', + '\u1F5D', + ['\u1F5F', '\u1F7D'], + ['\u1F80', '\u1FB4'], + ['\u1FB6', '\u1FBC'], + '\u1FBE', + ['\u1FC2', '\u1FC4'], + ['\u1FC6', '\u1FCC'], + ['\u1FD0', '\u1FD3'], + ['\u1FD6', '\u1FDB'], + ['\u1FE0', '\u1FEC'], + ['\u1FF2', '\u1FF4'], + ['\u1FF6', '\u1FFC'], + '\u2071', + '\u207F', + ['\u2090', '\u209C'], + '\u2102', + '\u2107', + ['\u210A', '\u2113'], + '\u2115', + ['\u2119', '\u211D'], + '\u2124', + '\u2126', + '\u2128', + ['\u212A', '\u212D'], + ['\u212F', '\u2139'], + ['\u213C', '\u213F'], + ['\u2145', '\u2149'], + '\u214E', + ['\u2160', '\u2188'], + ['\u2C00', '\u2C2E'], + ['\u2C30', '\u2C5E'], + ['\u2C60', '\u2CE4'], + ['\u2CEB', '\u2CEE'], + ['\u2CF2', '\u2CF3'], + ['\u2D00', '\u2D25'], + '\u2D27', + '\u2D2D', + ['\u2D30', '\u2D67'], + '\u2D6F', + ['\u2D80', '\u2D96'], + ['\u2DA0', '\u2DA6'], + ['\u2DA8', '\u2DAE'], + ['\u2DB0', '\u2DB6'], + ['\u2DB8', '\u2DBE'], + ['\u2DC0', '\u2DC6'], + ['\u2DC8', '\u2DCE'], + ['\u2DD0', '\u2DD6'], + ['\u2DD8', '\u2DDE'], + '\u2E2F', + ['\u3005', '\u3007'], + ['\u3021', '\u3029'], + ['\u3031', '\u3035'], + ['\u3038', '\u303C'], + ['\u3041', '\u3096'], + ['\u309D', '\u309F'], + ['\u30A1', '\u30FA'], + ['\u30FC', '\u30FF'], + ['\u3105', '\u312F'], + ['\u3131', '\u318E'], + ['\u31A0', '\u31BA'], + ['\u31F0', '\u31FF'], + ['\u3400', '\u4DB5'], + ['\u4E00', '\u9FEF'], + ['\uA000', '\uA48C'], + ['\uA4D0', '\uA4FD'], + ['\uA500', '\uA60C'], + ['\uA610', '\uA61F'], + ['\uA62A', '\uA62B'], + ['\uA640', '\uA66E'], + ['\uA67F', '\uA69D'], + ['\uA6A0', '\uA6EF'], + ['\uA717', '\uA71F'], + ['\uA722', '\uA788'], + ['\uA78B', '\uA7B9'], + ['\uA7F7', '\uA801'], + ['\uA803', '\uA805'], + ['\uA807', '\uA80A'], + ['\uA80C', '\uA822'], + ['\uA840', '\uA873'], + ['\uA882', '\uA8B3'], + ['\uA8F2', '\uA8F7'], + '\uA8FB', + ['\uA8FD', '\uA8FE'], + ['\uA90A', '\uA925'], + ['\uA930', '\uA946'], + ['\uA960', '\uA97C'], + ['\uA984', '\uA9B2'], + '\uA9CF', + ['\uA9E0', '\uA9E4'], + ['\uA9E6', '\uA9EF'], + ['\uA9FA', '\uA9FE'], + ['\uAA00', '\uAA28'], + ['\uAA40', '\uAA42'], + ['\uAA44', '\uAA4B'], + ['\uAA60', '\uAA76'], + '\uAA7A', + ['\uAA7E', '\uAAAF'], + '\uAAB1', + ['\uAAB5', '\uAAB6'], + ['\uAAB9', '\uAABD'], + '\uAAC0', + '\uAAC2', + ['\uAADB', '\uAADD'], + ['\uAAE0', '\uAAEA'], + ['\uAAF2', '\uAAF4'], + ['\uAB01', '\uAB06'], + ['\uAB09', '\uAB0E'], + ['\uAB11', '\uAB16'], + ['\uAB20', '\uAB26'], + ['\uAB28', '\uAB2E'], + ['\uAB30', '\uAB5A'], + ['\uAB5C', '\uAB65'], + ['\uAB70', '\uABE2'], + ['\uAC00', '\uD7A3'], + ['\uD7B0', '\uD7C6'], + ['\uD7CB', '\uD7FB'], + ['\uF900', '\uFA6D'], + ['\uFA70', '\uFAD9'], + ['\uFB00', '\uFB06'], + ['\uFB13', '\uFB17'], + '\uFB1D', + ['\uFB1F', '\uFB28'], + ['\uFB2A', '\uFB36'], + ['\uFB38', '\uFB3C'], + '\uFB3E', + ['\uFB40', '\uFB41'], + ['\uFB43', '\uFB44'], + ['\uFB46', '\uFBB1'], + ['\uFBD3', '\uFD3D'], + ['\uFD50', '\uFD8F'], + ['\uFD92', '\uFDC7'], + ['\uFDF0', '\uFDFB'], + ['\uFE70', '\uFE74'], + ['\uFE76', '\uFEFC'], + ['\uFF21', '\uFF3A'], + ['\uFF41', '\uFF5A'], + ['\uFF66', '\uFFBE'], + ['\uFFC2', '\uFFC7'], + ['\uFFCA', '\uFFCF'], + ['\uFFD2', '\uFFD7'], + ['\uFFDA', '\uFFDC'], + ], + false, + false + ) + var peg$e49 = peg$classExpectation( + [ + ['a', 'z'], + '\xB5', + ['\xDF', '\xF6'], + ['\xF8', '\xFF'], + '\u0101', + '\u0103', + '\u0105', + '\u0107', + '\u0109', + '\u010B', + '\u010D', + '\u010F', + '\u0111', + '\u0113', + '\u0115', + '\u0117', + '\u0119', + '\u011B', + '\u011D', + '\u011F', + '\u0121', + '\u0123', + '\u0125', + '\u0127', + '\u0129', + '\u012B', + '\u012D', + '\u012F', + '\u0131', + '\u0133', + '\u0135', + ['\u0137', '\u0138'], + '\u013A', + '\u013C', + '\u013E', + '\u0140', + '\u0142', + '\u0144', + '\u0146', + ['\u0148', '\u0149'], + '\u014B', + '\u014D', + '\u014F', + '\u0151', + '\u0153', + '\u0155', + '\u0157', + '\u0159', + '\u015B', + '\u015D', + '\u015F', + '\u0161', + '\u0163', + '\u0165', + '\u0167', + '\u0169', + '\u016B', + '\u016D', + '\u016F', + '\u0171', + '\u0173', + '\u0175', + '\u0177', + '\u017A', + '\u017C', + ['\u017E', '\u0180'], + '\u0183', + '\u0185', + '\u0188', + ['\u018C', '\u018D'], + '\u0192', + '\u0195', + ['\u0199', '\u019B'], + '\u019E', + '\u01A1', + '\u01A3', + '\u01A5', + '\u01A8', + ['\u01AA', '\u01AB'], + '\u01AD', + '\u01B0', + '\u01B4', + '\u01B6', + ['\u01B9', '\u01BA'], + ['\u01BD', '\u01BF'], + '\u01C6', + '\u01C9', + '\u01CC', + '\u01CE', + '\u01D0', + '\u01D2', + '\u01D4', + '\u01D6', + '\u01D8', + '\u01DA', + ['\u01DC', '\u01DD'], + '\u01DF', + '\u01E1', + '\u01E3', + '\u01E5', + '\u01E7', + '\u01E9', + '\u01EB', + '\u01ED', + ['\u01EF', '\u01F0'], + '\u01F3', + '\u01F5', + '\u01F9', + '\u01FB', + '\u01FD', + '\u01FF', + '\u0201', + '\u0203', + '\u0205', + '\u0207', + '\u0209', + '\u020B', + '\u020D', + '\u020F', + '\u0211', + '\u0213', + '\u0215', + '\u0217', + '\u0219', + '\u021B', + '\u021D', + '\u021F', + '\u0221', + '\u0223', + '\u0225', + '\u0227', + '\u0229', + '\u022B', + '\u022D', + '\u022F', + '\u0231', + ['\u0233', '\u0239'], + '\u023C', + ['\u023F', '\u0240'], + '\u0242', + '\u0247', + '\u0249', + '\u024B', + '\u024D', + ['\u024F', '\u0293'], + ['\u0295', '\u02AF'], + '\u0371', + '\u0373', + '\u0377', + ['\u037B', '\u037D'], + '\u0390', + ['\u03AC', '\u03CE'], + ['\u03D0', '\u03D1'], + ['\u03D5', '\u03D7'], + '\u03D9', + '\u03DB', + '\u03DD', + '\u03DF', + '\u03E1', + '\u03E3', + '\u03E5', + '\u03E7', + '\u03E9', + '\u03EB', + '\u03ED', + ['\u03EF', '\u03F3'], + '\u03F5', + '\u03F8', + ['\u03FB', '\u03FC'], + ['\u0430', '\u045F'], + '\u0461', + '\u0463', + '\u0465', + '\u0467', + '\u0469', + '\u046B', + '\u046D', + '\u046F', + '\u0471', + '\u0473', + '\u0475', + '\u0477', + '\u0479', + '\u047B', + '\u047D', + '\u047F', + '\u0481', + '\u048B', + '\u048D', + '\u048F', + '\u0491', + '\u0493', + '\u0495', + '\u0497', + '\u0499', + '\u049B', + '\u049D', + '\u049F', + '\u04A1', + '\u04A3', + '\u04A5', + '\u04A7', + '\u04A9', + '\u04AB', + '\u04AD', + '\u04AF', + '\u04B1', + '\u04B3', + '\u04B5', + '\u04B7', + '\u04B9', + '\u04BB', + '\u04BD', + '\u04BF', + '\u04C2', + '\u04C4', + '\u04C6', + '\u04C8', + '\u04CA', + '\u04CC', + ['\u04CE', '\u04CF'], + '\u04D1', + '\u04D3', + '\u04D5', + '\u04D7', + '\u04D9', + '\u04DB', + '\u04DD', + '\u04DF', + '\u04E1', + '\u04E3', + '\u04E5', + '\u04E7', + '\u04E9', + '\u04EB', + '\u04ED', + '\u04EF', + '\u04F1', + '\u04F3', + '\u04F5', + '\u04F7', + '\u04F9', + '\u04FB', + '\u04FD', + '\u04FF', + '\u0501', + '\u0503', + '\u0505', + '\u0507', + '\u0509', + '\u050B', + '\u050D', + '\u050F', + '\u0511', + '\u0513', + '\u0515', + '\u0517', + '\u0519', + '\u051B', + '\u051D', + '\u051F', + '\u0521', + '\u0523', + '\u0525', + '\u0527', + '\u0529', + '\u052B', + '\u052D', + '\u052F', + ['\u0560', '\u0588'], + ['\u10D0', '\u10FA'], + ['\u10FD', '\u10FF'], + ['\u13F8', '\u13FD'], + ['\u1C80', '\u1C88'], + ['\u1D00', '\u1D2B'], + ['\u1D6B', '\u1D77'], + ['\u1D79', '\u1D9A'], + '\u1E01', + '\u1E03', + '\u1E05', + '\u1E07', + '\u1E09', + '\u1E0B', + '\u1E0D', + '\u1E0F', + '\u1E11', + '\u1E13', + '\u1E15', + '\u1E17', + '\u1E19', + '\u1E1B', + '\u1E1D', + '\u1E1F', + '\u1E21', + '\u1E23', + '\u1E25', + '\u1E27', + '\u1E29', + '\u1E2B', + '\u1E2D', + '\u1E2F', + '\u1E31', + '\u1E33', + '\u1E35', + '\u1E37', + '\u1E39', + '\u1E3B', + '\u1E3D', + '\u1E3F', + '\u1E41', + '\u1E43', + '\u1E45', + '\u1E47', + '\u1E49', + '\u1E4B', + '\u1E4D', + '\u1E4F', + '\u1E51', + '\u1E53', + '\u1E55', + '\u1E57', + '\u1E59', + '\u1E5B', + '\u1E5D', + '\u1E5F', + '\u1E61', + '\u1E63', + '\u1E65', + '\u1E67', + '\u1E69', + '\u1E6B', + '\u1E6D', + '\u1E6F', + '\u1E71', + '\u1E73', + '\u1E75', + '\u1E77', + '\u1E79', + '\u1E7B', + '\u1E7D', + '\u1E7F', + '\u1E81', + '\u1E83', + '\u1E85', + '\u1E87', + '\u1E89', + '\u1E8B', + '\u1E8D', + '\u1E8F', + '\u1E91', + '\u1E93', + ['\u1E95', '\u1E9D'], + '\u1E9F', + '\u1EA1', + '\u1EA3', + '\u1EA5', + '\u1EA7', + '\u1EA9', + '\u1EAB', + '\u1EAD', + '\u1EAF', + '\u1EB1', + '\u1EB3', + '\u1EB5', + '\u1EB7', + '\u1EB9', + '\u1EBB', + '\u1EBD', + '\u1EBF', + '\u1EC1', + '\u1EC3', + '\u1EC5', + '\u1EC7', + '\u1EC9', + '\u1ECB', + '\u1ECD', + '\u1ECF', + '\u1ED1', + '\u1ED3', + '\u1ED5', + '\u1ED7', + '\u1ED9', + '\u1EDB', + '\u1EDD', + '\u1EDF', + '\u1EE1', + '\u1EE3', + '\u1EE5', + '\u1EE7', + '\u1EE9', + '\u1EEB', + '\u1EED', + '\u1EEF', + '\u1EF1', + '\u1EF3', + '\u1EF5', + '\u1EF7', + '\u1EF9', + '\u1EFB', + '\u1EFD', + ['\u1EFF', '\u1F07'], + ['\u1F10', '\u1F15'], + ['\u1F20', '\u1F27'], + ['\u1F30', '\u1F37'], + ['\u1F40', '\u1F45'], + ['\u1F50', '\u1F57'], + ['\u1F60', '\u1F67'], + ['\u1F70', '\u1F7D'], + ['\u1F80', '\u1F87'], + ['\u1F90', '\u1F97'], + ['\u1FA0', '\u1FA7'], + ['\u1FB0', '\u1FB4'], + ['\u1FB6', '\u1FB7'], + '\u1FBE', + ['\u1FC2', '\u1FC4'], + ['\u1FC6', '\u1FC7'], + ['\u1FD0', '\u1FD3'], + ['\u1FD6', '\u1FD7'], + ['\u1FE0', '\u1FE7'], + ['\u1FF2', '\u1FF4'], + ['\u1FF6', '\u1FF7'], + '\u210A', + ['\u210E', '\u210F'], + '\u2113', + '\u212F', + '\u2134', + '\u2139', + ['\u213C', '\u213D'], + ['\u2146', '\u2149'], + '\u214E', + '\u2184', + ['\u2C30', '\u2C5E'], + '\u2C61', + ['\u2C65', '\u2C66'], + '\u2C68', + '\u2C6A', + '\u2C6C', + '\u2C71', + ['\u2C73', '\u2C74'], + ['\u2C76', '\u2C7B'], + '\u2C81', + '\u2C83', + '\u2C85', + '\u2C87', + '\u2C89', + '\u2C8B', + '\u2C8D', + '\u2C8F', + '\u2C91', + '\u2C93', + '\u2C95', + '\u2C97', + '\u2C99', + '\u2C9B', + '\u2C9D', + '\u2C9F', + '\u2CA1', + '\u2CA3', + '\u2CA5', + '\u2CA7', + '\u2CA9', + '\u2CAB', + '\u2CAD', + '\u2CAF', + '\u2CB1', + '\u2CB3', + '\u2CB5', + '\u2CB7', + '\u2CB9', + '\u2CBB', + '\u2CBD', + '\u2CBF', + '\u2CC1', + '\u2CC3', + '\u2CC5', + '\u2CC7', + '\u2CC9', + '\u2CCB', + '\u2CCD', + '\u2CCF', + '\u2CD1', + '\u2CD3', + '\u2CD5', + '\u2CD7', + '\u2CD9', + '\u2CDB', + '\u2CDD', + '\u2CDF', + '\u2CE1', + ['\u2CE3', '\u2CE4'], + '\u2CEC', + '\u2CEE', + '\u2CF3', + ['\u2D00', '\u2D25'], + '\u2D27', + '\u2D2D', + '\uA641', + '\uA643', + '\uA645', + '\uA647', + '\uA649', + '\uA64B', + '\uA64D', + '\uA64F', + '\uA651', + '\uA653', + '\uA655', + '\uA657', + '\uA659', + '\uA65B', + '\uA65D', + '\uA65F', + '\uA661', + '\uA663', + '\uA665', + '\uA667', + '\uA669', + '\uA66B', + '\uA66D', + '\uA681', + '\uA683', + '\uA685', + '\uA687', + '\uA689', + '\uA68B', + '\uA68D', + '\uA68F', + '\uA691', + '\uA693', + '\uA695', + '\uA697', + '\uA699', + '\uA69B', + '\uA723', + '\uA725', + '\uA727', + '\uA729', + '\uA72B', + '\uA72D', + ['\uA72F', '\uA731'], + '\uA733', + '\uA735', + '\uA737', + '\uA739', + '\uA73B', + '\uA73D', + '\uA73F', + '\uA741', + '\uA743', + '\uA745', + '\uA747', + '\uA749', + '\uA74B', + '\uA74D', + '\uA74F', + '\uA751', + '\uA753', + '\uA755', + '\uA757', + '\uA759', + '\uA75B', + '\uA75D', + '\uA75F', + '\uA761', + '\uA763', + '\uA765', + '\uA767', + '\uA769', + '\uA76B', + '\uA76D', + '\uA76F', + ['\uA771', '\uA778'], + '\uA77A', + '\uA77C', + '\uA77F', + '\uA781', + '\uA783', + '\uA785', + '\uA787', + '\uA78C', + '\uA78E', + '\uA791', + ['\uA793', '\uA795'], + '\uA797', + '\uA799', + '\uA79B', + '\uA79D', + '\uA79F', + '\uA7A1', + '\uA7A3', + '\uA7A5', + '\uA7A7', + '\uA7A9', + '\uA7AF', + '\uA7B5', + '\uA7B7', + '\uA7B9', + '\uA7FA', + ['\uAB30', '\uAB5A'], + ['\uAB60', '\uAB65'], + ['\uAB70', '\uABBF'], + ['\uFB00', '\uFB06'], + ['\uFB13', '\uFB17'], + ['\uFF41', '\uFF5A'], + ], + false, + false + ) + var peg$e50 = peg$classExpectation( + [ + ['\u02B0', '\u02C1'], + ['\u02C6', '\u02D1'], + ['\u02E0', '\u02E4'], + '\u02EC', + '\u02EE', + '\u0374', + '\u037A', + '\u0559', + '\u0640', + ['\u06E5', '\u06E6'], + ['\u07F4', '\u07F5'], + '\u07FA', + '\u081A', + '\u0824', + '\u0828', + '\u0971', + '\u0E46', + '\u0EC6', + '\u10FC', + '\u17D7', + '\u1843', + '\u1AA7', + ['\u1C78', '\u1C7D'], + ['\u1D2C', '\u1D6A'], + '\u1D78', + ['\u1D9B', '\u1DBF'], + '\u2071', + '\u207F', + ['\u2090', '\u209C'], + ['\u2C7C', '\u2C7D'], + '\u2D6F', + '\u2E2F', + '\u3005', + ['\u3031', '\u3035'], + '\u303B', + ['\u309D', '\u309E'], + ['\u30FC', '\u30FE'], + '\uA015', + ['\uA4F8', '\uA4FD'], + '\uA60C', + '\uA67F', + ['\uA69C', '\uA69D'], + ['\uA717', '\uA71F'], + '\uA770', + '\uA788', + ['\uA7F8', '\uA7F9'], + '\uA9CF', + '\uA9E6', + '\uAA70', + '\uAADD', + ['\uAAF3', '\uAAF4'], + ['\uAB5C', '\uAB5F'], + '\uFF70', + ['\uFF9E', '\uFF9F'], + ], + false, + false + ) + var peg$e51 = peg$classExpectation( + [ + '\xAA', + '\xBA', + '\u01BB', + ['\u01C0', '\u01C3'], + '\u0294', + ['\u05D0', '\u05EA'], + ['\u05EF', '\u05F2'], + ['\u0620', '\u063F'], + ['\u0641', '\u064A'], + ['\u066E', '\u066F'], + ['\u0671', '\u06D3'], + '\u06D5', + ['\u06EE', '\u06EF'], + ['\u06FA', '\u06FC'], + '\u06FF', + '\u0710', + ['\u0712', '\u072F'], + ['\u074D', '\u07A5'], + '\u07B1', + ['\u07CA', '\u07EA'], + ['\u0800', '\u0815'], + ['\u0840', '\u0858'], + ['\u0860', '\u086A'], + ['\u08A0', '\u08B4'], + ['\u08B6', '\u08BD'], + ['\u0904', '\u0939'], + '\u093D', + '\u0950', + ['\u0958', '\u0961'], + ['\u0972', '\u0980'], + ['\u0985', '\u098C'], + ['\u098F', '\u0990'], + ['\u0993', '\u09A8'], + ['\u09AA', '\u09B0'], + '\u09B2', + ['\u09B6', '\u09B9'], + '\u09BD', + '\u09CE', + ['\u09DC', '\u09DD'], + ['\u09DF', '\u09E1'], + ['\u09F0', '\u09F1'], + '\u09FC', + ['\u0A05', '\u0A0A'], + ['\u0A0F', '\u0A10'], + ['\u0A13', '\u0A28'], + ['\u0A2A', '\u0A30'], + ['\u0A32', '\u0A33'], + ['\u0A35', '\u0A36'], + ['\u0A38', '\u0A39'], + ['\u0A59', '\u0A5C'], + '\u0A5E', + ['\u0A72', '\u0A74'], + ['\u0A85', '\u0A8D'], + ['\u0A8F', '\u0A91'], + ['\u0A93', '\u0AA8'], + ['\u0AAA', '\u0AB0'], + ['\u0AB2', '\u0AB3'], + ['\u0AB5', '\u0AB9'], + '\u0ABD', + '\u0AD0', + ['\u0AE0', '\u0AE1'], + '\u0AF9', + ['\u0B05', '\u0B0C'], + ['\u0B0F', '\u0B10'], + ['\u0B13', '\u0B28'], + ['\u0B2A', '\u0B30'], + ['\u0B32', '\u0B33'], + ['\u0B35', '\u0B39'], + '\u0B3D', + ['\u0B5C', '\u0B5D'], + ['\u0B5F', '\u0B61'], + '\u0B71', + '\u0B83', + ['\u0B85', '\u0B8A'], + ['\u0B8E', '\u0B90'], + ['\u0B92', '\u0B95'], + ['\u0B99', '\u0B9A'], + '\u0B9C', + ['\u0B9E', '\u0B9F'], + ['\u0BA3', '\u0BA4'], + ['\u0BA8', '\u0BAA'], + ['\u0BAE', '\u0BB9'], + '\u0BD0', + ['\u0C05', '\u0C0C'], + ['\u0C0E', '\u0C10'], + ['\u0C12', '\u0C28'], + ['\u0C2A', '\u0C39'], + '\u0C3D', + ['\u0C58', '\u0C5A'], + ['\u0C60', '\u0C61'], + '\u0C80', + ['\u0C85', '\u0C8C'], + ['\u0C8E', '\u0C90'], + ['\u0C92', '\u0CA8'], + ['\u0CAA', '\u0CB3'], + ['\u0CB5', '\u0CB9'], + '\u0CBD', + '\u0CDE', + ['\u0CE0', '\u0CE1'], + ['\u0CF1', '\u0CF2'], + ['\u0D05', '\u0D0C'], + ['\u0D0E', '\u0D10'], + ['\u0D12', '\u0D3A'], + '\u0D3D', + '\u0D4E', + ['\u0D54', '\u0D56'], + ['\u0D5F', '\u0D61'], + ['\u0D7A', '\u0D7F'], + ['\u0D85', '\u0D96'], + ['\u0D9A', '\u0DB1'], + ['\u0DB3', '\u0DBB'], + '\u0DBD', + ['\u0DC0', '\u0DC6'], + ['\u0E01', '\u0E30'], + ['\u0E32', '\u0E33'], + ['\u0E40', '\u0E45'], + ['\u0E81', '\u0E82'], + '\u0E84', + ['\u0E87', '\u0E88'], + '\u0E8A', + '\u0E8D', + ['\u0E94', '\u0E97'], + ['\u0E99', '\u0E9F'], + ['\u0EA1', '\u0EA3'], + '\u0EA5', + '\u0EA7', + ['\u0EAA', '\u0EAB'], + ['\u0EAD', '\u0EB0'], + ['\u0EB2', '\u0EB3'], + '\u0EBD', + ['\u0EC0', '\u0EC4'], + ['\u0EDC', '\u0EDF'], + '\u0F00', + ['\u0F40', '\u0F47'], + ['\u0F49', '\u0F6C'], + ['\u0F88', '\u0F8C'], + ['\u1000', '\u102A'], + '\u103F', + ['\u1050', '\u1055'], + ['\u105A', '\u105D'], + '\u1061', + ['\u1065', '\u1066'], + ['\u106E', '\u1070'], + ['\u1075', '\u1081'], + '\u108E', + ['\u1100', '\u1248'], + ['\u124A', '\u124D'], + ['\u1250', '\u1256'], + '\u1258', + ['\u125A', '\u125D'], + ['\u1260', '\u1288'], + ['\u128A', '\u128D'], + ['\u1290', '\u12B0'], + ['\u12B2', '\u12B5'], + ['\u12B8', '\u12BE'], + '\u12C0', + ['\u12C2', '\u12C5'], + ['\u12C8', '\u12D6'], + ['\u12D8', '\u1310'], + ['\u1312', '\u1315'], + ['\u1318', '\u135A'], + ['\u1380', '\u138F'], + ['\u1401', '\u166C'], + ['\u166F', '\u167F'], + ['\u1681', '\u169A'], + ['\u16A0', '\u16EA'], + ['\u16F1', '\u16F8'], + ['\u1700', '\u170C'], + ['\u170E', '\u1711'], + ['\u1720', '\u1731'], + ['\u1740', '\u1751'], + ['\u1760', '\u176C'], + ['\u176E', '\u1770'], + ['\u1780', '\u17B3'], + '\u17DC', + ['\u1820', '\u1842'], + ['\u1844', '\u1878'], + ['\u1880', '\u1884'], + ['\u1887', '\u18A8'], + '\u18AA', + ['\u18B0', '\u18F5'], + ['\u1900', '\u191E'], + ['\u1950', '\u196D'], + ['\u1970', '\u1974'], + ['\u1980', '\u19AB'], + ['\u19B0', '\u19C9'], + ['\u1A00', '\u1A16'], + ['\u1A20', '\u1A54'], + ['\u1B05', '\u1B33'], + ['\u1B45', '\u1B4B'], + ['\u1B83', '\u1BA0'], + ['\u1BAE', '\u1BAF'], + ['\u1BBA', '\u1BE5'], + ['\u1C00', '\u1C23'], + ['\u1C4D', '\u1C4F'], + ['\u1C5A', '\u1C77'], + ['\u1CE9', '\u1CEC'], + ['\u1CEE', '\u1CF1'], + ['\u1CF5', '\u1CF6'], + ['\u2135', '\u2138'], + ['\u2D30', '\u2D67'], + ['\u2D80', '\u2D96'], + ['\u2DA0', '\u2DA6'], + ['\u2DA8', '\u2DAE'], + ['\u2DB0', '\u2DB6'], + ['\u2DB8', '\u2DBE'], + ['\u2DC0', '\u2DC6'], + ['\u2DC8', '\u2DCE'], + ['\u2DD0', '\u2DD6'], + ['\u2DD8', '\u2DDE'], + '\u3006', + '\u303C', + ['\u3041', '\u3096'], + '\u309F', + ['\u30A1', '\u30FA'], + '\u30FF', + ['\u3105', '\u312F'], + ['\u3131', '\u318E'], + ['\u31A0', '\u31BA'], + ['\u31F0', '\u31FF'], + ['\u3400', '\u4DB5'], + ['\u4E00', '\u9FEF'], + ['\uA000', '\uA014'], + ['\uA016', '\uA48C'], + ['\uA4D0', '\uA4F7'], + ['\uA500', '\uA60B'], + ['\uA610', '\uA61F'], + ['\uA62A', '\uA62B'], + '\uA66E', + ['\uA6A0', '\uA6E5'], + '\uA78F', + '\uA7F7', + ['\uA7FB', '\uA801'], + ['\uA803', '\uA805'], + ['\uA807', '\uA80A'], + ['\uA80C', '\uA822'], + ['\uA840', '\uA873'], + ['\uA882', '\uA8B3'], + ['\uA8F2', '\uA8F7'], + '\uA8FB', + ['\uA8FD', '\uA8FE'], + ['\uA90A', '\uA925'], + ['\uA930', '\uA946'], + ['\uA960', '\uA97C'], + ['\uA984', '\uA9B2'], + ['\uA9E0', '\uA9E4'], + ['\uA9E7', '\uA9EF'], + ['\uA9FA', '\uA9FE'], + ['\uAA00', '\uAA28'], + ['\uAA40', '\uAA42'], + ['\uAA44', '\uAA4B'], + ['\uAA60', '\uAA6F'], + ['\uAA71', '\uAA76'], + '\uAA7A', + ['\uAA7E', '\uAAAF'], + '\uAAB1', + ['\uAAB5', '\uAAB6'], + ['\uAAB9', '\uAABD'], + '\uAAC0', + '\uAAC2', + ['\uAADB', '\uAADC'], + ['\uAAE0', '\uAAEA'], + '\uAAF2', + ['\uAB01', '\uAB06'], + ['\uAB09', '\uAB0E'], + ['\uAB11', '\uAB16'], + ['\uAB20', '\uAB26'], + ['\uAB28', '\uAB2E'], + ['\uABC0', '\uABE2'], + ['\uAC00', '\uD7A3'], + ['\uD7B0', '\uD7C6'], + ['\uD7CB', '\uD7FB'], + ['\uF900', '\uFA6D'], + ['\uFA70', '\uFAD9'], + '\uFB1D', + ['\uFB1F', '\uFB28'], + ['\uFB2A', '\uFB36'], + ['\uFB38', '\uFB3C'], + '\uFB3E', + ['\uFB40', '\uFB41'], + ['\uFB43', '\uFB44'], + ['\uFB46', '\uFBB1'], + ['\uFBD3', '\uFD3D'], + ['\uFD50', '\uFD8F'], + ['\uFD92', '\uFDC7'], + ['\uFDF0', '\uFDFB'], + ['\uFE70', '\uFE74'], + ['\uFE76', '\uFEFC'], + ['\uFF66', '\uFF6F'], + ['\uFF71', '\uFF9D'], + ['\uFFA0', '\uFFBE'], + ['\uFFC2', '\uFFC7'], + ['\uFFCA', '\uFFCF'], + ['\uFFD2', '\uFFD7'], + ['\uFFDA', '\uFFDC'], + ], + false, + false + ) + var peg$e52 = peg$classExpectation( + [ + '\u01C5', + '\u01C8', + '\u01CB', + '\u01F2', + ['\u1F88', '\u1F8F'], + ['\u1F98', '\u1F9F'], + ['\u1FA8', '\u1FAF'], + '\u1FBC', + '\u1FCC', + '\u1FFC', + ], + false, + false + ) + var peg$e53 = peg$classExpectation( + [ + ['A', 'Z'], + ['\xC0', '\xD6'], + ['\xD8', '\xDE'], + '\u0100', + '\u0102', + '\u0104', + '\u0106', + '\u0108', + '\u010A', + '\u010C', + '\u010E', + '\u0110', + '\u0112', + '\u0114', + '\u0116', + '\u0118', + '\u011A', + '\u011C', + '\u011E', + '\u0120', + '\u0122', + '\u0124', + '\u0126', + '\u0128', + '\u012A', + '\u012C', + '\u012E', + '\u0130', + '\u0132', + '\u0134', + '\u0136', + '\u0139', + '\u013B', + '\u013D', + '\u013F', + '\u0141', + '\u0143', + '\u0145', + '\u0147', + '\u014A', + '\u014C', + '\u014E', + '\u0150', + '\u0152', + '\u0154', + '\u0156', + '\u0158', + '\u015A', + '\u015C', + '\u015E', + '\u0160', + '\u0162', + '\u0164', + '\u0166', + '\u0168', + '\u016A', + '\u016C', + '\u016E', + '\u0170', + '\u0172', + '\u0174', + '\u0176', + ['\u0178', '\u0179'], + '\u017B', + '\u017D', + ['\u0181', '\u0182'], + '\u0184', + ['\u0186', '\u0187'], + ['\u0189', '\u018B'], + ['\u018E', '\u0191'], + ['\u0193', '\u0194'], + ['\u0196', '\u0198'], + ['\u019C', '\u019D'], + ['\u019F', '\u01A0'], + '\u01A2', + '\u01A4', + ['\u01A6', '\u01A7'], + '\u01A9', + '\u01AC', + ['\u01AE', '\u01AF'], + ['\u01B1', '\u01B3'], + '\u01B5', + ['\u01B7', '\u01B8'], + '\u01BC', + '\u01C4', + '\u01C7', + '\u01CA', + '\u01CD', + '\u01CF', + '\u01D1', + '\u01D3', + '\u01D5', + '\u01D7', + '\u01D9', + '\u01DB', + '\u01DE', + '\u01E0', + '\u01E2', + '\u01E4', + '\u01E6', + '\u01E8', + '\u01EA', + '\u01EC', + '\u01EE', + '\u01F1', + '\u01F4', + ['\u01F6', '\u01F8'], + '\u01FA', + '\u01FC', + '\u01FE', + '\u0200', + '\u0202', + '\u0204', + '\u0206', + '\u0208', + '\u020A', + '\u020C', + '\u020E', + '\u0210', + '\u0212', + '\u0214', + '\u0216', + '\u0218', + '\u021A', + '\u021C', + '\u021E', + '\u0220', + '\u0222', + '\u0224', + '\u0226', + '\u0228', + '\u022A', + '\u022C', + '\u022E', + '\u0230', + '\u0232', + ['\u023A', '\u023B'], + ['\u023D', '\u023E'], + '\u0241', + ['\u0243', '\u0246'], + '\u0248', + '\u024A', + '\u024C', + '\u024E', + '\u0370', + '\u0372', + '\u0376', + '\u037F', + '\u0386', + ['\u0388', '\u038A'], + '\u038C', + ['\u038E', '\u038F'], + ['\u0391', '\u03A1'], + ['\u03A3', '\u03AB'], + '\u03CF', + ['\u03D2', '\u03D4'], + '\u03D8', + '\u03DA', + '\u03DC', + '\u03DE', + '\u03E0', + '\u03E2', + '\u03E4', + '\u03E6', + '\u03E8', + '\u03EA', + '\u03EC', + '\u03EE', + '\u03F4', + '\u03F7', + ['\u03F9', '\u03FA'], + ['\u03FD', '\u042F'], + '\u0460', + '\u0462', + '\u0464', + '\u0466', + '\u0468', + '\u046A', + '\u046C', + '\u046E', + '\u0470', + '\u0472', + '\u0474', + '\u0476', + '\u0478', + '\u047A', + '\u047C', + '\u047E', + '\u0480', + '\u048A', + '\u048C', + '\u048E', + '\u0490', + '\u0492', + '\u0494', + '\u0496', + '\u0498', + '\u049A', + '\u049C', + '\u049E', + '\u04A0', + '\u04A2', + '\u04A4', + '\u04A6', + '\u04A8', + '\u04AA', + '\u04AC', + '\u04AE', + '\u04B0', + '\u04B2', + '\u04B4', + '\u04B6', + '\u04B8', + '\u04BA', + '\u04BC', + '\u04BE', + ['\u04C0', '\u04C1'], + '\u04C3', + '\u04C5', + '\u04C7', + '\u04C9', + '\u04CB', + '\u04CD', + '\u04D0', + '\u04D2', + '\u04D4', + '\u04D6', + '\u04D8', + '\u04DA', + '\u04DC', + '\u04DE', + '\u04E0', + '\u04E2', + '\u04E4', + '\u04E6', + '\u04E8', + '\u04EA', + '\u04EC', + '\u04EE', + '\u04F0', + '\u04F2', + '\u04F4', + '\u04F6', + '\u04F8', + '\u04FA', + '\u04FC', + '\u04FE', + '\u0500', + '\u0502', + '\u0504', + '\u0506', + '\u0508', + '\u050A', + '\u050C', + '\u050E', + '\u0510', + '\u0512', + '\u0514', + '\u0516', + '\u0518', + '\u051A', + '\u051C', + '\u051E', + '\u0520', + '\u0522', + '\u0524', + '\u0526', + '\u0528', + '\u052A', + '\u052C', + '\u052E', + ['\u0531', '\u0556'], + ['\u10A0', '\u10C5'], + '\u10C7', + '\u10CD', + ['\u13A0', '\u13F5'], + ['\u1C90', '\u1CBA'], + ['\u1CBD', '\u1CBF'], + '\u1E00', + '\u1E02', + '\u1E04', + '\u1E06', + '\u1E08', + '\u1E0A', + '\u1E0C', + '\u1E0E', + '\u1E10', + '\u1E12', + '\u1E14', + '\u1E16', + '\u1E18', + '\u1E1A', + '\u1E1C', + '\u1E1E', + '\u1E20', + '\u1E22', + '\u1E24', + '\u1E26', + '\u1E28', + '\u1E2A', + '\u1E2C', + '\u1E2E', + '\u1E30', + '\u1E32', + '\u1E34', + '\u1E36', + '\u1E38', + '\u1E3A', + '\u1E3C', + '\u1E3E', + '\u1E40', + '\u1E42', + '\u1E44', + '\u1E46', + '\u1E48', + '\u1E4A', + '\u1E4C', + '\u1E4E', + '\u1E50', + '\u1E52', + '\u1E54', + '\u1E56', + '\u1E58', + '\u1E5A', + '\u1E5C', + '\u1E5E', + '\u1E60', + '\u1E62', + '\u1E64', + '\u1E66', + '\u1E68', + '\u1E6A', + '\u1E6C', + '\u1E6E', + '\u1E70', + '\u1E72', + '\u1E74', + '\u1E76', + '\u1E78', + '\u1E7A', + '\u1E7C', + '\u1E7E', + '\u1E80', + '\u1E82', + '\u1E84', + '\u1E86', + '\u1E88', + '\u1E8A', + '\u1E8C', + '\u1E8E', + '\u1E90', + '\u1E92', + '\u1E94', + '\u1E9E', + '\u1EA0', + '\u1EA2', + '\u1EA4', + '\u1EA6', + '\u1EA8', + '\u1EAA', + '\u1EAC', + '\u1EAE', + '\u1EB0', + '\u1EB2', + '\u1EB4', + '\u1EB6', + '\u1EB8', + '\u1EBA', + '\u1EBC', + '\u1EBE', + '\u1EC0', + '\u1EC2', + '\u1EC4', + '\u1EC6', + '\u1EC8', + '\u1ECA', + '\u1ECC', + '\u1ECE', + '\u1ED0', + '\u1ED2', + '\u1ED4', + '\u1ED6', + '\u1ED8', + '\u1EDA', + '\u1EDC', + '\u1EDE', + '\u1EE0', + '\u1EE2', + '\u1EE4', + '\u1EE6', + '\u1EE8', + '\u1EEA', + '\u1EEC', + '\u1EEE', + '\u1EF0', + '\u1EF2', + '\u1EF4', + '\u1EF6', + '\u1EF8', + '\u1EFA', + '\u1EFC', + '\u1EFE', + ['\u1F08', '\u1F0F'], + ['\u1F18', '\u1F1D'], + ['\u1F28', '\u1F2F'], + ['\u1F38', '\u1F3F'], + ['\u1F48', '\u1F4D'], + '\u1F59', + '\u1F5B', + '\u1F5D', + '\u1F5F', + ['\u1F68', '\u1F6F'], + ['\u1FB8', '\u1FBB'], + ['\u1FC8', '\u1FCB'], + ['\u1FD8', '\u1FDB'], + ['\u1FE8', '\u1FEC'], + ['\u1FF8', '\u1FFB'], + '\u2102', + '\u2107', + ['\u210B', '\u210D'], + ['\u2110', '\u2112'], + '\u2115', + ['\u2119', '\u211D'], + '\u2124', + '\u2126', + '\u2128', + ['\u212A', '\u212D'], + ['\u2130', '\u2133'], + ['\u213E', '\u213F'], + '\u2145', + '\u2183', + ['\u2C00', '\u2C2E'], + '\u2C60', + ['\u2C62', '\u2C64'], + '\u2C67', + '\u2C69', + '\u2C6B', + ['\u2C6D', '\u2C70'], + '\u2C72', + '\u2C75', + ['\u2C7E', '\u2C80'], + '\u2C82', + '\u2C84', + '\u2C86', + '\u2C88', + '\u2C8A', + '\u2C8C', + '\u2C8E', + '\u2C90', + '\u2C92', + '\u2C94', + '\u2C96', + '\u2C98', + '\u2C9A', + '\u2C9C', + '\u2C9E', + '\u2CA0', + '\u2CA2', + '\u2CA4', + '\u2CA6', + '\u2CA8', + '\u2CAA', + '\u2CAC', + '\u2CAE', + '\u2CB0', + '\u2CB2', + '\u2CB4', + '\u2CB6', + '\u2CB8', + '\u2CBA', + '\u2CBC', + '\u2CBE', + '\u2CC0', + '\u2CC2', + '\u2CC4', + '\u2CC6', + '\u2CC8', + '\u2CCA', + '\u2CCC', + '\u2CCE', + '\u2CD0', + '\u2CD2', + '\u2CD4', + '\u2CD6', + '\u2CD8', + '\u2CDA', + '\u2CDC', + '\u2CDE', + '\u2CE0', + '\u2CE2', + '\u2CEB', + '\u2CED', + '\u2CF2', + '\uA640', + '\uA642', + '\uA644', + '\uA646', + '\uA648', + '\uA64A', + '\uA64C', + '\uA64E', + '\uA650', + '\uA652', + '\uA654', + '\uA656', + '\uA658', + '\uA65A', + '\uA65C', + '\uA65E', + '\uA660', + '\uA662', + '\uA664', + '\uA666', + '\uA668', + '\uA66A', + '\uA66C', + '\uA680', + '\uA682', + '\uA684', + '\uA686', + '\uA688', + '\uA68A', + '\uA68C', + '\uA68E', + '\uA690', + '\uA692', + '\uA694', + '\uA696', + '\uA698', + '\uA69A', + '\uA722', + '\uA724', + '\uA726', + '\uA728', + '\uA72A', + '\uA72C', + '\uA72E', + '\uA732', + '\uA734', + '\uA736', + '\uA738', + '\uA73A', + '\uA73C', + '\uA73E', + '\uA740', + '\uA742', + '\uA744', + '\uA746', + '\uA748', + '\uA74A', + '\uA74C', + '\uA74E', + '\uA750', + '\uA752', + '\uA754', + '\uA756', + '\uA758', + '\uA75A', + '\uA75C', + '\uA75E', + '\uA760', + '\uA762', + '\uA764', + '\uA766', + '\uA768', + '\uA76A', + '\uA76C', + '\uA76E', + '\uA779', + '\uA77B', + ['\uA77D', '\uA77E'], + '\uA780', + '\uA782', + '\uA784', + '\uA786', + '\uA78B', + '\uA78D', + '\uA790', + '\uA792', + '\uA796', + '\uA798', + '\uA79A', + '\uA79C', + '\uA79E', + '\uA7A0', + '\uA7A2', + '\uA7A4', + '\uA7A6', + '\uA7A8', + ['\uA7AA', '\uA7AE'], + ['\uA7B0', '\uA7B4'], + '\uA7B6', + '\uA7B8', + ['\uFF21', '\uFF3A'], + ], + false, + false + ) + var peg$e54 = peg$classExpectation( + [ + ['\u16EE', '\u16F0'], + ['\u2160', '\u2182'], + ['\u2185', '\u2188'], + '\u3007', + ['\u3021', '\u3029'], + ['\u3038', '\u303A'], + ['\uA6E6', '\uA6EF'], + ], + false, + false + ) - var peg$f0 = function() { return parseFloat(text()); }; - var peg$f1 = function() { return parseInt(text()); }; - var peg$f2 = function(chars) { const s = chars.join(""); return (s === ".") || (s === "_") }; - var peg$f3 = function(chars) { return new AtomStub(chars.join("")) }; - var peg$f4 = function(s) { return s }; - var peg$f5 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; }; - var peg$f6 = function(a) { return a }; - var peg$f7 = function(s) { s.arguments_.alignment = 'polymeter_slowcat'; return s; }; - var peg$f8 = function(a) { return x => x.options_['weight'] = (x.options_['weight'] ?? 1) + (a ?? 2) - 1 }; - var peg$f9 = function(a) { return x => {// A bit fiddly, to support both x!4 and x!!! as equivalent.. - const reps = (x.options_['reps'] ?? 1) + (a ?? 2) - 1; - x.options_['reps'] = reps; - x.options_['ops'] = x.options_['ops'].filter(x => x.type_ !== "replicate"); - x.options_['ops'].push({ type_: "replicate", arguments_ :{ amount:reps }}); - x.options_['weight'] = reps; - } - }; - var peg$f10 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) }; - var peg$f11 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) }; - var peg$f12 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) }; - var peg$f13 = function(a) { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a, seed: seed++ } }) }; - var peg$f14 = function(s) { return x => x.options_['ops'].push({ type_: "tail", arguments_ :{ element:s } }) }; - var peg$f15 = function(s) { return x => x.options_['ops'].push({ type_: "range", arguments_ :{ element:s } }) }; - var peg$f16 = function(s, ops) { const result = new ElementStub(s, {ops: [], weight: 1, reps: 1}); - for (const op of ops) { - op(result); + var peg$f0 = function () { + return parseFloat(text()) + } + var peg$f1 = function () { + return parseInt(text()) + } + var peg$f2 = function (chars) { + const s = chars.join('') + return s === '.' || s === '_' + } + var peg$f3 = function (chars) { + return new AtomStub(chars.join('')) + } + var peg$f4 = function (s) { + return s + } + var peg$f5 = function (s, stepsPerCycle) { + s.arguments_.stepsPerCycle = stepsPerCycle + return s + } + var peg$f6 = function (a) { + return a + } + var peg$f7 = function (s) { + s.arguments_.alignment = 'polymeter_slowcat' + return s + } + var peg$f8 = function (a) { + return (x) => (x.options_['weight'] = (x.options_['weight'] ?? 1) + (a ?? 2) - 1) + } + var peg$f9 = function (a) { + return (x) => { + // A bit fiddly, to support both x!4 and x!!! as equivalent.. + const reps = (x.options_['reps'] ?? 1) + (a ?? 2) - 1 + x.options_['reps'] = reps + x.options_['ops'] = x.options_['ops'].filter((x) => x.type_ !== 'replicate') + x.options_['ops'].push({ type_: 'replicate', arguments_: { amount: reps } }) + x.options_['weight'] = reps } - return result; - }; - var peg$f17 = function(_steps, s) { return new PatternStub(s, 'fastcat', undefined, !!_steps); }; - var peg$f18 = function(tail) { return { alignment: 'stack', list: tail }; }; - var peg$f19 = function(tail) { return { alignment: 'rand', list: tail, seed: seed++ }; }; - var peg$f20 = function(tail) { return { alignment: 'feet', list: tail, seed: seed++ }; }; - var peg$f21 = function(head, tail) {if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment, tail.seed); } else { return head; } }; - var peg$f22 = function(head, tail) { return new PatternStub(tail ? [head, ...tail.list] : [head], 'polymeter'); }; - var peg$f23 = function(sc) { return sc; }; - var peg$f24 = function(s) { return { name: "struct", args: { mini:s }}}; - var peg$f25 = function(s) { return { name: "target", args : { name:s}}}; - var peg$f26 = function(p, s, r) { return { name: "bjorklund", args :{ pulse: p, step:parseInt(s) }}}; - var peg$f27 = function(a) { return { name: "stretch", args :{ amount: a}}}; - var peg$f28 = function(a) { return { name: "shift", args :{ amount: "-"+a}}}; - var peg$f29 = function(a) { return { name: "shift", args :{ amount: a}}}; - var peg$f30 = function(a) { return { name: "stretch", args :{ amount: "1/"+a}}}; - var peg$f31 = function(s) { return { name: "scale", args :{ scale: s.join("")}}}; - var peg$f32 = function(s, v) { return v}; - var peg$f33 = function(s, ss) { ss.unshift(s); return new PatternStub(ss, 'slowcat'); }; - var peg$f34 = function(sg) {return sg}; - var peg$f35 = function(o, soc) { return new OperatorStub(o.name,o.args,soc)}; - var peg$f36 = function(sc) { return sc }; - var peg$f37 = function(c) { return c }; - var peg$f38 = function(v) { return new CommandStub("setcps", { value: v})}; - var peg$f39 = function(v) { return new CommandStub("setcps", { value: (v/120/2)})}; - var peg$f40 = function() { return new CommandStub("hush")}; - var peg$currPos = options.peg$currPos | 0; - var peg$savedPos = peg$currPos; - var peg$posDetailsCache = [{ line: 1, column: 1 }]; - var peg$maxFailPos = peg$currPos; - var peg$maxFailExpected = options.peg$maxFailExpected || []; - var peg$silentFails = options.peg$silentFails | 0; + } + var peg$f10 = function (p, s, r) { + return (x) => + x.options_['ops'].push({ type_: 'bjorklund', arguments_: { pulse: p, step: s, rotation: r } }) + } + var peg$f11 = function (a) { + return (x) => + x.options_['ops'].push({ type_: 'stretch', arguments_: { amount: a, type: 'slow' } }) + } + var peg$f12 = function (a) { + return (x) => + x.options_['ops'].push({ type_: 'stretch', arguments_: { amount: a, type: 'fast' } }) + } + var peg$f13 = function (a) { + return (x) => + x.options_['ops'].push({ type_: 'degradeBy', arguments_: { amount: a, seed: seed++ } }) + } + var peg$f14 = function (s) { + return (x) => x.options_['ops'].push({ type_: 'tail', arguments_: { element: s } }) + } + var peg$f15 = function (s) { + return (x) => x.options_['ops'].push({ type_: 'range', arguments_: { element: s } }) + } + var peg$f16 = function (s, ops) { + const result = new ElementStub(s, { ops: [], weight: 1, reps: 1 }) + for (const op of ops) { + op(result) + } + return result + } + var peg$f17 = function (_steps, s) { + return new PatternStub(s, 'fastcat', undefined, !!_steps) + } + var peg$f18 = function (tail) { + return { alignment: 'stack', list: tail } + } + var peg$f19 = function (tail) { + return { alignment: 'rand', list: tail, seed: seed++ } + } + var peg$f20 = function (tail) { + return { alignment: 'feet', list: tail, seed: seed++ } + } + var peg$f21 = function (head, tail) { + if (tail && tail.list.length > 0) { + return new PatternStub([head, ...tail.list], tail.alignment, tail.seed) + } else { + return head + } + } + var peg$f22 = function (head, tail) { + return new PatternStub(tail ? [head, ...tail.list] : [head], 'polymeter') + } + var peg$f23 = function (sc) { + return sc + } + var peg$f24 = function (s) { + return { name: 'struct', args: { mini: s } } + } + var peg$f25 = function (s) { + return { name: 'target', args: { name: s } } + } + var peg$f26 = function (p, s, r) { + return { name: 'bjorklund', args: { pulse: p, step: parseInt(s) } } + } + var peg$f27 = function (a) { + return { name: 'stretch', args: { amount: a } } + } + var peg$f28 = function (a) { + return { name: 'shift', args: { amount: '-' + a } } + } + var peg$f29 = function (a) { + return { name: 'shift', args: { amount: a } } + } + var peg$f30 = function (a) { + return { name: 'stretch', args: { amount: '1/' + a } } + } + var peg$f31 = function (s) { + return { name: 'scale', args: { scale: s.join('') } } + } + var peg$f32 = function (s, v) { + return v + } + var peg$f33 = function (s, ss) { + ss.unshift(s) + return new PatternStub(ss, 'slowcat') + } + var peg$f34 = function (sg) { + return sg + } + var peg$f35 = function (o, soc) { + return new OperatorStub(o.name, o.args, soc) + } + var peg$f36 = function (sc) { + return sc + } + var peg$f37 = function (c) { + return c + } + var peg$f38 = function (v) { + return new CommandStub('setcps', { value: v }) + } + var peg$f39 = function (v) { + return new CommandStub('setcps', { value: v / 120 / 2 }) + } + var peg$f40 = function () { + return new CommandStub('hush') + } + var peg$currPos = options.peg$currPos | 0 + var peg$savedPos = peg$currPos + var peg$posDetailsCache = [{ line: 1, column: 1 }] + var peg$maxFailPos = peg$currPos + var peg$maxFailExpected = options.peg$maxFailExpected || [] + var peg$silentFails = options.peg$silentFails | 0 - var peg$result; + var peg$result if (options.startRule) { if (!(options.startRule in peg$startRuleFunctions)) { - throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + throw new Error('Can\'t start parsing from rule "' + options.startRule + '".') } - peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + peg$startRuleFunction = peg$startRuleFunctions[options.startRule] } function text() { - return input.substring(peg$savedPos, peg$currPos); + return input.substring(peg$savedPos, peg$currPos) } function offset() { - return peg$savedPos; + return peg$savedPos } function range() { return { source: peg$source, start: peg$savedPos, - end: peg$currPos - }; + end: peg$currPos, + } } function location() { - return peg$computeLocation(peg$savedPos, peg$currPos); + return peg$computeLocation(peg$savedPos, peg$currPos) } function expected(description, location) { - location = location !== undefined - ? location - : peg$computeLocation(peg$savedPos, peg$currPos); + location = location !== undefined ? location : peg$computeLocation(peg$savedPos, peg$currPos) throw peg$buildStructuredError( [peg$otherExpectation(description)], input.substring(peg$savedPos, peg$currPos), location - ); + ) } function error(message, location) { - location = location !== undefined - ? location - : peg$computeLocation(peg$savedPos, peg$currPos); + location = location !== undefined ? location : peg$computeLocation(peg$savedPos, peg$currPos) - throw peg$buildSimpleError(message, location); + throw peg$buildSimpleError(message, location) } function peg$literalExpectation(text, ignoreCase) { - return { type: "literal", text: text, ignoreCase: ignoreCase }; + return { type: 'literal', text: text, ignoreCase: ignoreCase } } function peg$classExpectation(parts, inverted, ignoreCase) { - return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; + return { type: 'class', parts: parts, inverted: inverted, ignoreCase: ignoreCase } } function peg$anyExpectation() { - return { type: "any" }; + return { type: 'any' } } function peg$endExpectation() { - return { type: "end" }; + return { type: 'end' } } function peg$otherExpectation(description) { - return { type: "other", description: description }; + return { type: 'other', description: description } } function peg$computePosDetails(pos) { - var details = peg$posDetailsCache[pos]; - var p; + var details = peg$posDetailsCache[pos] + var p if (details) { - return details; + return details } else { if (pos >= peg$posDetailsCache.length) { - p = peg$posDetailsCache.length - 1; + p = peg$posDetailsCache.length - 1 } else { - p = pos; + p = pos while (!peg$posDetailsCache[--p]) {} } - details = peg$posDetailsCache[p]; + details = peg$posDetailsCache[p] details = { line: details.line, - column: details.column - }; + column: details.column, + } while (p < pos) { if (input.charCodeAt(p) === 10) { - details.line++; - details.column = 1; + details.line++ + details.column = 1 } else { - details.column++; + details.column++ } - p++; + p++ } - peg$posDetailsCache[pos] = details; + peg$posDetailsCache[pos] = details - return details; + return details } } function peg$computeLocation(startPos, endPos, offset) { - var startPosDetails = peg$computePosDetails(startPos); - var endPosDetails = peg$computePosDetails(endPos); + var startPosDetails = peg$computePosDetails(startPos) + var endPosDetails = peg$computePosDetails(endPos) var res = { source: peg$source, start: { offset: startPos, line: startPosDetails.line, - column: startPosDetails.column + column: startPosDetails.column, }, end: { offset: endPos, line: endPosDetails.line, - column: endPosDetails.column - } - }; - if (offset && peg$source && (typeof peg$source.offset === "function")) { - res.start = peg$source.offset(res.start); - res.end = peg$source.offset(res.end); + column: endPosDetails.column, + }, } - return res; + if (offset && peg$source && typeof peg$source.offset === 'function') { + res.start = peg$source.offset(res.start) + res.end = peg$source.offset(res.end) + } + return res } function peg$fail(expected) { - if (peg$currPos < peg$maxFailPos) { return; } - - if (peg$currPos > peg$maxFailPos) { - peg$maxFailPos = peg$currPos; - peg$maxFailExpected = []; + if (peg$currPos < peg$maxFailPos) { + return } - peg$maxFailExpected.push(expected); + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos + peg$maxFailExpected = [] + } + + peg$maxFailExpected.push(expected) } function peg$buildSimpleError(message, location) { - return new peg$SyntaxError(message, null, null, location); + return new peg$SyntaxError(message, null, null, location) } function peg$buildStructuredError(expected, found, location) { @@ -497,639 +2996,689 @@ function peg$parse(input, options) { expected, found, location - ); + ) } function peg$parsestart() { - var s0; + var s0 - s0 = peg$parsestatement(); + s0 = peg$parsestatement() - return s0; + return s0 } function peg$parsenumber() { - var s0, s1, s2, s3, s4; + var s0, s1, s2, s3, s4 - peg$silentFails++; - s0 = peg$currPos; - s1 = peg$parseminus(); + peg$silentFails++ + s0 = peg$currPos + s1 = peg$parseminus() if (s1 === peg$FAILED) { - s1 = null; + s1 = null } - s2 = peg$parseint(); + s2 = peg$parseint() if (s2 !== peg$FAILED) { - s3 = peg$parsefrac(); + s3 = peg$parsefrac() if (s3 === peg$FAILED) { - s3 = null; + s3 = null } - s4 = peg$parseexp(); + s4 = peg$parseexp() if (s4 === peg$FAILED) { - s4 = null; + s4 = null } - peg$savedPos = s0; - s0 = peg$f0(); + peg$savedPos = s0 + s0 = peg$f0() } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - peg$silentFails--; + peg$silentFails-- if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e0); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e0) + } } - return s0; + return s0 } function peg$parsedecimal_point() { - var s0; + var s0 if (input.charCodeAt(peg$currPos) === 46) { - s0 = peg$c0; - peg$currPos++; + s0 = peg$c0 + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e1); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e1) + } } - return s0; + return s0 } function peg$parsedigit1_9() { - var s0; + var s0 - s0 = input.charAt(peg$currPos); + s0 = input.charAt(peg$currPos) if (peg$r0.test(s0)) { - peg$currPos++; + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e2); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e2) + } } - return s0; + return s0 } function peg$parsee() { - var s0; + var s0 - s0 = input.charAt(peg$currPos); + s0 = input.charAt(peg$currPos) if (peg$r1.test(s0)) { - peg$currPos++; + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e3); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e3) + } } - return s0; + return s0 } function peg$parseexp() { - var s0, s1, s2, s3, s4; + var s0, s1, s2, s3, s4 - s0 = peg$currPos; - s1 = peg$parsee(); + s0 = peg$currPos + s1 = peg$parsee() if (s1 !== peg$FAILED) { - s2 = input.charAt(peg$currPos); + s2 = input.charAt(peg$currPos) if (peg$r2.test(s2)) { - peg$currPos++; + peg$currPos++ } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e4); } + s2 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e4) + } } if (s2 === peg$FAILED) { - s2 = null; + s2 = null } - s3 = []; - s4 = peg$parseDIGIT(); + s3 = [] + s4 = peg$parseDIGIT() if (s4 !== peg$FAILED) { while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$parseDIGIT(); + s3.push(s4) + s4 = peg$parseDIGIT() } } else { - s3 = peg$FAILED; + s3 = peg$FAILED } if (s3 !== peg$FAILED) { - s1 = [s1, s2, s3]; - s0 = s1; + s1 = [s1, s2, s3] + s0 = s1 } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsefrac() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; - s1 = peg$parsedecimal_point(); + s0 = peg$currPos + s1 = peg$parsedecimal_point() if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$parseDIGIT(); + s2 = [] + s3 = peg$parseDIGIT() if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parseDIGIT(); + s2.push(s3) + s3 = peg$parseDIGIT() } } else { - s2 = peg$FAILED; + s2 = peg$FAILED } if (s2 !== peg$FAILED) { - s1 = [s1, s2]; - s0 = s1; + s1 = [s1, s2] + s0 = s1 } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parseint() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$parsezero(); + s0 = peg$parsezero() if (s0 === peg$FAILED) { - s0 = peg$currPos; - s1 = peg$parsedigit1_9(); + s0 = peg$currPos + s1 = peg$parsedigit1_9() if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$parseDIGIT(); + s2 = [] + s3 = peg$parseDIGIT() while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parseDIGIT(); + s2.push(s3) + s3 = peg$parseDIGIT() } - s1 = [s1, s2]; - s0 = s1; + s1 = [s1, s2] + s0 = s1 } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } - return s0; + return s0 } function peg$parseintneg() { - var s0, s1, s2; + var s0, s1, s2 - s0 = peg$currPos; - s1 = peg$parseminus(); + s0 = peg$currPos + s1 = peg$parseminus() if (s1 === peg$FAILED) { - s1 = null; + s1 = null } - s2 = peg$parseint(); + s2 = peg$parseint() if (s2 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f1(); + peg$savedPos = s0 + s0 = peg$f1() } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parseminus() { - var s0; + var s0 if (input.charCodeAt(peg$currPos) === 45) { - s0 = peg$c1; - peg$currPos++; + s0 = peg$c1 + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e5); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e5) + } } - return s0; + return s0 } function peg$parseplus() { - var s0; + var s0 if (input.charCodeAt(peg$currPos) === 43) { - s0 = peg$c2; - peg$currPos++; + s0 = peg$c2 + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e6); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e6) + } } - return s0; + return s0 } function peg$parsezero() { - var s0; + var s0 if (input.charCodeAt(peg$currPos) === 48) { - s0 = peg$c3; - peg$currPos++; + s0 = peg$c3 + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e7); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e7) + } } - return s0; + return s0 } function peg$parseDIGIT() { - var s0; + var s0 - s0 = input.charAt(peg$currPos); + s0 = input.charAt(peg$currPos) if (peg$r3.test(s0)) { - peg$currPos++; + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e8); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e8) + } } - return s0; + return s0 } function peg$parsews() { - var s0, s1; + var s0, s1 - peg$silentFails++; - s0 = []; - s1 = input.charAt(peg$currPos); + peg$silentFails++ + s0 = [] + s1 = input.charAt(peg$currPos) if (peg$r4.test(s1)) { - peg$currPos++; + peg$currPos++ } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e10); } - } - while (s1 !== peg$FAILED) { - s0.push(s1); - s1 = input.charAt(peg$currPos); - if (peg$r4.test(s1)) { - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e10); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e10) } } - peg$silentFails--; - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e9); } + while (s1 !== peg$FAILED) { + s0.push(s1) + s1 = input.charAt(peg$currPos) + if (peg$r4.test(s1)) { + peg$currPos++ + } else { + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e10) + } + } + } + peg$silentFails-- + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e9) + } - return s0; + return s0 } function peg$parsecomma() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; - s1 = peg$parsews(); + s0 = peg$currPos + s1 = peg$parsews() if (input.charCodeAt(peg$currPos) === 44) { - s2 = peg$c4; - peg$currPos++; + s2 = peg$c4 + peg$currPos++ } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e11); } + s2 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e11) + } } if (s2 !== peg$FAILED) { - s3 = peg$parsews(); - s1 = [s1, s2, s3]; - s0 = s1; + s3 = peg$parsews() + s1 = [s1, s2, s3] + s0 = s1 } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsepipe() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; - s1 = peg$parsews(); + s0 = peg$currPos + s1 = peg$parsews() if (input.charCodeAt(peg$currPos) === 124) { - s2 = peg$c5; - peg$currPos++; + s2 = peg$c5 + peg$currPos++ } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e12); } + s2 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e12) + } } if (s2 !== peg$FAILED) { - s3 = peg$parsews(); - s1 = [s1, s2, s3]; - s0 = s1; + s3 = peg$parsews() + s1 = [s1, s2, s3] + s0 = s1 } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsedot() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; - s1 = peg$parsews(); + s0 = peg$currPos + s1 = peg$parsews() if (input.charCodeAt(peg$currPos) === 46) { - s2 = peg$c0; - peg$currPos++; + s2 = peg$c0 + peg$currPos++ } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e1); } + s2 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e1) + } } if (s2 !== peg$FAILED) { - s3 = peg$parsews(); - s1 = [s1, s2, s3]; - s0 = s1; + s3 = peg$parsews() + s1 = [s1, s2, s3] + s0 = s1 } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsequote() { - var s0; + var s0 - s0 = input.charAt(peg$currPos); + s0 = input.charAt(peg$currPos) if (peg$r5.test(s0)) { - peg$currPos++; + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e13); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e13) + } } - return s0; + return s0 } function peg$parsestep_char() { - var s0, s1; + var s0, s1 - peg$silentFails++; - s0 = input.charAt(peg$currPos); + peg$silentFails++ + s0 = input.charAt(peg$currPos) if (peg$r6.test(s0)) { - peg$currPos++; + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e15); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e15) + } } - peg$silentFails--; + peg$silentFails-- if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e14); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e14) + } } - return s0; + return s0 } function peg$parsestep() { - var s0, s1, s2, s3, s4; + var s0, s1, s2, s3, s4 - s0 = peg$currPos; - s1 = peg$parsews(); - s2 = []; - s3 = peg$parsestep_char(); + s0 = peg$currPos + s1 = peg$parsews() + s2 = [] + s3 = peg$parsestep_char() if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parsestep_char(); + s2.push(s3) + s3 = peg$parsestep_char() } } else { - s2 = peg$FAILED; + s2 = peg$FAILED } if (s2 !== peg$FAILED) { - s3 = peg$parsews(); - peg$savedPos = peg$currPos; - s4 = peg$f2(s2); + s3 = peg$parsews() + peg$savedPos = peg$currPos + s4 = peg$f2(s2) if (s4) { - s4 = peg$FAILED; + s4 = peg$FAILED } else { - s4 = undefined; + s4 = undefined } if (s4 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f3(s2); + peg$savedPos = s0 + s0 = peg$f3(s2) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsesub_cycle() { - var s0, s1, s2, s3, s4, s5, s6, s7; + var s0, s1, s2, s3, s4, s5, s6, s7 - s0 = peg$currPos; - s1 = peg$parsews(); + s0 = peg$currPos + s1 = peg$parsews() if (input.charCodeAt(peg$currPos) === 91) { - s2 = peg$c6; - peg$currPos++; + s2 = peg$c6 + peg$currPos++ } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e16); } + s2 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e16) + } } if (s2 !== peg$FAILED) { - s3 = peg$parsews(); - s4 = peg$parsestack_or_choose(); + s3 = peg$parsews() + s4 = peg$parsestack_or_choose() if (s4 !== peg$FAILED) { - s5 = peg$parsews(); + s5 = peg$parsews() if (input.charCodeAt(peg$currPos) === 93) { - s6 = peg$c7; - peg$currPos++; + s6 = peg$c7 + peg$currPos++ } else { - s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e17); } + s6 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e17) + } } if (s6 !== peg$FAILED) { - s7 = peg$parsews(); - peg$savedPos = s0; - s0 = peg$f4(s4); + s7 = peg$parsews() + peg$savedPos = s0 + s0 = peg$f4(s4) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsepolymeter() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8; + var s0, s1, s2, s3, s4, s5, s6, s7, s8 - s0 = peg$currPos; - s1 = peg$parsews(); + s0 = peg$currPos + s1 = peg$parsews() if (input.charCodeAt(peg$currPos) === 123) { - s2 = peg$c8; - peg$currPos++; + s2 = peg$c8 + peg$currPos++ } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e18); } + s2 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e18) + } } if (s2 !== peg$FAILED) { - s3 = peg$parsews(); - s4 = peg$parsepolymeter_stack(); + s3 = peg$parsews() + s4 = peg$parsepolymeter_stack() if (s4 !== peg$FAILED) { - s5 = peg$parsews(); + s5 = peg$parsews() if (input.charCodeAt(peg$currPos) === 125) { - s6 = peg$c9; - peg$currPos++; + s6 = peg$c9 + peg$currPos++ } else { - s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e19); } + s6 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e19) + } } if (s6 !== peg$FAILED) { - s7 = peg$parsepolymeter_steps(); + s7 = peg$parsepolymeter_steps() if (s7 === peg$FAILED) { - s7 = null; + s7 = null } - s8 = peg$parsews(); - peg$savedPos = s0; - s0 = peg$f5(s4, s7); + s8 = peg$parsews() + peg$savedPos = s0 + s0 = peg$f5(s4, s7) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsepolymeter_steps() { - var s0, s1, s2; + var s0, s1, s2 - s0 = peg$currPos; + s0 = peg$currPos if (input.charCodeAt(peg$currPos) === 37) { - s1 = peg$c10; - peg$currPos++; + s1 = peg$c10 + peg$currPos++ } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e20); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e20) + } } if (s1 !== peg$FAILED) { - s2 = peg$parseslice(); + s2 = peg$parseslice() if (s2 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f6(s2); + peg$savedPos = s0 + s0 = peg$f6(s2) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parseslow_sequence() { - var s0, s1, s2, s3, s4, s5, s6, s7; + var s0, s1, s2, s3, s4, s5, s6, s7 - s0 = peg$currPos; - s1 = peg$parsews(); + s0 = peg$currPos + s1 = peg$parsews() if (input.charCodeAt(peg$currPos) === 60) { - s2 = peg$c11; - peg$currPos++; + s2 = peg$c11 + peg$currPos++ } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e21); } + s2 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e21) + } } if (s2 !== peg$FAILED) { - s3 = peg$parsews(); - s4 = peg$parsepolymeter_stack(); + s3 = peg$parsews() + s4 = peg$parsepolymeter_stack() if (s4 !== peg$FAILED) { - s5 = peg$parsews(); + s5 = peg$parsews() if (input.charCodeAt(peg$currPos) === 62) { - s6 = peg$c12; - peg$currPos++; + s6 = peg$c12 + peg$currPos++ } else { - s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e22); } + s6 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e22) + } } if (s6 !== peg$FAILED) { - s7 = peg$parsews(); - peg$savedPos = s0; - s0 = peg$f7(s4); + s7 = peg$parsews() + peg$savedPos = s0 + s0 = peg$f7(s4) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parseslice() { - var s0; + var s0 - s0 = peg$parsestep(); + s0 = peg$parsestep() if (s0 === peg$FAILED) { - s0 = peg$parsesub_cycle(); + s0 = peg$parsesub_cycle() if (s0 === peg$FAILED) { - s0 = peg$parsepolymeter(); + s0 = peg$parsepolymeter() if (s0 === peg$FAILED) { - s0 = peg$parseslow_sequence(); + s0 = peg$parseslow_sequence() } } } - return s0; + return s0 } function peg$parseslice_op() { - var s0; + var s0 - s0 = peg$parseop_weight(); + s0 = peg$parseop_weight() if (s0 === peg$FAILED) { - s0 = peg$parseop_bjorklund(); + s0 = peg$parseop_bjorklund() if (s0 === peg$FAILED) { - s0 = peg$parseop_slow(); + s0 = peg$parseop_slow() if (s0 === peg$FAILED) { - s0 = peg$parseop_fast(); + s0 = peg$parseop_fast() if (s0 === peg$FAILED) { - s0 = peg$parseop_replicate(); + s0 = peg$parseop_replicate() if (s0 === peg$FAILED) { - s0 = peg$parseop_degrade(); + s0 = peg$parseop_degrade() if (s0 === peg$FAILED) { - s0 = peg$parseop_tail(); + s0 = peg$parseop_tail() if (s0 === peg$FAILED) { - s0 = peg$parseop_range(); + s0 = peg$parseop_range() } } } @@ -1138,566 +3687,586 @@ function peg$parse(input, options) { } } - return s0; + return s0 } function peg$parseop_weight() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; - s1 = peg$parsews(); - s2 = input.charAt(peg$currPos); + s0 = peg$currPos + s1 = peg$parsews() + s2 = input.charAt(peg$currPos) if (peg$r7.test(s2)) { - peg$currPos++; + peg$currPos++ } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e23); } + s2 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e23) + } } if (s2 !== peg$FAILED) { - s3 = peg$parsenumber(); + s3 = peg$parsenumber() if (s3 === peg$FAILED) { - s3 = null; + s3 = null } - peg$savedPos = s0; - s0 = peg$f8(s3); + peg$savedPos = s0 + s0 = peg$f8(s3) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parseop_replicate() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; - s1 = peg$parsews(); + s0 = peg$currPos + s1 = peg$parsews() if (input.charCodeAt(peg$currPos) === 33) { - s2 = peg$c13; - peg$currPos++; + s2 = peg$c13 + peg$currPos++ } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e24); } + s2 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e24) + } } if (s2 !== peg$FAILED) { - s3 = peg$parsenumber(); + s3 = peg$parsenumber() if (s3 === peg$FAILED) { - s3 = null; + s3 = null } - peg$savedPos = s0; - s0 = peg$f9(s3); + peg$savedPos = s0 + s0 = peg$f9(s3) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parseop_bjorklund() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13; + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13 - s0 = peg$currPos; + s0 = peg$currPos if (input.charCodeAt(peg$currPos) === 40) { - s1 = peg$c14; - peg$currPos++; + s1 = peg$c14 + peg$currPos++ } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e25); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e25) + } } if (s1 !== peg$FAILED) { - s2 = peg$parsews(); - s3 = peg$parseslice_with_ops(); + s2 = peg$parsews() + s3 = peg$parseslice_with_ops() if (s3 !== peg$FAILED) { - s4 = peg$parsews(); - s5 = peg$parsecomma(); + s4 = peg$parsews() + s5 = peg$parsecomma() if (s5 !== peg$FAILED) { - s6 = peg$parsews(); - s7 = peg$parseslice_with_ops(); + s6 = peg$parsews() + s7 = peg$parseslice_with_ops() if (s7 !== peg$FAILED) { - s8 = peg$parsews(); - s9 = peg$parsecomma(); + s8 = peg$parsews() + s9 = peg$parsecomma() if (s9 === peg$FAILED) { - s9 = null; + s9 = null } - s10 = peg$parsews(); - s11 = peg$parseslice_with_ops(); + s10 = peg$parsews() + s11 = peg$parseslice_with_ops() if (s11 === peg$FAILED) { - s11 = null; + s11 = null } - s12 = peg$parsews(); + s12 = peg$parsews() if (input.charCodeAt(peg$currPos) === 41) { - s13 = peg$c15; - peg$currPos++; + s13 = peg$c15 + peg$currPos++ } else { - s13 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e26); } + s13 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e26) + } } if (s13 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f10(s3, s7, s11); + peg$savedPos = s0 + s0 = peg$f10(s3, s7, s11) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parseop_slow() { - var s0, s1, s2; + var s0, s1, s2 - s0 = peg$currPos; + s0 = peg$currPos if (input.charCodeAt(peg$currPos) === 47) { - s1 = peg$c16; - peg$currPos++; + s1 = peg$c16 + peg$currPos++ } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e27); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e27) + } } if (s1 !== peg$FAILED) { - s2 = peg$parseslice(); + s2 = peg$parseslice() if (s2 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f11(s2); + peg$savedPos = s0 + s0 = peg$f11(s2) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parseop_fast() { - var s0, s1, s2; + var s0, s1, s2 - s0 = peg$currPos; + s0 = peg$currPos if (input.charCodeAt(peg$currPos) === 42) { - s1 = peg$c17; - peg$currPos++; + s1 = peg$c17 + peg$currPos++ } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e28); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e28) + } } if (s1 !== peg$FAILED) { - s2 = peg$parseslice(); + s2 = peg$parseslice() if (s2 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f12(s2); + peg$savedPos = s0 + s0 = peg$f12(s2) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parseop_degrade() { - var s0, s1, s2; + var s0, s1, s2 - s0 = peg$currPos; + s0 = peg$currPos if (input.charCodeAt(peg$currPos) === 63) { - s1 = peg$c18; - peg$currPos++; + s1 = peg$c18 + peg$currPos++ } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e29); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e29) + } } if (s1 !== peg$FAILED) { - s2 = peg$parsenumber(); + s2 = peg$parsenumber() if (s2 === peg$FAILED) { - s2 = null; + s2 = null } - peg$savedPos = s0; - s0 = peg$f13(s2); + peg$savedPos = s0 + s0 = peg$f13(s2) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parseop_tail() { - var s0, s1, s2; + var s0, s1, s2 - s0 = peg$currPos; + s0 = peg$currPos if (input.charCodeAt(peg$currPos) === 58) { - s1 = peg$c19; - peg$currPos++; + s1 = peg$c19 + peg$currPos++ } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e30); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e30) + } } if (s1 !== peg$FAILED) { - s2 = peg$parseslice(); + s2 = peg$parseslice() if (s2 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f14(s2); + peg$savedPos = s0 + s0 = peg$f14(s2) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parseop_range() { - var s0, s1, s2; + var s0, s1, s2 - s0 = peg$currPos; + s0 = peg$currPos if (input.substr(peg$currPos, 2) === peg$c20) { - s1 = peg$c20; - peg$currPos += 2; + s1 = peg$c20 + peg$currPos += 2 } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e31); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e31) + } } if (s1 !== peg$FAILED) { - s2 = peg$parseslice(); + s2 = peg$parseslice() if (s2 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f15(s2); + peg$savedPos = s0 + s0 = peg$f15(s2) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parseslice_with_ops() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; - s1 = peg$parseslice(); + s0 = peg$currPos + s1 = peg$parseslice() if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$parseslice_op(); + s2 = [] + s3 = peg$parseslice_op() while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parseslice_op(); + s2.push(s3) + s3 = peg$parseslice_op() } - peg$savedPos = s0; - s0 = peg$f16(s1, s2); + peg$savedPos = s0 + s0 = peg$f16(s1, s2) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsesequence() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; + s0 = peg$currPos if (input.charCodeAt(peg$currPos) === 94) { - s1 = peg$c21; - peg$currPos++; + s1 = peg$c21 + peg$currPos++ } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e32); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e32) + } } if (s1 === peg$FAILED) { - s1 = null; + s1 = null } - s2 = []; - s3 = peg$parseslice_with_ops(); + s2 = [] + s3 = peg$parseslice_with_ops() if (s3 !== peg$FAILED) { while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parseslice_with_ops(); + s2.push(s3) + s3 = peg$parseslice_with_ops() } } else { - s2 = peg$FAILED; + s2 = peg$FAILED } if (s2 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f17(s1, s2); + peg$savedPos = s0 + s0 = peg$f17(s1, s2) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsestack_tail() { - var s0, s1, s2, s3, s4; + var s0, s1, s2, s3, s4 - s0 = peg$currPos; - s1 = []; - s2 = peg$currPos; - s3 = peg$parsecomma(); + s0 = peg$currPos + s1 = [] + s2 = peg$currPos + s3 = peg$parsecomma() if (s3 !== peg$FAILED) { - s4 = peg$parsesequence(); + s4 = peg$parsesequence() if (s4 !== peg$FAILED) { - s2 = s4; + s2 = s4 } else { - peg$currPos = s2; - s2 = peg$FAILED; + peg$currPos = s2 + s2 = peg$FAILED } } else { - peg$currPos = s2; - s2 = peg$FAILED; + peg$currPos = s2 + s2 = peg$FAILED } if (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) { - s1.push(s2); - s2 = peg$currPos; - s3 = peg$parsecomma(); + s1.push(s2) + s2 = peg$currPos + s3 = peg$parsecomma() if (s3 !== peg$FAILED) { - s4 = peg$parsesequence(); + s4 = peg$parsesequence() if (s4 !== peg$FAILED) { - s2 = s4; + s2 = s4 } else { - peg$currPos = s2; - s2 = peg$FAILED; + peg$currPos = s2 + s2 = peg$FAILED } } else { - peg$currPos = s2; - s2 = peg$FAILED; + peg$currPos = s2 + s2 = peg$FAILED } } } else { - s1 = peg$FAILED; + s1 = peg$FAILED } if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f18(s1); + peg$savedPos = s0 + s1 = peg$f18(s1) } - s0 = s1; + s0 = s1 - return s0; + return s0 } function peg$parsechoose_tail() { - var s0, s1, s2, s3, s4; + var s0, s1, s2, s3, s4 - s0 = peg$currPos; - s1 = []; - s2 = peg$currPos; - s3 = peg$parsepipe(); + s0 = peg$currPos + s1 = [] + s2 = peg$currPos + s3 = peg$parsepipe() if (s3 !== peg$FAILED) { - s4 = peg$parsesequence(); + s4 = peg$parsesequence() if (s4 !== peg$FAILED) { - s2 = s4; + s2 = s4 } else { - peg$currPos = s2; - s2 = peg$FAILED; + peg$currPos = s2 + s2 = peg$FAILED } } else { - peg$currPos = s2; - s2 = peg$FAILED; + peg$currPos = s2 + s2 = peg$FAILED } if (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) { - s1.push(s2); - s2 = peg$currPos; - s3 = peg$parsepipe(); + s1.push(s2) + s2 = peg$currPos + s3 = peg$parsepipe() if (s3 !== peg$FAILED) { - s4 = peg$parsesequence(); + s4 = peg$parsesequence() if (s4 !== peg$FAILED) { - s2 = s4; + s2 = s4 } else { - peg$currPos = s2; - s2 = peg$FAILED; + peg$currPos = s2 + s2 = peg$FAILED } } else { - peg$currPos = s2; - s2 = peg$FAILED; + peg$currPos = s2 + s2 = peg$FAILED } } } else { - s1 = peg$FAILED; + s1 = peg$FAILED } if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f19(s1); + peg$savedPos = s0 + s1 = peg$f19(s1) } - s0 = s1; + s0 = s1 - return s0; + return s0 } function peg$parsedot_tail() { - var s0, s1, s2, s3, s4; + var s0, s1, s2, s3, s4 - s0 = peg$currPos; - s1 = []; - s2 = peg$currPos; - s3 = peg$parsedot(); + s0 = peg$currPos + s1 = [] + s2 = peg$currPos + s3 = peg$parsedot() if (s3 !== peg$FAILED) { - s4 = peg$parsesequence(); + s4 = peg$parsesequence() if (s4 !== peg$FAILED) { - s2 = s4; + s2 = s4 } else { - peg$currPos = s2; - s2 = peg$FAILED; + peg$currPos = s2 + s2 = peg$FAILED } } else { - peg$currPos = s2; - s2 = peg$FAILED; + peg$currPos = s2 + s2 = peg$FAILED } if (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) { - s1.push(s2); - s2 = peg$currPos; - s3 = peg$parsedot(); + s1.push(s2) + s2 = peg$currPos + s3 = peg$parsedot() if (s3 !== peg$FAILED) { - s4 = peg$parsesequence(); + s4 = peg$parsesequence() if (s4 !== peg$FAILED) { - s2 = s4; + s2 = s4 } else { - peg$currPos = s2; - s2 = peg$FAILED; + peg$currPos = s2 + s2 = peg$FAILED } } else { - peg$currPos = s2; - s2 = peg$FAILED; + peg$currPos = s2 + s2 = peg$FAILED } } } else { - s1 = peg$FAILED; + s1 = peg$FAILED } if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f20(s1); + peg$savedPos = s0 + s1 = peg$f20(s1) } - s0 = s1; + s0 = s1 - return s0; + return s0 } function peg$parsestack_or_choose() { - var s0, s1, s2; + var s0, s1, s2 - s0 = peg$currPos; - s1 = peg$parsesequence(); + s0 = peg$currPos + s1 = peg$parsesequence() if (s1 !== peg$FAILED) { - s2 = peg$parsestack_tail(); + s2 = peg$parsestack_tail() if (s2 === peg$FAILED) { - s2 = peg$parsechoose_tail(); + s2 = peg$parsechoose_tail() if (s2 === peg$FAILED) { - s2 = peg$parsedot_tail(); + s2 = peg$parsedot_tail() } } if (s2 === peg$FAILED) { - s2 = null; + s2 = null } - peg$savedPos = s0; - s0 = peg$f21(s1, s2); + peg$savedPos = s0 + s0 = peg$f21(s1, s2) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsepolymeter_stack() { - var s0, s1, s2; + var s0, s1, s2 - s0 = peg$currPos; - s1 = peg$parsesequence(); + s0 = peg$currPos + s1 = peg$parsesequence() if (s1 !== peg$FAILED) { - s2 = peg$parsestack_tail(); + s2 = peg$parsestack_tail() if (s2 === peg$FAILED) { - s2 = null; + s2 = null } - peg$savedPos = s0; - s0 = peg$f22(s1, s2); + peg$savedPos = s0 + s0 = peg$f22(s1, s2) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsemini() { - var s0, s1, s2, s3, s4, s5, s6; + var s0, s1, s2, s3, s4, s5, s6 - s0 = peg$currPos; - s1 = peg$parsews(); - s2 = peg$parsequote(); + s0 = peg$currPos + s1 = peg$parsews() + s2 = peg$parsequote() if (s2 !== peg$FAILED) { - s3 = peg$parsews(); - s4 = peg$parsestack_or_choose(); + s3 = peg$parsews() + s4 = peg$parsestack_or_choose() if (s4 !== peg$FAILED) { - s5 = peg$parsews(); - s6 = peg$parsequote(); + s5 = peg$parsews() + s6 = peg$parsequote() if (s6 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f23(s4); + peg$savedPos = s0 + s0 = peg$f23(s4) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parseoperator() { - var s0; + var s0 - s0 = peg$parsescale(); + s0 = peg$parsescale() if (s0 === peg$FAILED) { - s0 = peg$parseslow(); + s0 = peg$parseslow() if (s0 === peg$FAILED) { - s0 = peg$parsefast(); + s0 = peg$parsefast() if (s0 === peg$FAILED) { - s0 = peg$parsetarget(); + s0 = peg$parsetarget() if (s0 === peg$FAILED) { - s0 = peg$parsebjorklund(); + s0 = peg$parsebjorklund() if (s0 === peg$FAILED) { - s0 = peg$parsestruct(); + s0 = peg$parsestruct() if (s0 === peg$FAILED) { - s0 = peg$parserotR(); + s0 = peg$parserotR() if (s0 === peg$FAILED) { - s0 = peg$parserotL(); + s0 = peg$parserotL() } } } @@ -1706,759 +4275,803 @@ function peg$parse(input, options) { } } - return s0; + return s0 } function peg$parsestruct() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; + s0 = peg$currPos if (input.substr(peg$currPos, 6) === peg$c22) { - s1 = peg$c22; - peg$currPos += 6; + s1 = peg$c22 + peg$currPos += 6 } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e33); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e33) + } } if (s1 !== peg$FAILED) { - s2 = peg$parsews(); - s3 = peg$parsemini_or_operator(); + s2 = peg$parsews() + s3 = peg$parsemini_or_operator() if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f24(s3); + peg$savedPos = s0 + s0 = peg$f24(s3) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsetarget() { - var s0, s1, s2, s3, s4, s5; + var s0, s1, s2, s3, s4, s5 - s0 = peg$currPos; + s0 = peg$currPos if (input.substr(peg$currPos, 6) === peg$c23) { - s1 = peg$c23; - peg$currPos += 6; + s1 = peg$c23 + peg$currPos += 6 } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e34); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e34) + } } if (s1 !== peg$FAILED) { - s2 = peg$parsews(); - s3 = peg$parsequote(); + s2 = peg$parsews() + s3 = peg$parsequote() if (s3 !== peg$FAILED) { - s4 = peg$parsestep(); + s4 = peg$parsestep() if (s4 !== peg$FAILED) { - s5 = peg$parsequote(); + s5 = peg$parsequote() if (s5 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f25(s4); + peg$savedPos = s0 + s0 = peg$f25(s4) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsebjorklund() { - var s0, s1, s2, s3, s4, s5, s6, s7; + var s0, s1, s2, s3, s4, s5, s6, s7 - s0 = peg$currPos; + s0 = peg$currPos if (input.substr(peg$currPos, 6) === peg$c24) { - s1 = peg$c24; - peg$currPos += 6; + s1 = peg$c24 + peg$currPos += 6 } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e35); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e35) + } } if (s1 !== peg$FAILED) { - s2 = peg$parsews(); - s3 = peg$parseint(); + s2 = peg$parsews() + s3 = peg$parseint() if (s3 !== peg$FAILED) { - s4 = peg$parsews(); - s5 = peg$parseint(); + s4 = peg$parsews() + s5 = peg$parseint() if (s5 !== peg$FAILED) { - s6 = peg$parsews(); - s7 = peg$parseint(); + s6 = peg$parsews() + s7 = peg$parseint() if (s7 === peg$FAILED) { - s7 = null; + s7 = null } - peg$savedPos = s0; - s0 = peg$f26(s3, s5, s7); + peg$savedPos = s0 + s0 = peg$f26(s3, s5, s7) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parseslow() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; + s0 = peg$currPos if (input.substr(peg$currPos, 4) === peg$c25) { - s1 = peg$c25; - peg$currPos += 4; + s1 = peg$c25 + peg$currPos += 4 } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e36); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e36) + } } if (s1 !== peg$FAILED) { - s2 = peg$parsews(); - s3 = peg$parsenumber(); + s2 = peg$parsews() + s3 = peg$parsenumber() if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f27(s3); + peg$savedPos = s0 + s0 = peg$f27(s3) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parserotL() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; + s0 = peg$currPos if (input.substr(peg$currPos, 4) === peg$c26) { - s1 = peg$c26; - peg$currPos += 4; + s1 = peg$c26 + peg$currPos += 4 } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e37); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e37) + } } if (s1 !== peg$FAILED) { - s2 = peg$parsews(); - s3 = peg$parsenumber(); + s2 = peg$parsews() + s3 = peg$parsenumber() if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f28(s3); + peg$savedPos = s0 + s0 = peg$f28(s3) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parserotR() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; + s0 = peg$currPos if (input.substr(peg$currPos, 4) === peg$c27) { - s1 = peg$c27; - peg$currPos += 4; + s1 = peg$c27 + peg$currPos += 4 } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e38); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e38) + } } if (s1 !== peg$FAILED) { - s2 = peg$parsews(); - s3 = peg$parsenumber(); + s2 = peg$parsews() + s3 = peg$parsenumber() if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f29(s3); + peg$savedPos = s0 + s0 = peg$f29(s3) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsefast() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; + s0 = peg$currPos if (input.substr(peg$currPos, 4) === peg$c28) { - s1 = peg$c28; - peg$currPos += 4; + s1 = peg$c28 + peg$currPos += 4 } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e39); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e39) + } } if (s1 !== peg$FAILED) { - s2 = peg$parsews(); - s3 = peg$parsenumber(); + s2 = peg$parsews() + s3 = peg$parsenumber() if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f30(s3); + peg$savedPos = s0 + s0 = peg$f30(s3) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsescale() { - var s0, s1, s2, s3, s4, s5; + var s0, s1, s2, s3, s4, s5 - s0 = peg$currPos; + s0 = peg$currPos if (input.substr(peg$currPos, 5) === peg$c29) { - s1 = peg$c29; - peg$currPos += 5; + s1 = peg$c29 + peg$currPos += 5 } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e40); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e40) + } } if (s1 !== peg$FAILED) { - s2 = peg$parsews(); - s3 = peg$parsequote(); + s2 = peg$parsews() + s3 = peg$parsequote() if (s3 !== peg$FAILED) { - s4 = []; - s5 = peg$parsestep_char(); + s4 = [] + s5 = peg$parsestep_char() if (s5 !== peg$FAILED) { while (s5 !== peg$FAILED) { - s4.push(s5); - s5 = peg$parsestep_char(); + s4.push(s5) + s5 = peg$parsestep_char() } } else { - s4 = peg$FAILED; + s4 = peg$FAILED } if (s4 !== peg$FAILED) { - s5 = peg$parsequote(); + s5 = peg$parsequote() if (s5 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f31(s4); + peg$savedPos = s0 + s0 = peg$f31(s4) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsecomment() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; + s0 = peg$currPos if (input.substr(peg$currPos, 2) === peg$c30) { - s1 = peg$c30; - peg$currPos += 2; + s1 = peg$c30 + peg$currPos += 2 } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e41); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e41) + } } if (s1 !== peg$FAILED) { - s2 = []; - s3 = input.charAt(peg$currPos); + s2 = [] + s3 = input.charAt(peg$currPos) if (peg$r8.test(s3)) { - peg$currPos++; + peg$currPos++ } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e42); } - } - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = input.charAt(peg$currPos); - if (peg$r8.test(s3)) { - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e42); } + s3 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e42) } } - s1 = [s1, s2]; - s0 = s1; + while (s3 !== peg$FAILED) { + s2.push(s3) + s3 = input.charAt(peg$currPos) + if (peg$r8.test(s3)) { + peg$currPos++ + } else { + s3 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e42) + } + } + } + s1 = [s1, s2] + s0 = s1 } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsecat() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9 - s0 = peg$currPos; + s0 = peg$currPos if (input.substr(peg$currPos, 3) === peg$c31) { - s1 = peg$c31; - peg$currPos += 3; + s1 = peg$c31 + peg$currPos += 3 } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e43); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e43) + } } if (s1 !== peg$FAILED) { - s2 = peg$parsews(); + s2 = peg$parsews() if (input.charCodeAt(peg$currPos) === 91) { - s3 = peg$c6; - peg$currPos++; + s3 = peg$c6 + peg$currPos++ } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e16); } + s3 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e16) + } } if (s3 !== peg$FAILED) { - s4 = peg$parsews(); - s5 = peg$parsemini_or_operator(); + s4 = peg$parsews() + s5 = peg$parsemini_or_operator() if (s5 !== peg$FAILED) { - s6 = []; - s7 = peg$currPos; - s8 = peg$parsecomma(); + s6 = [] + s7 = peg$currPos + s8 = peg$parsecomma() if (s8 !== peg$FAILED) { - s9 = peg$parsemini_or_operator(); + s9 = peg$parsemini_or_operator() if (s9 !== peg$FAILED) { - peg$savedPos = s7; - s7 = peg$f32(s5, s9); + peg$savedPos = s7 + s7 = peg$f32(s5, s9) } else { - peg$currPos = s7; - s7 = peg$FAILED; + peg$currPos = s7 + s7 = peg$FAILED } } else { - peg$currPos = s7; - s7 = peg$FAILED; + peg$currPos = s7 + s7 = peg$FAILED } while (s7 !== peg$FAILED) { - s6.push(s7); - s7 = peg$currPos; - s8 = peg$parsecomma(); + s6.push(s7) + s7 = peg$currPos + s8 = peg$parsecomma() if (s8 !== peg$FAILED) { - s9 = peg$parsemini_or_operator(); + s9 = peg$parsemini_or_operator() if (s9 !== peg$FAILED) { - peg$savedPos = s7; - s7 = peg$f32(s5, s9); + peg$savedPos = s7 + s7 = peg$f32(s5, s9) } else { - peg$currPos = s7; - s7 = peg$FAILED; + peg$currPos = s7 + s7 = peg$FAILED } } else { - peg$currPos = s7; - s7 = peg$FAILED; + peg$currPos = s7 + s7 = peg$FAILED } } - s7 = peg$parsews(); + s7 = peg$parsews() if (input.charCodeAt(peg$currPos) === 93) { - s8 = peg$c7; - peg$currPos++; + s8 = peg$c7 + peg$currPos++ } else { - s8 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e17); } + s8 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e17) + } } if (s8 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f33(s5, s6); + peg$savedPos = s0 + s0 = peg$f33(s5, s6) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsemini_or_group() { - var s0; + var s0 - s0 = peg$parsecat(); + s0 = peg$parsecat() if (s0 === peg$FAILED) { - s0 = peg$parsemini(); + s0 = peg$parsemini() } - return s0; + return s0 } function peg$parsemini_or_operator() { - var s0, s1, s2, s3, s4, s5; + var s0, s1, s2, s3, s4, s5 - s0 = peg$currPos; - s1 = peg$parsemini_or_group(); + s0 = peg$currPos + s1 = peg$parsemini_or_group() if (s1 !== peg$FAILED) { - s2 = peg$parsews(); - s3 = []; - s4 = peg$parsecomment(); + s2 = peg$parsews() + s3 = [] + s4 = peg$parsecomment() while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$parsecomment(); + s3.push(s4) + s4 = peg$parsecomment() } - peg$savedPos = s0; - s0 = peg$f34(s1); + peg$savedPos = s0 + s0 = peg$f34(s1) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } if (s0 === peg$FAILED) { - s0 = peg$currPos; - s1 = peg$parseoperator(); + s0 = peg$currPos + s1 = peg$parseoperator() if (s1 !== peg$FAILED) { - s2 = peg$parsews(); + s2 = peg$parsews() if (input.charCodeAt(peg$currPos) === 36) { - s3 = peg$c32; - peg$currPos++; + s3 = peg$c32 + peg$currPos++ } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e44); } + s3 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e44) + } } if (s3 !== peg$FAILED) { - s4 = peg$parsews(); - s5 = peg$parsemini_or_operator(); + s4 = peg$parsews() + s5 = peg$parsemini_or_operator() if (s5 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f35(s1, s5); + peg$savedPos = s0 + s0 = peg$f35(s1, s5) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } - return s0; + return s0 } function peg$parsesequ_or_operator_or_comment() { - var s0, s1; + var s0, s1 - s0 = peg$currPos; - s1 = peg$parsemini_or_operator(); + s0 = peg$currPos + s1 = peg$parsemini_or_operator() if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f36(s1); + peg$savedPos = s0 + s1 = peg$f36(s1) } - s0 = s1; + s0 = s1 if (s0 === peg$FAILED) { - s0 = peg$parsecomment(); + s0 = peg$parsecomment() } - return s0; + return s0 } function peg$parsemini_definition() { - var s0; + var s0 - s0 = peg$parsesequ_or_operator_or_comment(); + s0 = peg$parsesequ_or_operator_or_comment() - return s0; + return s0 } function peg$parsecommand() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; - s1 = peg$parsews(); - s2 = peg$parsesetcps(); + s0 = peg$currPos + s1 = peg$parsews() + s2 = peg$parsesetcps() if (s2 === peg$FAILED) { - s2 = peg$parsesetbpm(); + s2 = peg$parsesetbpm() if (s2 === peg$FAILED) { - s2 = peg$parsehush(); + s2 = peg$parsehush() } } if (s2 !== peg$FAILED) { - s3 = peg$parsews(); - peg$savedPos = s0; - s0 = peg$f37(s2); + s3 = peg$parsews() + peg$savedPos = s0 + s0 = peg$f37(s2) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsesetcps() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; + s0 = peg$currPos if (input.substr(peg$currPos, 6) === peg$c33) { - s1 = peg$c33; - peg$currPos += 6; + s1 = peg$c33 + peg$currPos += 6 } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e45); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e45) + } } if (s1 !== peg$FAILED) { - s2 = peg$parsews(); - s3 = peg$parsenumber(); + s2 = peg$parsews() + s3 = peg$parsenumber() if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f38(s3); + peg$savedPos = s0 + s0 = peg$f38(s3) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsesetbpm() { - var s0, s1, s2, s3; + var s0, s1, s2, s3 - s0 = peg$currPos; + s0 = peg$currPos if (input.substr(peg$currPos, 6) === peg$c34) { - s1 = peg$c34; - peg$currPos += 6; + s1 = peg$c34 + peg$currPos += 6 } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e46); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e46) + } } if (s1 !== peg$FAILED) { - s2 = peg$parsews(); - s3 = peg$parsenumber(); + s2 = peg$parsews() + s3 = peg$parsenumber() if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f39(s3); + peg$savedPos = s0 + s0 = peg$f39(s3) } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } } else { - peg$currPos = s0; - s0 = peg$FAILED; + peg$currPos = s0 + s0 = peg$FAILED } - return s0; + return s0 } function peg$parsehush() { - var s0, s1; + var s0, s1 - s0 = peg$currPos; + s0 = peg$currPos if (input.substr(peg$currPos, 4) === peg$c35) { - s1 = peg$c35; - peg$currPos += 4; + s1 = peg$c35 + peg$currPos += 4 } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e47); } + s1 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e47) + } } if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f40(); + peg$savedPos = s0 + s1 = peg$f40() } - s0 = s1; + s0 = s1 - return s0; + return s0 } function peg$parsestatement() { - var s0; + var s0 - s0 = peg$parsemini_definition(); + s0 = peg$parsemini_definition() if (s0 === peg$FAILED) { - s0 = peg$parsecommand(); + s0 = peg$parsecommand() } - return s0; + return s0 } function peg$parseunicode_letter() { - var s0; + var s0 - s0 = input.charAt(peg$currPos); + s0 = input.charAt(peg$currPos) if (peg$r9.test(s0)) { - peg$currPos++; + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e48); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e48) + } } - return s0; + return s0 } function peg$parseLl() { - var s0; + var s0 - s0 = input.charAt(peg$currPos); + s0 = input.charAt(peg$currPos) if (peg$r10.test(s0)) { - peg$currPos++; + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e49); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e49) + } } - return s0; + return s0 } function peg$parseLm() { - var s0; + var s0 - s0 = input.charAt(peg$currPos); + s0 = input.charAt(peg$currPos) if (peg$r11.test(s0)) { - peg$currPos++; + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e50); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e50) + } } - return s0; + return s0 } function peg$parseLo() { - var s0; + var s0 - s0 = input.charAt(peg$currPos); + s0 = input.charAt(peg$currPos) if (peg$r12.test(s0)) { - peg$currPos++; + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e51); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e51) + } } - return s0; + return s0 } function peg$parseLt() { - var s0; + var s0 - s0 = input.charAt(peg$currPos); + s0 = input.charAt(peg$currPos) if (peg$r13.test(s0)) { - peg$currPos++; + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e52); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e52) + } } - return s0; + return s0 } function peg$parseLu() { - var s0; + var s0 - s0 = input.charAt(peg$currPos); + s0 = input.charAt(peg$currPos) if (peg$r14.test(s0)) { - peg$currPos++; + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e53); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e53) + } } - return s0; + return s0 } function peg$parseNl() { - var s0; + var s0 - s0 = input.charAt(peg$currPos); + s0 = input.charAt(peg$currPos) if (peg$r15.test(s0)) { - peg$currPos++; + peg$currPos++ } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e54); } + s0 = peg$FAILED + if (peg$silentFails === 0) { + peg$fail(peg$e54) + } } - return s0; + return s0 } - - var AtomStub = function(source) - { - this.type_ = "atom"; - this.source_ = source; - this.location_ = location(); + var AtomStub = function (source) { + this.type_ = 'atom' + this.source_ = source + this.location_ = location() } - var PatternStub = function(source, alignment, seed, _steps) - { - this.type_ = "pattern"; - this.arguments_ = { alignment: alignment, _steps: _steps }; + var PatternStub = function (source, alignment, seed, _steps) { + this.type_ = 'pattern' + this.arguments_ = { alignment: alignment, _steps: _steps } if (seed !== undefined) { - this.arguments_.seed = seed; + this.arguments_.seed = seed } - this.source_ = source; + this.source_ = source } - var OperatorStub = function(name, args, source) - { - this.type_ = name; - this.arguments_ = args; - this.source_ = source; + var OperatorStub = function (name, args, source) { + this.type_ = name + this.arguments_ = args + this.source_ = source } - var ElementStub = function(source, options) - { - this.type_ = "element"; - this.source_ = source; - this.options_ = options; - this.location_ = location(); + var ElementStub = function (source, options) { + this.type_ = 'element' + this.source_ = source + this.options_ = options + this.location_ = location() } - var CommandStub = function(name, options) - { - this.type_ = "command"; - this.name_ = name; - this.options_ = options; + var CommandStub = function (name, options) { + this.type_ = 'command' + this.name_ = name + this.options_ = options } - var seed = 0; + var seed = 0 - peg$result = peg$startRuleFunction(); + peg$result = peg$startRuleFunction() if (options.peg$library) { return /** @type {any} */ ({ @@ -2466,14 +5079,14 @@ function peg$parse(input, options) { peg$currPos, peg$FAILED, peg$maxFailExpected, - peg$maxFailPos - }); + peg$maxFailPos, + }) } if (peg$result !== peg$FAILED && peg$currPos === input.length) { - return peg$result; + return peg$result } else { if (peg$result !== peg$FAILED && peg$currPos < input.length) { - peg$fail(peg$endExpectation()); + peg$fail(peg$endExpectation()) } throw peg$buildStructuredError( @@ -2482,16 +5095,10 @@ function peg$parse(input, options) { peg$maxFailPos < input.length ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) - ); + ) } } -const peg$allowedStartRules = [ - "start" -]; +const peg$allowedStartRules = ['start'] -export { - peg$allowedStartRules as StartRules, - peg$SyntaxError as SyntaxError, - peg$parse as parse -}; +export { peg$allowedStartRules as StartRules, peg$SyntaxError as SyntaxError, peg$parse as parse } diff --git a/src/strudel/mini/krill.pegjs b/src/strudel/mini/krill.pegjs deleted file mode 100644 index 8af82ca..0000000 --- a/src/strudel/mini/krill.pegjs +++ /dev/null @@ -1,303 +0,0 @@ -/* -krill.pegjs - -Copyright (C) 2022 Strudel contributors - see -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 . -*/ - -// Some terminology: -// mini(notation) = a series of elements placed between quotes -// a stack = a series of vertically aligned slices sharing the same overall length -// a sequence = a series of horizontally aligned elements -// a choose = a series of elements, one of which is chosen at random - - -{ - var AtomStub = function(source) - { - this.type_ = "atom"; - this.source_ = source; - this.location_ = location(); - } - - var PatternStub = function(source, alignment, seed, _steps) - { - this.type_ = "pattern"; - this.arguments_ = { alignment: alignment, _steps: _steps }; - if (seed !== undefined) { - this.arguments_.seed = seed; - } - this.source_ = source; - } - - var OperatorStub = function(name, args, source) - { - this.type_ = name; - this.arguments_ = args; - this.source_ = source; - } - - var ElementStub = function(source, options) - { - this.type_ = "element"; - this.source_ = source; - this.options_ = options; - this.location_ = location(); - } - - var CommandStub = function(name, options) - { - this.type_ = "command"; - this.name_ = name; - this.options_ = options; - } - - var seed = 0; -} - -start = statement - -// ----- Numbers ----- - -number "number" - = minus? int frac? exp? { return parseFloat(text()); } - -decimal_point - = "." - -digit1_9 - = [1-9] - -e - = [eE] - -exp - = e (minus / plus)? DIGIT+ - -frac - = decimal_point DIGIT+ - -int - = zero / (digit1_9 DIGIT*) - -intneg - = minus? int { return parseInt(text()); } - -minus - = "-" - -plus - = "+" - -zero - = "0" - -DIGIT = [0-9] - -// ------------------ delimiters --------------------------- - -ws "whitespace" = [ \n\r\t\u00A0]* -comma = ws "," ws -pipe = ws "|" ws -dot = ws "." ws -quote = '"' / "'" - -// ------------------ steps and cycles --------------------------- - -// single step definition (e.g bd) -step_char "a letter, a number, \"-\", \"#\", \".\", \"^\", \"_\"" = - unicode_letter / [0-9~] / "-" / "#" / "." / "^" / "_" - -step = ws chars:step_char+ ws !{ const s = chars.join(""); return (s === ".") || (s === "_") } { return new AtomStub(chars.join("")) } - -// define a sub cycle e.g. [1 2, 3 [4]] -sub_cycle = ws "[" ws s:stack_or_choose ws "]" ws { return s } - -// define a polymeter e.g. {1 2, 3 4 5} -polymeter = ws "{" ws s:polymeter_stack ws "}" stepsPerCycle:polymeter_steps? ws - { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; } - -polymeter_steps = "%"a:slice - { return a } - -// define a step-per-cycle timeline e.g <1 3 [3 5]>. We simply defer to a sequence and -// change the alignment to slowcat -slow_sequence = ws "<" ws s:polymeter_stack ws ">" ws - { s.arguments_.alignment = 'polymeter_slowcat'; return s; } - -// a slice is either a single step or a sub cycle -slice = step / sub_cycle / polymeter / slow_sequence - -// slice modifier affects the timing/size of a slice (e.g. [a b c]@3) -// at this point, we assume we can represent them as regular sequence operators -slice_op = op_weight / op_bjorklund / op_slow / op_fast / op_replicate / op_degrade / op_tail / op_range - -op_weight = ws ("@" / "_") a:number? - { return x => x.options_['weight'] = (x.options_['weight'] ?? 1) + (a ?? 2) - 1 } - -op_replicate = ws "!" a:number? - { return x => {// A bit fiddly, to support both x!4 and x!!! as equivalent.. - const reps = (x.options_['reps'] ?? 1) + (a ?? 2) - 1; - x.options_['reps'] = reps; - x.options_['ops'] = x.options_['ops'].filter(x => x.type_ !== "replicate"); - x.options_['ops'].push({ type_: "replicate", arguments_ :{ amount:reps }}); - x.options_['weight'] = reps; - } - } - -op_bjorklund = "(" ws p:slice_with_ops ws comma ws s:slice_with_ops ws comma? ws r:slice_with_ops? ws ")" - { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) } - -op_slow = "/"a:slice - { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) } - -op_fast = "*"a:slice - { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) } - -op_degrade = "?"a:number? - { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a, seed: seed++ } }) } - -op_tail = ":" s:slice - { return x => x.options_['ops'].push({ type_: "tail", arguments_ :{ element:s } }) } - -op_range = ".." s:slice - { return x => x.options_['ops'].push({ type_: "range", arguments_ :{ element:s } }) } - -// a slice with an modifier applied i.e [bd@4 sd@3]@2 hh] -slice_with_ops = s:slice ops:slice_op* - { const result = new ElementStub(s, {ops: [], weight: 1, reps: 1}); - for (const op of ops) { - op(result); - } - return result; - } - -// a sequence is a combination of one or more successive slices (as an array) -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)+ - { return { alignment: 'stack', list: tail }; } - -// a choose is a series of pipe-separated sequence, one of which is -// chosen at random, each cycle -choose_tail = tail:(pipe @sequence)+ - { return { alignment: 'rand', list: tail, seed: seed++ }; } - -// a foot separates subsequences, as an alternative to wrapping them in [] -dot_tail = tail:(dot @sequence)+ - { return { alignment: 'feet', list: tail, seed: seed++ }; } - -// if the stack contains only one element, we don't create a stack but return the -// underlying element -stack_or_choose = head:sequence tail:(stack_tail / choose_tail / dot_tail)? - {if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment, tail.seed); } else { return head; } } - -polymeter_stack = head:sequence tail:stack_tail? - { return new PatternStub(tail ? [head, ...tail.list] : [head], 'polymeter'); } - - -// Mini-notation innards ends -// ---------->8---------->8---------->8---------->8---------->8---------- -// Experimental haskellish parser begins - -// mini-notation = a quoted stack -mini = ws quote ws sc:stack_or_choose ws quote - { return sc; } - -// ------------------ operators --------------------------- - -operator = scale / slow / fast / target / bjorklund / struct / rotR / rotL - -struct = "struct" ws s:mini_or_operator - { return { name: "struct", args: { mini:s }}} - -target = "target" ws quote s:step quote - { return { name: "target", args : { name:s}}} - -bjorklund = "euclid" ws p:int ws s:int ws r:int? - { return { name: "bjorklund", args :{ pulse: p, step:parseInt(s) }}} - -slow = "slow" ws a:number - { return { name: "stretch", args :{ amount: a}}} - -rotL = "rotL" ws a:number - { return { name: "shift", args :{ amount: "-"+a}}} - -rotR = "rotR" ws a:number - { return { name: "shift", args :{ amount: a}}} - -fast = "fast" ws a:number - { return { name: "stretch", args :{ amount: "1/"+a}}} - -scale = "scale" ws quote s:(step_char)+ quote -{ return { name: "scale", args :{ scale: s.join("")}}} - -comment = '//' p:([^\n]*) - -// ---------------- grouping -------------------------------- - -group_operator = cat - -// cat is another form of timeline -cat = "cat" ws "[" ws s:mini_or_operator ss:(comma v:mini_or_operator { return v})* ws "]" - { ss.unshift(s); return new PatternStub(ss, 'slowcat'); } - -// ------------------ high level mini --------------------------- - -mini_or_group = - group_operator / - mini - -mini_or_operator = - sg:mini_or_group ws (comment)* - {return sg} - / o:operator ws "$" ws soc:mini_or_operator - { return new OperatorStub(o.name,o.args,soc)} - -sequ_or_operator_or_comment = - sc: mini_or_operator - { return sc } - / comment - -mini_definition = s:sequ_or_operator_or_comment - -// ---------------------- statements ---------------------------- - -command = ws c:(setcps / setbpm / hush) ws - { return c } - -setcps = "setcps" ws v:number - { return new CommandStub("setcps", { value: v})} - -setbpm = "setbpm" ws v:number - { return new CommandStub("setcps", { value: (v/120/2)})} - -hush = "hush" - { return new CommandStub("hush")} - -// ---------------------- statements ---------------------------- - -statement = mini_definition / command - -// ---------------------- unicode ---------------------------- - -unicode_letter = Lu / Ll / Lt / Lm / Lo / Nl - -// Letter, Lowercase -Ll = [\u0061-\u007A\u00B5\u00DF-\u00F6\u00F8-\u00FF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137-\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148-\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C-\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA-\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9-\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC-\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF-\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F-\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02AF\u0371\u0373\u0377\u037B-\u037D\u0390\u03AC-\u03CE\u03D0-\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB-\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE-\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0560-\u0588\u10D0-\u10FA\u10FD-\u10FF\u13F8-\u13FD\u1C80-\u1C88\u1D00-\u1D2B\u1D6B-\u1D77\u1D79-\u1D9A\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6-\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FC7\u1FD0-\u1FD3\u1FD6-\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6-\u1FF7\u210A\u210E-\u210F\u2113\u212F\u2134\u2139\u213C-\u213D\u2146-\u2149\u214E\u2184\u2C30-\u2C5E\u2C61\u2C65-\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73-\u2C74\u2C76-\u2C7B\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3-\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F\uA771-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7AF\uA7B5\uA7B7\uA7B9\uA7FA\uAB30-\uAB5A\uAB60-\uAB65\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A] - -// Letter, Modifier -Lm = [\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0374\u037A\u0559\u0640\u06E5-\u06E6\u07F4-\u07F5\u07FA\u081A\u0824\u0828\u0971\u0E46\u0EC6\u10FC\u17D7\u1843\u1AA7\u1C78-\u1C7D\u1D2C-\u1D6A\u1D78\u1D9B-\u1DBF\u2071\u207F\u2090-\u209C\u2C7C-\u2C7D\u2D6F\u2E2F\u3005\u3031-\u3035\u303B\u309D-\u309E\u30FC-\u30FE\uA015\uA4F8-\uA4FD\uA60C\uA67F\uA69C-\uA69D\uA717-\uA71F\uA770\uA788\uA7F8-\uA7F9\uA9CF\uA9E6\uAA70\uAADD\uAAF3-\uAAF4\uAB5C-\uAB5F\uFF70\uFF9E-\uFF9F] - -// Letter, Other -Lo = [\u00AA\u00BA\u01BB\u01C0-\u01C3\u0294\u05D0-\u05EA\u05EF-\u05F2\u0620-\u063F\u0641-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u0800-\u0815\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0972-\u0980\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u09FC\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60-\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0CF1-\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E45\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17DC\u1820-\u1842\u1844-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C77\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5-\u1CF6\u2135-\u2138\u2D30-\u2D67\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3006\u303C\u3041-\u3096\u309F\u30A1-\u30FA\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEF\uA000-\uA014\uA016-\uA48C\uA4D0-\uA4F7\uA500-\uA60B\uA610-\uA61F\uA62A-\uA62B\uA66E\uA6A0-\uA6E5\uA78F\uA7F7\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD-\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9E0-\uA9E4\uA9E7-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA6F\uAA71-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5-\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADC\uAAE0-\uAAEA\uAAF2\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF66-\uFF6F\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC] - -// Letter, Titlecase -Lt = [\u01C5\u01C8\u01CB\u01F2\u1F88-\u1F8F\u1F98-\u1F9F\u1FA8-\u1FAF\u1FBC\u1FCC\u1FFC] - -// Letter, Uppercase -Lu = [\u0041-\u005A\u00C0-\u00D6\u00D8-\u00DE\u0100\u0102\u0104\u0106\u0108\u010A\u010C\u010E\u0110\u0112\u0114\u0116\u0118\u011A\u011C\u011E\u0120\u0122\u0124\u0126\u0128\u012A\u012C\u012E\u0130\u0132\u0134\u0136\u0139\u013B\u013D\u013F\u0141\u0143\u0145\u0147\u014A\u014C\u014E\u0150\u0152\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0162\u0164\u0166\u0168\u016A\u016C\u016E\u0170\u0172\u0174\u0176\u0178-\u0179\u017B\u017D\u0181-\u0182\u0184\u0186-\u0187\u0189-\u018B\u018E-\u0191\u0193-\u0194\u0196-\u0198\u019C-\u019D\u019F-\u01A0\u01A2\u01A4\u01A6-\u01A7\u01A9\u01AC\u01AE-\u01AF\u01B1-\u01B3\u01B5\u01B7-\u01B8\u01BC\u01C4\u01C7\u01CA\u01CD\u01CF\u01D1\u01D3\u01D5\u01D7\u01D9\u01DB\u01DE\u01E0\u01E2\u01E4\u01E6\u01E8\u01EA\u01EC\u01EE\u01F1\u01F4\u01F6-\u01F8\u01FA\u01FC\u01FE\u0200\u0202\u0204\u0206\u0208\u020A\u020C\u020E\u0210\u0212\u0214\u0216\u0218\u021A\u021C\u021E\u0220\u0222\u0224\u0226\u0228\u022A\u022C\u022E\u0230\u0232\u023A-\u023B\u023D-\u023E\u0241\u0243-\u0246\u0248\u024A\u024C\u024E\u0370\u0372\u0376\u037F\u0386\u0388-\u038A\u038C\u038E-\u038F\u0391-\u03A1\u03A3-\u03AB\u03CF\u03D2-\u03D4\u03D8\u03DA\u03DC\u03DE\u03E0\u03E2\u03E4\u03E6\u03E8\u03EA\u03EC\u03EE\u03F4\u03F7\u03F9-\u03FA\u03FD-\u042F\u0460\u0462\u0464\u0466\u0468\u046A\u046C\u046E\u0470\u0472\u0474\u0476\u0478\u047A\u047C\u047E\u0480\u048A\u048C\u048E\u0490\u0492\u0494\u0496\u0498\u049A\u049C\u049E\u04A0\u04A2\u04A4\u04A6\u04A8\u04AA\u04AC\u04AE\u04B0\u04B2\u04B4\u04B6\u04B8\u04BA\u04BC\u04BE\u04C0-\u04C1\u04C3\u04C5\u04C7\u04C9\u04CB\u04CD\u04D0\u04D2\u04D4\u04D6\u04D8\u04DA\u04DC\u04DE\u04E0\u04E2\u04E4\u04E6\u04E8\u04EA\u04EC\u04EE\u04F0\u04F2\u04F4\u04F6\u04F8\u04FA\u04FC\u04FE\u0500\u0502\u0504\u0506\u0508\u050A\u050C\u050E\u0510\u0512\u0514\u0516\u0518\u051A\u051C\u051E\u0520\u0522\u0524\u0526\u0528\u052A\u052C\u052E\u0531-\u0556\u10A0-\u10C5\u10C7\u10CD\u13A0-\u13F5\u1C90-\u1CBA\u1CBD-\u1CBF\u1E00\u1E02\u1E04\u1E06\u1E08\u1E0A\u1E0C\u1E0E\u1E10\u1E12\u1E14\u1E16\u1E18\u1E1A\u1E1C\u1E1E\u1E20\u1E22\u1E24\u1E26\u1E28\u1E2A\u1E2C\u1E2E\u1E30\u1E32\u1E34\u1E36\u1E38\u1E3A\u1E3C\u1E3E\u1E40\u1E42\u1E44\u1E46\u1E48\u1E4A\u1E4C\u1E4E\u1E50\u1E52\u1E54\u1E56\u1E58\u1E5A\u1E5C\u1E5E\u1E60\u1E62\u1E64\u1E66\u1E68\u1E6A\u1E6C\u1E6E\u1E70\u1E72\u1E74\u1E76\u1E78\u1E7A\u1E7C\u1E7E\u1E80\u1E82\u1E84\u1E86\u1E88\u1E8A\u1E8C\u1E8E\u1E90\u1E92\u1E94\u1E9E\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EBE\u1EC0\u1EC2\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u1EF6\u1EF8\u1EFA\u1EFC\u1EFE\u1F08-\u1F0F\u1F18-\u1F1D\u1F28-\u1F2F\u1F38-\u1F3F\u1F48-\u1F4D\u1F59\u1F5B\u1F5D\u1F5F\u1F68-\u1F6F\u1FB8-\u1FBB\u1FC8-\u1FCB\u1FD8-\u1FDB\u1FE8-\u1FEC\u1FF8-\u1FFB\u2102\u2107\u210B-\u210D\u2110-\u2112\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u2130-\u2133\u213E-\u213F\u2145\u2183\u2C00-\u2C2E\u2C60\u2C62-\u2C64\u2C67\u2C69\u2C6B\u2C6D-\u2C70\u2C72\u2C75\u2C7E-\u2C80\u2C82\u2C84\u2C86\u2C88\u2C8A\u2C8C\u2C8E\u2C90\u2C92\u2C94\u2C96\u2C98\u2C9A\u2C9C\u2C9E\u2CA0\u2CA2\u2CA4\u2CA6\u2CA8\u2CAA\u2CAC\u2CAE\u2CB0\u2CB2\u2CB4\u2CB6\u2CB8\u2CBA\u2CBC\u2CBE\u2CC0\u2CC2\u2CC4\u2CC6\u2CC8\u2CCA\u2CCC\u2CCE\u2CD0\u2CD2\u2CD4\u2CD6\u2CD8\u2CDA\u2CDC\u2CDE\u2CE0\u2CE2\u2CEB\u2CED\u2CF2\uA640\uA642\uA644\uA646\uA648\uA64A\uA64C\uA64E\uA650\uA652\uA654\uA656\uA658\uA65A\uA65C\uA65E\uA660\uA662\uA664\uA666\uA668\uA66A\uA66C\uA680\uA682\uA684\uA686\uA688\uA68A\uA68C\uA68E\uA690\uA692\uA694\uA696\uA698\uA69A\uA722\uA724\uA726\uA728\uA72A\uA72C\uA72E\uA732\uA734\uA736\uA738\uA73A\uA73C\uA73E\uA740\uA742\uA744\uA746\uA748\uA74A\uA74C\uA74E\uA750\uA752\uA754\uA756\uA758\uA75A\uA75C\uA75E\uA760\uA762\uA764\uA766\uA768\uA76A\uA76C\uA76E\uA779\uA77B\uA77D-\uA77E\uA780\uA782\uA784\uA786\uA78B\uA78D\uA790\uA792\uA796\uA798\uA79A\uA79C\uA79E\uA7A0\uA7A2\uA7A4\uA7A6\uA7A8\uA7AA-\uA7AE\uA7B0-\uA7B4\uA7B6\uA7B8\uFF21-\uFF3A] - -// Number, Letter -Nl = [\u16EE-\u16F0\u2160-\u2182\u2185-\u2188\u3007\u3021-\u3029\u3038-\u303A\uA6E6-\uA6EF] diff --git a/src/strudel/mini/mini.mjs b/src/strudel/mini/mini.mjs index d138d9f..e338dbc 100644 --- a/src/strudel/mini/mini.mjs +++ b/src/strudel/mini/mini.mjs @@ -4,258 +4,276 @@ Copyright (C) 2022 Strudel contributors - see . */ -import * as krill from './krill-parser.js'; -import * as strudel from '@strudel/core'; -import Fraction, { lcm } from '@strudel/core/fraction.mjs'; +import * as krill from './krill-parser.js' +import * as strudel from '../core/index.mjs' +import Fraction, { lcm } from '../core/fraction.mjs' -const randOffset = 0.0003; +const randOffset = 0.0003 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; + 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_) { case 'stretch': { - const legalTypes = ['fast', 'slow']; - const { type, amount } = op.arguments_; + const legalTypes = ['fast', 'slow'] + const { type, amount } = op.arguments_ if (!legalTypes.includes(type)) { - throw new Error(`mini: stretch: type must be one of ${legalTypes.join('|')} but got ${type}`); + throw new Error( + `mini: stretch: type must be one of ${legalTypes.join('|')} but got ${type}` + ) } - pat = strudel.reify(pat)[type](enter(amount)); - break; + pat = strudel.reify(pat)[type](enter(amount)) + break } case 'replicate': { - const { amount } = op.arguments_; - pat = strudel.reify(pat); - pat = pat._repeatCycles(amount)._fast(amount); - break; + const { amount } = op.arguments_ + pat = strudel.reify(pat) + pat = pat._repeatCycles(amount)._fast(amount) + break } case 'bjorklund': { if (op.arguments_.rotation) { - pat = pat.euclidRot(enter(op.arguments_.pulse), enter(op.arguments_.step), enter(op.arguments_.rotation)); + pat = pat.euclidRot( + enter(op.arguments_.pulse), + enter(op.arguments_.step), + enter(op.arguments_.rotation) + ) } else { - pat = pat.euclid(enter(op.arguments_.pulse), enter(op.arguments_.step)); + pat = pat.euclid(enter(op.arguments_.pulse), enter(op.arguments_.step)) } - break; + break } case 'degradeBy': { pat = strudel .reify(pat) - ._degradeByWith(strudel.rand.early(randOffset * op.arguments_.seed), op.arguments_.amount ?? 0.5); - break; + ._degradeByWith( + strudel.rand.early(randOffset * op.arguments_.seed), + op.arguments_.amount ?? 0.5 + ) + break } case 'tail': { - const friend = enter(op.arguments_.element); - pat = pat.fmap((a) => (b) => (Array.isArray(a) ? [...a, b] : [a, b])).appLeft(friend); - break; + const friend = enter(op.arguments_.element) + pat = pat.fmap((a) => (b) => (Array.isArray(a) ? [...a, b] : [a, b])).appLeft(friend) + break } case 'range': { - const friend = enter(op.arguments_.element); - pat = strudel.reify(pat); + const friend = enter(op.arguments_.element) + pat = strudel.reify(pat) const arrayRange = (start, stop, step = 1) => Array.from({ length: Math.abs(stop - start) / step + 1 }, (value, index) => - start < stop ? start + index * step : start - index * step, - ); - let range = (apat, bpat) => apat.squeezeBind((a) => bpat.bind((b) => strudel.fastcat(...arrayRange(a, b)))); - pat = range(pat, friend); - break; + start < stop ? start + index * step : start - index * step + ) + let range = (apat, bpat) => + apat.squeezeBind((a) => bpat.bind((b) => strudel.fastcat(...arrayRange(a, b)))) + pat = range(pat, friend) + break } default: { - console.warn(`operator "${op.type_}" not implemented`); + console.warn(`operator "${op.type_}" not implemented`) } } } } - pat.__steps_source = pat.__steps_source || steps_source; - return pat; -}; + pat.__steps_source = pat.__steps_source || steps_source + return pat +} // expects ast from mini2ast + quoted mini string + optional callback when a node is entered export function patternifyAST(ast, code, onEnter, offset = 0) { - onEnter?.(ast); - const enter = (node) => patternifyAST(node, code, onEnter, offset); + onEnter?.(ast) + const enter = (node) => patternifyAST(node, code, onEnter, offset) switch (ast.type_) { case 'pattern': { // resolveReplications(ast); - const children = ast.source_.map((child) => enter(child)).map(applyOptions(ast, enter)); - const alignment = ast.arguments_.alignment; - const with_steps = children.filter((child) => child.__steps_source); - let pat; + const children = ast.source_.map((child) => enter(child)).map(applyOptions(ast, enter)) + const alignment = ast.arguments_.alignment + const with_steps = children.filter((child) => child.__steps_source) + let pat switch (alignment) { case 'stack': { - pat = strudel.stack(...children); + pat = strudel.stack(...children) if (with_steps.length) { - pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps))); + pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps))) } - break; + break } case 'polymeter_slowcat': { - pat = strudel.stack(...children.map((child) => child._slow(child.__weight))); + pat = strudel.stack(...children.map((child) => child._slow(child.__weight))) if (with_steps.length) { - pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps))); + pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps))) } - break; + break } case 'polymeter': { // polymeter const stepsPerCycle = ast.arguments_.stepsPerCycle ? enter(ast.arguments_.stepsPerCycle).fmap((x) => strudel.Fraction(x)) - : strudel.pure(strudel.Fraction(children.length > 0 ? children[0].__weight : 1)); + : strudel.pure(strudel.Fraction(children.length > 0 ? children[0].__weight : 1)) - const aligned = children.map((child) => child.fast(stepsPerCycle.fmap((x) => x.div(child.__weight)))); - pat = strudel.stack(...aligned); - break; + const aligned = children.map((child) => + child.fast(stepsPerCycle.fmap((x) => x.div(child.__weight))) + ) + pat = strudel.stack(...aligned) + break } case 'rand': { - pat = strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children); + pat = strudel.chooseInWith( + strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), + children + ) if (with_steps.length) { - pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps))); + pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps))) } - break; + break } case 'feet': { - pat = strudel.fastcat(...children); - break; + pat = strudel.fastcat(...children) + break } default: { - const weightedChildren = ast.source_.some((child) => !!child.options_?.weight); + const weightedChildren = ast.source_.some((child) => !!child.options_?.weight) if (weightedChildren) { const weightSum = ast.source_.reduce( (sum, child) => sum.add(child.options_?.weight || strudel.Fraction(1)), - strudel.Fraction(0), - ); + strudel.Fraction(0) + ) pat = strudel.timeCat( - ...ast.source_.map((child, i) => [child.options_?.weight || strudel.Fraction(1), children[i]]), - ); - pat.__weight = weightSum; // for polymeter - pat._steps = weightSum; + ...ast.source_.map((child, i) => [ + child.options_?.weight || strudel.Fraction(1), + children[i], + ]) + ) + pat.__weight = weightSum // for polymeter + pat._steps = weightSum if (with_steps.length) { - pat._steps = pat._steps.mul(lcm(...with_steps.map((x) => Fraction(x._steps)))); + pat._steps = pat._steps.mul(lcm(...with_steps.map((x) => Fraction(x._steps)))) } } else { - pat = strudel.sequence(...children); - pat._steps = children.length; + pat = strudel.sequence(...children) + pat._steps = children.length } if (ast.arguments_._steps) { - pat.__steps_source = true; + pat.__steps_source = true } } } if (with_steps.length) { - pat.__steps_source = true; + pat.__steps_source = true } - return pat; + return pat } case 'element': { - 1; - return enter(ast.source_); + 1 + return enter(ast.source_) } case 'atom': { if (ast.source_ === '~' || ast.source_ === '-') { - return strudel.silence; + return strudel.silence } if (!ast.location_) { - console.warn('no location for', ast); - return ast.source_; + console.warn('no location for', ast) + return ast.source_ } - const value = !isNaN(Number(ast.source_)) ? Number(ast.source_) : ast.source_; + const value = !isNaN(Number(ast.source_)) ? Number(ast.source_) : ast.source_ if (offset === -1) { // skip location handling (used when getting leaves to avoid confusion) - return strudel.pure(value); + return strudel.pure(value) } - const [from, to] = getLeafLocation(code, ast, offset); - return strudel.pure(value).withLoc(from, to); + const [from, to] = getLeafLocation(code, ast, offset) + return strudel.pure(value).withLoc(from, to) } case 'stretch': - return enter(ast.source_).slow(enter(ast.arguments_.amount)); + return enter(ast.source_).slow(enter(ast.arguments_.amount)) default: - console.warn(`node type "${ast.type_}" not implemented -> returning silence`); - return strudel.silence; + console.warn(`node type "${ast.type_}" not implemented -> returning silence`) + return strudel.silence } } // takes quoted mini string + leaf node within, returns source location of node (whitespace corrected) export const getLeafLocation = (code, leaf, globalOffset = 0) => { // value is expected without quotes! - const { start, end } = leaf.location_; - const actual = code?.split('').slice(start.offset, end.offset).join(''); + const { start, end } = leaf.location_ + const actual = code?.split('').slice(start.offset, end.offset).join('') // make sure whitespaces are not part of the highlight const [offsetStart = 0, offsetEnd = 0] = actual ? actual.split(leaf.source_).map((p) => p.split('').filter((c) => c === ' ').length) - : []; - return [start.offset + offsetStart + globalOffset, end.offset - offsetEnd + globalOffset]; -}; + : [] + return [start.offset + offsetStart + globalOffset, end.offset - offsetEnd + globalOffset] +} // takes quoted mini string, returns ast export const mini2ast = (code, start = 0, userCode = code) => { try { - return krill.parse(code); + return krill.parse(code) } catch (error) { - const region = [error.location.start.offset + start, error.location.end.offset + start]; - const line = userCode.slice(0, region[0]).split('\n').length; - throw new Error(`[mini] parse error at line ${line}: ${error.message}`); + const region = [error.location.start.offset + start, error.location.end.offset + start] + const line = userCode.slice(0, region[0]).split('\n').length + throw new Error(`[mini] parse error at line ${line}: ${error.message}`) } -}; +} // takes quoted mini string, returns all nodes that are leaves export const getLeaves = (code, start, userCode) => { - const ast = mini2ast(code, start, userCode); - let leaves = []; + const ast = mini2ast(code, start, userCode) + let leaves = [] patternifyAST( ast, code, (node) => { if (node.type_ === 'atom') { - leaves.push(node); + leaves.push(node) } }, - -1, - ); - return leaves; -}; + -1 + ) + return leaves +} // takes quoted mini string, returns locations [fromCol,toCol] of all leaf nodes export const getLeafLocations = (code, start = 0, userCode) => { - return getLeaves(code, start, userCode).map((l) => getLeafLocation(code, l, start)); -}; + return getLeaves(code, start, userCode).map((l) => getLeafLocation(code, l, start)) +} // mini notation only (wraps in "") export const mini = (...strings) => { const pats = strings.map((str) => { - const code = `"${str}"`; - const ast = mini2ast(code); - return patternifyAST(ast, code); - }); - return strudel.sequence(...pats); -}; + const code = `"${str}"` + const ast = mini2ast(code) + return patternifyAST(ast, code) + }) + return strudel.sequence(...pats) +} // turns str mini string (without quotes) into pattern // offset is the position of the mini string in the JS code // each leaf node will get .withLoc added // this function is used by the transpiler for double quoted strings export const m = (str, offset) => { - const code = `"${str}"`; - const ast = mini2ast(code); - return patternifyAST(ast, code, null, offset); -}; + const code = `"${str}"` + const ast = mini2ast(code) + return patternifyAST(ast, code, null, offset) +} // includes haskell style (raw krill parsing) export const h = (string) => { - const ast = mini2ast(string); - return patternifyAST(ast, string); -}; + const ast = mini2ast(string) + return patternifyAST(ast, string) +} export function minify(thing) { if (typeof thing === 'string') { - return mini(thing); + return mini(thing) } - return strudel.reify(thing); + return strudel.reify(thing) } // calling this function will cause patterns to parse strings as mini notation by default export function miniAllStrings() { - strudel.setStringParser(mini); + strudel.setStringParser(mini) } diff --git a/src/strudel/soundfonts/convert.js b/src/strudel/soundfonts/convert.js deleted file mode 100644 index ffd80cc..0000000 --- a/src/strudel/soundfonts/convert.js +++ /dev/null @@ -1,13 +0,0 @@ -// this script converts a soundfont into a json file, it has not been not used yet -import fetch from 'node-fetch'; - -const name = '0000_JCLive'; - -const js = await fetch(`https://felixroos.github.io/webaudiofontdata/sound/${name}_sf2_file.js`).then((res) => - res.text(), -); -// console.log(js); - -let [_, data] = js.split('_sf2_file='); -data = eval(data); -console.log(JSON.stringify(data)); diff --git a/src/strudel/soundfonts/fontloader.mjs b/src/strudel/soundfonts/fontloader.mjs index 427f57e..34627d2 100644 --- a/src/strudel/soundfonts/fontloader.mjs +++ b/src/strudel/soundfonts/fontloader.mjs @@ -1,4 +1,4 @@ -import { noteToMidi, freqToMidi, getSoundIndex } from '@strudel/core'; +import { noteToMidi, freqToMidi, getSoundIndex } from '../core/index.mjs' import { getAudioContext, registerSound, @@ -6,53 +6,53 @@ import { getADSRValues, getPitchEnvelope, getVibratoOscillator, -} from '@strudel/webaudio'; -import gm from './gm.mjs'; +} from '../webaudio/index.mjs' +import gm from './gm.mjs' -let defaultSoundfontUrl = 'https://felixroos.github.io/webaudiofontdata/sound'; -let soundfontUrl = defaultSoundfontUrl; +let defaultSoundfontUrl = 'https://felixroos.github.io/webaudiofontdata/sound' +let soundfontUrl = defaultSoundfontUrl export function setSoundfontUrl(value) { - soundfontUrl = value; + soundfontUrl = value } -let loadCache = {}; +let loadCache = {} async function loadFont(name) { if (loadCache[name]) { - return loadCache[name]; + return loadCache[name] } const load = async () => { // TODO: make soundfont source configurable - const url = `${soundfontUrl}/${name}.js`; - const preset = await fetch(url).then((res) => res.text()); - let [_, data] = preset.split('={'); - return eval('{' + data); - }; - loadCache[name] = load(); - return loadCache[name]; + const url = `${soundfontUrl}/${name}.js` + const preset = await fetch(url).then((res) => res.text()) + let [_, data] = preset.split('={') + return eval('{' + data) + } + loadCache[name] = load() + return loadCache[name] } export async function getFontBufferSource(name, value, ac) { - let { note = 'c3', freq } = value; - let midi; + let { note = 'c3', freq } = value + let midi if (freq) { - midi = freqToMidi(freq); + midi = freqToMidi(freq) } else if (typeof note === 'string') { - midi = noteToMidi(note); + midi = noteToMidi(note) } else if (typeof note === 'number') { - midi = note; + midi = note } else { - throw new Error(`unexpected "note" type "${typeof note}"`); + throw new Error(`unexpected "note" type "${typeof note}"`) } - const { buffer, zone } = await getFontPitch(name, midi, ac); - const src = ac.createBufferSource(); - src.buffer = buffer; - const baseDetune = zone.originalPitch - 100.0 * zone.coarseTune - zone.fineTune; - const playbackRate = 1.0 * Math.pow(2, (100.0 * midi - baseDetune) / 1200.0); + const { buffer, zone } = await getFontPitch(name, midi, ac) + const src = ac.createBufferSource() + src.buffer = buffer + const baseDetune = zone.originalPitch - 100.0 * zone.coarseTune - zone.fineTune + const playbackRate = 1.0 * Math.pow(2, (100.0 * midi - baseDetune) / 1200.0) // src detune? - src.playbackRate.value = playbackRate; - const loop = zone.loopStart > 1 && zone.loopStart < zone.loopEnd; + src.playbackRate.value = playbackRate + const loop = zone.loopStart > 1 && zone.loopStart < zone.loopEnd if (!loop) { /* const waveDuration = duration + this.afterTime; if (waveDuration > zone.buffer.duration / playbackRate) { @@ -60,81 +60,81 @@ export async function getFontBufferSource(name, value, ac) { // TODO: do sth with waveduration } */ } else { - src.loop = true; - src.loopStart = zone.loopStart / zone.sampleRate; - src.loopEnd = zone.loopEnd / zone.sampleRate; + src.loop = true + src.loopStart = zone.loopStart / zone.sampleRate + src.loopEnd = zone.loopEnd / zone.sampleRate //+ (zone.delay ? zone.delay : 0); } - return src; + return src } -let bufferCache = {}; +let bufferCache = {} export async function getFontPitch(name, pitch, ac) { - const key = `${name}:::${pitch}`; + const key = `${name}:::${pitch}` if (bufferCache[key]) { - return bufferCache[key]; + return bufferCache[key] } // console.log('load buffer', key); const load = async () => { - const preset = await loadFont(name); + const preset = await loadFont(name) if (!preset) { - throw new Error(`Could not load soundfont ${name}`); + throw new Error(`Could not load soundfont ${name}`) } - const zone = findZone(preset, pitch); + const zone = findZone(preset, pitch) if (!zone) { - throw new Error('no soundfont zone found for preset ', name, 'pitch', pitch); + throw new Error('no soundfont zone found for preset ', name, 'pitch', pitch) } - const buffer = await getBuffer(zone, ac); + const buffer = await getBuffer(zone, ac) if (!buffer) { - throw new Error(`no soundfont buffer found for preset ${name}, pitch: ${pitch}`); + throw new Error(`no soundfont buffer found for preset ${name}, pitch: ${pitch}`) } - return { buffer, zone }; - }; - bufferCache[key] = load(); // dont await here to cache promise immediately! - return bufferCache[key]; + return { buffer, zone } + } + bufferCache[key] = load() // dont await here to cache promise immediately! + return bufferCache[key] } function findZone(preset, pitch) { return preset.find((zone) => { - return zone.keyRangeLow <= pitch && zone.keyRangeHigh + 1 >= pitch; - }); + return zone.keyRangeLow <= pitch && zone.keyRangeHigh + 1 >= pitch + }) } // promisified version of https://github.com/felixroos/webaudiofont/blob/c6f97249b60dcfafc20fca5bb381294a6b2f8f51/npm/dist/WebAudioFontPlayer.js#L740 async function getBuffer(zone, audioContext) { if (zone.sample) { - console.warn('zone.sample untested!'); - const decoded = atob(zone.sample); - zone.buffer = audioContext.createBuffer(1, decoded.length / 2, zone.sampleRate); - const float32Array = zone.buffer.getChannelData(0); - let b1, b2, n; + console.warn('zone.sample untested!') + const decoded = atob(zone.sample) + zone.buffer = audioContext.createBuffer(1, decoded.length / 2, zone.sampleRate) + const float32Array = zone.buffer.getChannelData(0) + let b1, b2, n for (var i = 0; i < decoded.length / 2; i++) { - b1 = decoded.charCodeAt(i * 2); - b2 = decoded.charCodeAt(i * 2 + 1); + b1 = decoded.charCodeAt(i * 2) + b2 = decoded.charCodeAt(i * 2 + 1) if (b1 < 0) { - b1 = 256 + b1; + b1 = 256 + b1 } if (b2 < 0) { - b2 = 256 + b2; + b2 = 256 + b2 } - n = b2 * 256 + b1; + n = b2 * 256 + b1 if (n >= 65536 / 2) { - n = n - 65536; + n = n - 65536 } - float32Array[i] = n / 65536.0; + float32Array[i] = n / 65536.0 } } else { if (zone.file) { - const datalen = zone.file.length; - const arraybuffer = new ArrayBuffer(datalen); - const view = new Uint8Array(arraybuffer); - const decoded = atob(zone.file); - let b; + const datalen = zone.file.length + const arraybuffer = new ArrayBuffer(datalen) + const view = new Uint8Array(arraybuffer) + const decoded = atob(zone.file) + let b for (let i = 0; i < decoded.length; i++) { - b = decoded.charCodeAt(i); - view[i] = b; + b = decoded.charCodeAt(i) + view[i] = b } - return new Promise((resolve) => audioContext.decodeAudioData(arraybuffer, resolve)); + return new Promise((resolve) => audioContext.decodeAudioData(arraybuffer, resolve)) } } } @@ -149,36 +149,38 @@ export function registerSoundfonts() { value.decay, value.sustain, value.release, - ]); + ]) - const { duration } = value; - const n = getSoundIndex(value.n, fonts.length); - const font = fonts[n]; - const ctx = getAudioContext(); - const bufferSource = await getFontBufferSource(font, value, ctx); - bufferSource.start(time); - const envGain = ctx.createGain(); - const node = bufferSource.connect(envGain); - const holdEnd = time + duration; - getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, time, holdEnd, 'linear'); - let envEnd = holdEnd + release + 0.01; + const { duration } = value + const n = getSoundIndex(value.n, fonts.length) + const font = fonts[n] + const ctx = getAudioContext() + const bufferSource = await getFontBufferSource(font, value, ctx) + bufferSource.start(time) + const envGain = ctx.createGain() + const node = bufferSource.connect(envGain) + const holdEnd = time + duration + getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, time, holdEnd, 'linear') + let envEnd = holdEnd + release + 0.01 // vibrato - let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, time); + let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, time) // pitch envelope - getPitchEnvelope(bufferSource.detune, value, time, holdEnd); + getPitchEnvelope(bufferSource.detune, value, time, holdEnd) - bufferSource.stop(envEnd); - const stop = (releaseTime) => {}; + bufferSource.stop(envEnd) + const stop = (releaseTime) => { + return + } bufferSource.onended = () => { - bufferSource.disconnect(); - vibratoOscillator?.stop(); - node.disconnect(); - onended(); - }; - return { node, stop }; + bufferSource.disconnect() + vibratoOscillator?.stop() + node.disconnect() + onended() + } + return { node, stop } }, - { type: 'soundfont', prebake: true, fonts }, - ); - }); + { type: 'soundfont', prebake: true, fonts } + ) + }) } diff --git a/src/strudel/soundfonts/index.mjs b/src/strudel/soundfonts/index.mjs index 3179260..1abb9da 100644 --- a/src/strudel/soundfonts/index.mjs +++ b/src/strudel/soundfonts/index.mjs @@ -1,6 +1,13 @@ -import { getFontBufferSource, registerSoundfonts, setSoundfontUrl } from './fontloader.mjs'; -import * as soundfontList from './list.mjs'; -import { startPresetNote } from 'sfumato'; -import { loadSoundfont } from './sfumato.mjs'; +import { getFontBufferSource, registerSoundfonts, setSoundfontUrl } from './fontloader.mjs' +import * as soundfontList from './list.mjs' +import { startPresetNote } from 'sfumato' +import { loadSoundfont } from './sfumato.mjs' -export { loadSoundfont, startPresetNote, getFontBufferSource, soundfontList, registerSoundfonts, setSoundfontUrl }; +export { + loadSoundfont, + startPresetNote, + getFontBufferSource, + soundfontList, + registerSoundfonts, + setSoundfontUrl, +} diff --git a/src/strudel/soundfonts/list.mjs b/src/strudel/soundfonts/list.mjs index 116eecf..7e8ffd9 100644 --- a/src/strudel/soundfonts/list.mjs +++ b/src/strudel/soundfonts/list.mjs @@ -1655,7 +1655,7 @@ export const instruments = [ '1272_GeneralUserGS_sf2_file', '1273_GeneralUserGS_sf2_file', '1274_GeneralUserGS_sf2_file', -]; +] export const drums = [ ////'35_0_SBLive_sf2' @@ -1894,135 +1894,135 @@ export const drums = [ '81_16_JCLive_sf2_file', '81_18_JCLive_sf2_file', '81_4_Chaos_sf2_file', -]; +] // see https://www.midi.org/specifications-old/item/gm-level-1-sound-set -export const instrumentNames = []; -instrumentNames[0] = 'Acoustic Grand Piano: Piano'; -instrumentNames[1] = 'Bright Acoustic Piano: Piano'; -instrumentNames[2] = 'Electric Grand Piano: Piano'; -instrumentNames[3] = 'Honky-tonk Piano: Piano'; -instrumentNames[4] = 'Electric Piano 1: Piano'; -instrumentNames[5] = 'Electric Piano 2: Piano'; -instrumentNames[6] = 'Harpsichord: Piano'; -instrumentNames[7] = 'Clavinet: Piano'; -instrumentNames[8] = 'Celesta: Chromatic Percussion'; -instrumentNames[9] = 'Glockenspiel: Chromatic Percussion'; -instrumentNames[10] = 'Music Box: Chromatic Percussion'; -instrumentNames[11] = 'Vibraphone: Chromatic Percussion'; -instrumentNames[12] = 'Marimba: Chromatic Percussion'; // out of tune -instrumentNames[13] = 'Xylophone: Chromatic Percussion'; -instrumentNames[14] = 'Tubular Bells: Chromatic Percussion'; -instrumentNames[15] = 'Dulcimer: Chromatic Percussion'; -instrumentNames[16] = 'Drawbar Organ: Organ'; -instrumentNames[17] = 'Percussive Organ: Organ'; -instrumentNames[18] = 'Rock Organ: Organ'; -instrumentNames[19] = 'Church Organ: Organ'; -instrumentNames[20] = 'Reed Organ: Organ'; -instrumentNames[21] = 'Accordion: Organ'; // some notes are one octave up -instrumentNames[22] = 'Harmonica: Organ'; -instrumentNames[23] = 'Tango Accordion: Organ'; -instrumentNames[24] = 'Acoustic Guitar (nylon): Guitar'; -instrumentNames[25] = 'Acoustic Guitar (steel): Guitar'; -instrumentNames[26] = 'Electric Guitar (jazz): Guitar'; -instrumentNames[27] = 'Electric Guitar (clean): Guitar'; -instrumentNames[28] = 'Electric Guitar (muted): Guitar'; -instrumentNames[29] = 'Overdriven Guitar: Guitar'; -instrumentNames[30] = 'Distortion Guitar: Guitar'; -instrumentNames[31] = 'Guitar Harmonics: Guitar'; -instrumentNames[32] = 'Acoustic Bass: Bass'; -instrumentNames[33] = 'Electric Bass (finger): Bass'; -instrumentNames[34] = 'Electric Bass (pick): Bass'; -instrumentNames[35] = 'Fretless Bass: Bass'; -instrumentNames[36] = 'Slap Bass 1: Bass'; -instrumentNames[37] = 'Slap Bass 2: Bass'; -instrumentNames[38] = 'Synth Bass 1: Bass'; // not found? -instrumentNames[39] = 'Synth Bass 2: Bass'; -instrumentNames[40] = 'Violin: Strings'; -instrumentNames[41] = 'Viola: Strings'; -instrumentNames[42] = 'Cello: Strings'; -instrumentNames[43] = 'Contrabass: Strings'; -instrumentNames[44] = 'Tremolo Strings: Strings'; -instrumentNames[45] = 'Pizzicato Strings: Strings'; -instrumentNames[46] = 'Orchestral Harp: Strings'; -instrumentNames[47] = 'Timpani: Strings'; -instrumentNames[48] = 'String Ensemble 1: Ensemble'; -instrumentNames[49] = 'String Ensemble 2: Ensemble'; -instrumentNames[50] = 'Synth Strings 1: Ensemble'; -instrumentNames[51] = 'Synth Strings 2: Ensemble'; -instrumentNames[52] = 'Choir Aahs: Ensemble'; -instrumentNames[53] = 'Voice Oohs: Ensemble'; -instrumentNames[54] = 'Synth Choir: Ensemble'; -instrumentNames[55] = 'Orchestra Hit: Ensemble'; -instrumentNames[56] = 'Trumpet: Brass'; -instrumentNames[57] = 'Trombone: Brass'; -instrumentNames[58] = 'Tuba: Brass'; -instrumentNames[59] = 'Muted Trumpet: Brass'; -instrumentNames[60] = 'French Horn: Brass'; -instrumentNames[61] = 'Brass Section: Brass'; -instrumentNames[62] = 'Synth Brass 1: Brass'; -instrumentNames[63] = 'Synth Brass 2: Brass'; -instrumentNames[64] = 'Soprano Sax: Reed'; // out of time? -instrumentNames[65] = 'Alto Sax: Reed'; -instrumentNames[66] = 'Tenor Sax: Reed'; -instrumentNames[67] = 'Baritone Sax: Reed'; -instrumentNames[68] = 'Oboe: Reed'; -instrumentNames[69] = 'English Horn: Reed'; -instrumentNames[70] = 'Bassoon: Reed'; -instrumentNames[71] = 'Clarinet: Reed'; -instrumentNames[72] = 'Piccolo: Pipe'; -instrumentNames[73] = 'Flute: Pipe'; -instrumentNames[74] = 'Recorder: Pipe'; // out of time? -instrumentNames[75] = 'Pan Flute: Pipe'; -instrumentNames[76] = 'Blown bottle: Pipe'; -instrumentNames[77] = 'Shakuhachi: Pipe'; -instrumentNames[78] = 'Whistle: Pipe'; -instrumentNames[79] = 'Ocarina: Pipe'; -instrumentNames[80] = 'Lead 1 (square): Synth Lead'; -instrumentNames[81] = 'Lead 2 (sawtooth): Synth Lead'; -instrumentNames[82] = 'Lead 3 (calliope): Synth Lead'; -instrumentNames[83] = 'Lead 4 (chiff): Synth Lead'; -instrumentNames[84] = 'Lead 5 (charang): Synth Lead'; -instrumentNames[85] = 'Lead 6 (voice): Synth Lead'; // terranigma! -instrumentNames[86] = 'Lead 7 (fifths): Synth Lead'; -instrumentNames[87] = 'Lead 8 (bass + lead): Synth Lead'; -instrumentNames[88] = 'Pad 1 (new age): Synth Pad'; -instrumentNames[89] = 'Pad 2 (warm): Synth Pad'; -instrumentNames[90] = 'Pad 3 (polysynth): Synth Pad'; -instrumentNames[91] = 'Pad 4 (choir): Synth Pad'; -instrumentNames[92] = 'Pad 5 (bowed): Synth Pad'; -instrumentNames[93] = 'Pad 6 (metallic): Synth Pad'; -instrumentNames[94] = 'Pad 7 (halo): Synth Pad'; -instrumentNames[95] = 'Pad 8 (sweep): Synth Pad'; -instrumentNames[96] = 'FX 1 (rain): Synth Effects'; -instrumentNames[97] = 'FX 2 (soundtrack): Synth Effects'; -instrumentNames[98] = 'FX 3 (crystal): Synth Effects'; -instrumentNames[99] = 'FX 4 (atmosphere): Synth Effects'; -instrumentNames[100] = 'FX 5 (brightness): Synth Effects'; -instrumentNames[101] = 'FX 6 (goblins): Synth Effects'; -instrumentNames[102] = 'FX 7 (echoes): Synth Effects'; -instrumentNames[103] = 'FX 8 (sci-fi): Synth Effects'; -instrumentNames[104] = 'Sitar: Ethnic'; -instrumentNames[105] = 'Banjo: Ethnic'; -instrumentNames[106] = 'Shamisen: Ethnic'; -instrumentNames[107] = 'Koto: Ethnic'; -instrumentNames[108] = 'Kalimba: Ethnic'; -instrumentNames[109] = 'Bagpipe: Ethnic'; -instrumentNames[110] = 'Fiddle: Ethnic'; -instrumentNames[111] = 'Shanai: Ethnic'; -instrumentNames[112] = 'Tinkle Bell: Percussive'; -instrumentNames[113] = 'Agogo: Percussive'; -instrumentNames[114] = 'Steel Drums: Percussive'; -instrumentNames[115] = 'Woodblock: Percussive'; -instrumentNames[116] = 'Taiko Drum: Percussive'; -instrumentNames[117] = 'Melodic Tom: Percussive'; -instrumentNames[118] = 'Synth Drum: Percussive'; -instrumentNames[119] = 'Reverse Cymbal: Percussive'; -instrumentNames[120] = 'Guitar Fret Noise: Sound effects'; -instrumentNames[121] = 'Breath Noise: Sound effects'; -instrumentNames[122] = 'Seashore: Sound effects'; -instrumentNames[123] = 'Bird Tweet: Sound effects'; -instrumentNames[124] = 'Telephone Ring: Sound effects'; -instrumentNames[125] = 'Helicopter: Sound effects'; -instrumentNames[126] = 'Applause: Sound effects'; -instrumentNames[127] = 'Gunshot: Sound effects'; +export const instrumentNames = [] +instrumentNames[0] = 'Acoustic Grand Piano: Piano' +instrumentNames[1] = 'Bright Acoustic Piano: Piano' +instrumentNames[2] = 'Electric Grand Piano: Piano' +instrumentNames[3] = 'Honky-tonk Piano: Piano' +instrumentNames[4] = 'Electric Piano 1: Piano' +instrumentNames[5] = 'Electric Piano 2: Piano' +instrumentNames[6] = 'Harpsichord: Piano' +instrumentNames[7] = 'Clavinet: Piano' +instrumentNames[8] = 'Celesta: Chromatic Percussion' +instrumentNames[9] = 'Glockenspiel: Chromatic Percussion' +instrumentNames[10] = 'Music Box: Chromatic Percussion' +instrumentNames[11] = 'Vibraphone: Chromatic Percussion' +instrumentNames[12] = 'Marimba: Chromatic Percussion' // out of tune +instrumentNames[13] = 'Xylophone: Chromatic Percussion' +instrumentNames[14] = 'Tubular Bells: Chromatic Percussion' +instrumentNames[15] = 'Dulcimer: Chromatic Percussion' +instrumentNames[16] = 'Drawbar Organ: Organ' +instrumentNames[17] = 'Percussive Organ: Organ' +instrumentNames[18] = 'Rock Organ: Organ' +instrumentNames[19] = 'Church Organ: Organ' +instrumentNames[20] = 'Reed Organ: Organ' +instrumentNames[21] = 'Accordion: Organ' // some notes are one octave up +instrumentNames[22] = 'Harmonica: Organ' +instrumentNames[23] = 'Tango Accordion: Organ' +instrumentNames[24] = 'Acoustic Guitar (nylon): Guitar' +instrumentNames[25] = 'Acoustic Guitar (steel): Guitar' +instrumentNames[26] = 'Electric Guitar (jazz): Guitar' +instrumentNames[27] = 'Electric Guitar (clean): Guitar' +instrumentNames[28] = 'Electric Guitar (muted): Guitar' +instrumentNames[29] = 'Overdriven Guitar: Guitar' +instrumentNames[30] = 'Distortion Guitar: Guitar' +instrumentNames[31] = 'Guitar Harmonics: Guitar' +instrumentNames[32] = 'Acoustic Bass: Bass' +instrumentNames[33] = 'Electric Bass (finger): Bass' +instrumentNames[34] = 'Electric Bass (pick): Bass' +instrumentNames[35] = 'Fretless Bass: Bass' +instrumentNames[36] = 'Slap Bass 1: Bass' +instrumentNames[37] = 'Slap Bass 2: Bass' +instrumentNames[38] = 'Synth Bass 1: Bass' // not found? +instrumentNames[39] = 'Synth Bass 2: Bass' +instrumentNames[40] = 'Violin: Strings' +instrumentNames[41] = 'Viola: Strings' +instrumentNames[42] = 'Cello: Strings' +instrumentNames[43] = 'Contrabass: Strings' +instrumentNames[44] = 'Tremolo Strings: Strings' +instrumentNames[45] = 'Pizzicato Strings: Strings' +instrumentNames[46] = 'Orchestral Harp: Strings' +instrumentNames[47] = 'Timpani: Strings' +instrumentNames[48] = 'String Ensemble 1: Ensemble' +instrumentNames[49] = 'String Ensemble 2: Ensemble' +instrumentNames[50] = 'Synth Strings 1: Ensemble' +instrumentNames[51] = 'Synth Strings 2: Ensemble' +instrumentNames[52] = 'Choir Aahs: Ensemble' +instrumentNames[53] = 'Voice Oohs: Ensemble' +instrumentNames[54] = 'Synth Choir: Ensemble' +instrumentNames[55] = 'Orchestra Hit: Ensemble' +instrumentNames[56] = 'Trumpet: Brass' +instrumentNames[57] = 'Trombone: Brass' +instrumentNames[58] = 'Tuba: Brass' +instrumentNames[59] = 'Muted Trumpet: Brass' +instrumentNames[60] = 'French Horn: Brass' +instrumentNames[61] = 'Brass Section: Brass' +instrumentNames[62] = 'Synth Brass 1: Brass' +instrumentNames[63] = 'Synth Brass 2: Brass' +instrumentNames[64] = 'Soprano Sax: Reed' // out of time? +instrumentNames[65] = 'Alto Sax: Reed' +instrumentNames[66] = 'Tenor Sax: Reed' +instrumentNames[67] = 'Baritone Sax: Reed' +instrumentNames[68] = 'Oboe: Reed' +instrumentNames[69] = 'English Horn: Reed' +instrumentNames[70] = 'Bassoon: Reed' +instrumentNames[71] = 'Clarinet: Reed' +instrumentNames[72] = 'Piccolo: Pipe' +instrumentNames[73] = 'Flute: Pipe' +instrumentNames[74] = 'Recorder: Pipe' // out of time? +instrumentNames[75] = 'Pan Flute: Pipe' +instrumentNames[76] = 'Blown bottle: Pipe' +instrumentNames[77] = 'Shakuhachi: Pipe' +instrumentNames[78] = 'Whistle: Pipe' +instrumentNames[79] = 'Ocarina: Pipe' +instrumentNames[80] = 'Lead 1 (square): Synth Lead' +instrumentNames[81] = 'Lead 2 (sawtooth): Synth Lead' +instrumentNames[82] = 'Lead 3 (calliope): Synth Lead' +instrumentNames[83] = 'Lead 4 (chiff): Synth Lead' +instrumentNames[84] = 'Lead 5 (charang): Synth Lead' +instrumentNames[85] = 'Lead 6 (voice): Synth Lead' // terranigma! +instrumentNames[86] = 'Lead 7 (fifths): Synth Lead' +instrumentNames[87] = 'Lead 8 (bass + lead): Synth Lead' +instrumentNames[88] = 'Pad 1 (new age): Synth Pad' +instrumentNames[89] = 'Pad 2 (warm): Synth Pad' +instrumentNames[90] = 'Pad 3 (polysynth): Synth Pad' +instrumentNames[91] = 'Pad 4 (choir): Synth Pad' +instrumentNames[92] = 'Pad 5 (bowed): Synth Pad' +instrumentNames[93] = 'Pad 6 (metallic): Synth Pad' +instrumentNames[94] = 'Pad 7 (halo): Synth Pad' +instrumentNames[95] = 'Pad 8 (sweep): Synth Pad' +instrumentNames[96] = 'FX 1 (rain): Synth Effects' +instrumentNames[97] = 'FX 2 (soundtrack): Synth Effects' +instrumentNames[98] = 'FX 3 (crystal): Synth Effects' +instrumentNames[99] = 'FX 4 (atmosphere): Synth Effects' +instrumentNames[100] = 'FX 5 (brightness): Synth Effects' +instrumentNames[101] = 'FX 6 (goblins): Synth Effects' +instrumentNames[102] = 'FX 7 (echoes): Synth Effects' +instrumentNames[103] = 'FX 8 (sci-fi): Synth Effects' +instrumentNames[104] = 'Sitar: Ethnic' +instrumentNames[105] = 'Banjo: Ethnic' +instrumentNames[106] = 'Shamisen: Ethnic' +instrumentNames[107] = 'Koto: Ethnic' +instrumentNames[108] = 'Kalimba: Ethnic' +instrumentNames[109] = 'Bagpipe: Ethnic' +instrumentNames[110] = 'Fiddle: Ethnic' +instrumentNames[111] = 'Shanai: Ethnic' +instrumentNames[112] = 'Tinkle Bell: Percussive' +instrumentNames[113] = 'Agogo: Percussive' +instrumentNames[114] = 'Steel Drums: Percussive' +instrumentNames[115] = 'Woodblock: Percussive' +instrumentNames[116] = 'Taiko Drum: Percussive' +instrumentNames[117] = 'Melodic Tom: Percussive' +instrumentNames[118] = 'Synth Drum: Percussive' +instrumentNames[119] = 'Reverse Cymbal: Percussive' +instrumentNames[120] = 'Guitar Fret Noise: Sound effects' +instrumentNames[121] = 'Breath Noise: Sound effects' +instrumentNames[122] = 'Seashore: Sound effects' +instrumentNames[123] = 'Bird Tweet: Sound effects' +instrumentNames[124] = 'Telephone Ring: Sound effects' +instrumentNames[125] = 'Helicopter: Sound effects' +instrumentNames[126] = 'Applause: Sound effects' +instrumentNames[127] = 'Gunshot: Sound effects' diff --git a/src/strudel/soundfonts/sfumato.mjs b/src/strudel/soundfonts/sfumato.mjs index 4d5e7ef..a9508ab 100644 --- a/src/strudel/soundfonts/sfumato.mjs +++ b/src/strudel/soundfonts/sfumato.mjs @@ -1,26 +1,26 @@ -import { Pattern, getPlayableNoteValue, noteToMidi } from '@strudel/core'; -import { getAudioContext, registerSound } from '@strudel/webaudio'; -import { loadSoundfont as _loadSoundfont, startPresetNote } from 'sfumato'; +import { Pattern, getPlayableNoteValue, noteToMidi } from '../core/index.mjs' +import { getAudioContext, registerSound } from '../webaudio/index.mjs' +import { loadSoundfont as _loadSoundfont, startPresetNote } from 'sfumato' Pattern.prototype.soundfont = function (sf, n = 0) { return this.onTrigger((h, ct, cps, targetTime) => { - const ctx = getAudioContext(); - const note = getPlayableNoteValue(h); - const preset = sf.presets[n % sf.presets.length]; - const deadline = targetTime; - const args = [ctx, preset, noteToMidi(note), deadline]; - const stop = startPresetNote(...args); - stop(deadline + h.duration); - }); -}; + const ctx = getAudioContext() + const note = getPlayableNoteValue(h) + const preset = sf.presets[n % sf.presets.length] + const deadline = targetTime + const args = [ctx, preset, noteToMidi(note), deadline] + const stop = startPresetNote(...args) + stop(deadline + h.duration) + }) +} -const soundfontCache = new Map(); +const soundfontCache = new Map() export function loadSoundfont(url) { if (soundfontCache.get(url)) { - return soundfontCache.get(url); + return soundfontCache.get(url) } - const sf = _loadSoundfont(url); - soundfontCache.set(url, sf); + const sf = _loadSoundfont(url) + soundfontCache.set(url, sf) /*sf.then((font) => { font.presets.forEach((preset) => { console.log('preset', preset.header.name); @@ -45,5 +45,5 @@ export function loadSoundfont(url) { }); //console.log('f', f); });*/ - return sf; + return sf } diff --git a/src/strudel/superdough/README.md b/src/strudel/superdough/README.md deleted file mode 100644 index f5947d8..0000000 --- a/src/strudel/superdough/README.md +++ /dev/null @@ -1,171 +0,0 @@ -# superdough - -superdough is a simple web audio sampler and synth, intended for live coding. -It is the default output of [strudel](https://strudel.cc/). -This package has no ties to strudel and can be used to quickly bake your own music system on the web. - -## Install - -via npm: - -```js -npm i superdough --save -``` - -## Use - -```js -import { superdough, samples, initAudioOnFirstClick, registerSynthSounds } from 'superdough'; - -const init = Promise.all([ - initAudioOnFirstClick(), - samples('github:tidalcycles/dirt-samples'), - registerSynthSounds(), -]); - -const loop = (t = 0) => { - // superdough(value, time, duration) - superdough({ s: 'bd', delay: 0.5 }, t); - superdough({ note: 'g1', s: 'sawtooth', cutoff: 600, resonance: 8 }, t, 0.125); - superdough({ note: 'g2', s: 'sawtooth', cutoff: 600, resonance: 8 }, t + 0.25, 0.125); - superdough({ s: 'hh' }, t + 0.25); - superdough({ s: 'sd', room: 0.5 }, t + 0.5); - superdough({ s: 'hh' }, t + 0.75); -}; - -document.getElementById('play').addEventListener('click', async () => { - await init; - let t = 0.1; - while (t < 16) { - loop(t++); - } -}); -``` - -[Open this in Codesandbox](https://codesandbox.io/s/superdough-demo-forked-sf8djh?file=/src/index.js) - -## API - -### superdough(value, deadline, duration) - -```js -superdough({ s: 'bd', delay: 0.5 }, 0, 1); -``` - -- `value`: the sound properties: - - `s`: the name of the sound as loaded via `samples` or `registerSound` - - `n`: selects sample with given index - - `bank`: prefix_ that is attached to the sound, e.g. `{ s: 'bd', bank: 'RolandTR909' }` = `{ s: 'RolandTR909_bd' }` - - `gain`: gain from 0 to 1 (higher values also work but might clip) - - `velocity`: additional gain multiplier - - `cutoff`: low pass filter cutoff - - `resonance`: low pass filter resonance - - `hcutoff`: high pass filter cutoff - - `hresonance`: high pass filter resonance - - `bandf`: band pass filter cutoff - - `bandq`: band pass filter resonance - - `crush`: amplitude bit crusher using given number of bits - - `distort`: distortion effect. might get loud! - - `pan`: stereo panning from 0 (left) to 1 (right) - - `phaser`: sets the speed of the modulation - - `phaserdepth`: the amount the signal is affected by the phaser effect. - - `phasersweep`: the frequency sweep range of the lfo for the phaser effect. - - `phasercenter`: the amount the signal is affected by the phaser effect. - - `vowel`: vowel filter. possible values: "a", "e", "i", "o", "u" - - `delay`: delay mix - - `delayfeedback`: delay feedback - - `delaytime`: delay time - - `room`: reverb mix - - `size`: reverb room size - - `orbit`: bus name for global effects `delay` and `room`. same orbits will get the same effects - - `freq`: repitches sound to given frequency in Hz - - `note`: repitches sound to given note or midi number - - `cut`: sets cut group. Sounds of same group will cut each other off - - `clip`: multiplies duration with given number - - `speed`: repitches sound by given factor - - `begin`: moves beginning of sample to given factor (between 0 and 1) - - `end`: moves end of sample to given factor (between 0 and 1) - - `attack`: seconds of attack phase - - `decay`: seconds of decay phase - - `sustain`: gain of sustain phase - - `release`: seconds of release phase -- `deadline`: seconds until the sound should play (0 = immediate) -- `duration`: seconds the sound should last. optional for one shot samples, required for synth sounds - -### registerSynthSounds() - -Loads the default waveforms `sawtooth`, `square`, `triangle` and `sine`. Use them like this: - -```js -superdough({ s:'sawtooth' }, 0, 1) -``` - -The duration needs to be set for these sounds! - -### samples(sampleMap) - -allows you to load samples from URLs. There are 3 ways to load samples - -1. sample map object -2. url of sample map json file -3. github repo - -#### sample map object - -You can pass a sample map like this: - -```js -samples({ - '_base': 'https://raw.githubusercontent.com/felixroos/samples/main/', - 'bd': 'president/president_bd.mp3', - 'sd': ['president/president_sd.mp3', 'president/president_sd2.mp3'], - 'hh': ['president/president_hh.mp3'], -}) -``` - -The `_base` property defines the root url while the others declare one or more sample paths for each sound. - -For example the full URL for `bd` would then be `https://raw.githubusercontent.com/felixroos/samples/main/president/president_bd.mp3` - -A loaded sound can then be played with `superdough({ s: 'bd' }, 0)`. - -If you declare multiple sounds, you can select them with `n`: `superdough({ s: 'sd', n: 1 }, 0)` - -The duration property is not needed for samples. - -#### loading samples from a json file - -Instead of passing an object as a sample map, you can also pass a URL to a json that contains a sample map: - -```js -samples('https://raw.githubusercontent.com/felixroos/samples/main/strudel.json') -``` - -The json file is expected to have the same format as described above. - -#### loading samples from a github repo - -Because it is common to use github for samples, there is a short way to load a sample map from github: - -```js -samples('github:tidalcycles/dirt-samples') -``` - -The format is `github://`. - -If `` and `` are not specified, they will default to `samples` and `main` respectively. -It expects a `strudel.json` file to be present at the root of the given repository, which declares the sample paths in the repo. - -The format is also expected to be the same as explained above. - -### initAudioOnFirstClick() - -Initializes audio and makes sure it is playable after the first click in the document. A click is needed because of the [Autoplay Policy](https://www.w3.org/TR/autoplay-detection/). -You can call this function when the document loads. -Then just make sure your first call of `superdough` happens after a click of something. - -## Credits - -- [ZZFX](https://github.com/KilledByAPixel/ZzFX) used for synths starting with z -- [SuperDirt](https://github.com/musikinformatik/SuperDirt) -- [WebDirt](https://github.com/dktr0/WebDirt) diff --git a/src/strudel/superdough/audioContext.mjs b/src/strudel/superdough/audioContext.mjs index 71e01d5..b4b928e 100644 --- a/src/strudel/superdough/audioContext.mjs +++ b/src/strudel/superdough/audioContext.mjs @@ -1,18 +1,18 @@ -let audioContext; +let audioContext export const setDefaultAudioContext = () => { - audioContext = new AudioContext(); - return audioContext; -}; + audioContext = new AudioContext() + return audioContext +} export const getAudioContext = () => { if (!audioContext) { - return setDefaultAudioContext(); + return setDefaultAudioContext() } - return audioContext; -}; + return audioContext +} export function getAudioContextCurrentTime() { - return getAudioContext().currentTime; + return getAudioContext().currentTime } diff --git a/src/strudel/superdough/dspworklet.mjs b/src/strudel/superdough/dspworklet.mjs index aac0806..916812e 100644 --- a/src/strudel/superdough/dspworklet.mjs +++ b/src/strudel/superdough/dspworklet.mjs @@ -1,8 +1,8 @@ -import { getAudioContext } from './audioContext.mjs'; +import { getAudioContext } from './audioContext.mjs' -let worklet; +let worklet export async function dspWorklet(ac, code) { - const name = `dsp-worklet-${Date.now()}`; + const name = `dsp-worklet-${Date.now()}` const workletCode = `${code} let __q = []; // trigger queue class MyProcessor extends AudioWorkletProcessor { @@ -42,38 +42,44 @@ class MyProcessor extends AudioWorkletProcessor { } } registerProcessor('${name}', MyProcessor); -`; - const base64String = btoa(workletCode); - const dataURL = `data:text/javascript;base64,${base64String}`; - await ac.audioWorklet.addModule(dataURL); - const node = new AudioWorkletNode(ac, name); - const stop = () => node.port.postMessage('stop'); - return { node, stop }; +` + const base64String = btoa(workletCode) + const dataURL = `data:text/javascript;base64,${base64String}` + await ac.audioWorklet.addModule(dataURL) + const node = new AudioWorkletNode(ac, name) + const stop = () => node.port.postMessage('stop') + return { node, stop } } const stop = () => { if (worklet) { - worklet?.stop(); - worklet?.node?.disconnect(); + worklet?.stop() + worklet?.node?.disconnect() } -}; +} if (typeof window !== 'undefined') { window.addEventListener('message', (e) => { if (e.data === 'strudel-stop') { - stop(); + stop() } else if (e.data?.dough) { - worklet?.node.port.postMessage(e.data); + worklet?.node.port.postMessage(e.data) } - }); + }) } export const dough = async (code) => { - const ac = getAudioContext(); - stop(); - worklet = await dspWorklet(ac, code); - worklet.node.connect(ac.destination); -}; + const ac = getAudioContext() + stop() + worklet = await dspWorklet(ac, code) + worklet.node.connect(ac.destination) +} export function doughTrigger(hap, currentTime, cps, targetTime) { - window.postMessage({ time: targetTime, dough: hap.value, currentTime, duration: hap.duration, cps }); + window.postMessage({ + time: targetTime, + dough: hap.value, + currentTime, + duration: hap.duration, + cps, + }) } diff --git a/src/strudel/superdough/feedbackdelay.mjs b/src/strudel/superdough/feedbackdelay.mjs index c182d65..03dac0f 100644 --- a/src/strudel/superdough/feedbackdelay.mjs +++ b/src/strudel/superdough/feedbackdelay.mjs @@ -1,31 +1,31 @@ if (typeof DelayNode !== 'undefined') { class FeedbackDelayNode extends DelayNode { constructor(ac, wet, time, feedback) { - super(ac); - wet = Math.abs(wet); - this.delayTime.value = time; + super(ac) + wet = Math.abs(wet) + this.delayTime.value = time - const feedbackGain = ac.createGain(); - feedbackGain.gain.value = Math.min(Math.abs(feedback), 0.995); - this.feedback = feedbackGain.gain; + const feedbackGain = ac.createGain() + feedbackGain.gain.value = Math.min(Math.abs(feedback), 0.995) + this.feedback = feedbackGain.gain - const delayGain = ac.createGain(); - delayGain.gain.value = wet; - this.delayGain = delayGain; + const delayGain = ac.createGain() + delayGain.gain.value = wet + this.delayGain = delayGain - this.connect(feedbackGain); - this.connect(delayGain); - feedbackGain.connect(this); + this.connect(feedbackGain) + this.connect(delayGain) + feedbackGain.connect(this) - this.connect = (target) => delayGain.connect(target); - return this; + this.connect = (target) => delayGain.connect(target) + return this } start(t) { - this.delayGain.gain.setValueAtTime(this.delayGain.gain.value, t + this.delayTime.value); + this.delayGain.gain.setValueAtTime(this.delayGain.gain.value, t + this.delayTime.value) } } AudioContext.prototype.createFeedbackDelay = function (wet, time, feedback) { - return new FeedbackDelayNode(this, wet, time, feedback); - }; + return new FeedbackDelayNode(this, wet, time, feedback) + } } diff --git a/src/strudel/superdough/fft.js b/src/strudel/superdough/fft.js index 3829495..7cc3fcc 100644 --- a/src/strudel/superdough/fft.js +++ b/src/strudel/superdough/fft.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' // sourced from https://github.com/indutny/fft.js/ // LICENSE // This software is licensed under the MIT License. @@ -9,207 +9,207 @@ export default class FFT { constructor(size) { - this.size = size | 0; + this.size = size | 0 if (this.size <= 1 || (this.size & (this.size - 1)) !== 0) - throw new Error('FFT size must be a power of two and bigger than 1'); + throw new Error('FFT size must be a power of two and bigger than 1') - this._csize = size << 1; + this._csize = size << 1 // NOTE: Use of `var` is intentional for old V8 versions - var table = new Array(this.size * 2); + var table = new Array(this.size * 2) for (var i = 0; i < table.length; i += 2) { - const angle = (Math.PI * i) / this.size; - table[i] = Math.cos(angle); - table[i + 1] = -Math.sin(angle); + const angle = (Math.PI * i) / this.size + table[i] = Math.cos(angle) + table[i + 1] = -Math.sin(angle) } - this.table = table; + this.table = table // Find size's power of two - var power = 0; - for (var t = 1; this.size > t; t <<= 1) power++; + var power = 0 + for (var t = 1; this.size > t; t <<= 1) power++ // Calculate initial step's width: // * If we are full radix-4 - it is 2x smaller to give inital len=8 // * Otherwise it is the same as `power` to give len=4 - this._width = power % 2 === 0 ? power - 1 : power; + this._width = power % 2 === 0 ? power - 1 : power // Pre-compute bit-reversal patterns - this._bitrev = new Array(1 << this._width); + this._bitrev = new Array(1 << this._width) for (var j = 0; j < this._bitrev.length; j++) { - this._bitrev[j] = 0; + this._bitrev[j] = 0 for (var shift = 0; shift < this._width; shift += 2) { - var revShift = this._width - shift - 2; - this._bitrev[j] |= ((j >>> shift) & 3) << revShift; + var revShift = this._width - shift - 2 + this._bitrev[j] |= ((j >>> shift) & 3) << revShift } } - this._out = null; - this._data = null; - this._inv = 0; + this._out = null + this._data = null + this._inv = 0 } fromComplexArray(complex, storage) { - var res = storage || new Array(complex.length >>> 1); - for (var i = 0; i < complex.length; i += 2) res[i >>> 1] = complex[i]; - return res; + var res = storage || new Array(complex.length >>> 1) + for (var i = 0; i < complex.length; i += 2) res[i >>> 1] = complex[i] + return res } createComplexArray() { - const res = new Array(this._csize); - for (var i = 0; i < res.length; i++) res[i] = 0; - return res; + const res = new Array(this._csize) + for (var i = 0; i < res.length; i++) res[i] = 0 + return res } toComplexArray(input, storage) { - var res = storage || this.createComplexArray(); + var res = storage || this.createComplexArray() for (var i = 0; i < res.length; i += 2) { - res[i] = input[i >>> 1]; - res[i + 1] = 0; + res[i] = input[i >>> 1] + res[i + 1] = 0 } - return res; + return res } completeSpectrum(spectrum) { - var size = this._csize; - var half = size >>> 1; + var size = this._csize + var half = size >>> 1 for (var i = 2; i < half; i += 2) { - spectrum[size - i] = spectrum[i]; - spectrum[size - i + 1] = -spectrum[i + 1]; + spectrum[size - i] = spectrum[i] + spectrum[size - i + 1] = -spectrum[i + 1] } } transform(out, data) { - if (out === data) throw new Error('Input and output buffers must be different'); + if (out === data) throw new Error('Input and output buffers must be different') - this._out = out; - this._data = data; - this._inv = 0; - this._transform4(); - this._out = null; - this._data = null; + this._out = out + this._data = data + this._inv = 0 + this._transform4() + this._out = null + this._data = null } realTransform(out, data) { - if (out === data) throw new Error('Input and output buffers must be different'); + if (out === data) throw new Error('Input and output buffers must be different') - this._out = out; - this._data = data; - this._inv = 0; - this._realTransform4(); - this._out = null; - this._data = null; + this._out = out + this._data = data + this._inv = 0 + this._realTransform4() + this._out = null + this._data = null } inverseTransform(out, data) { - if (out === data) throw new Error('Input and output buffers must be different'); + if (out === data) throw new Error('Input and output buffers must be different') - this._out = out; - this._data = data; - this._inv = 1; - this._transform4(); - for (var i = 0; i < out.length; i++) out[i] /= this.size; - this._out = null; - this._data = null; + this._out = out + this._data = data + this._inv = 1 + this._transform4() + for (var i = 0; i < out.length; i++) out[i] /= this.size + this._out = null + this._data = null } // radix-4 implementation // // NOTE: Uses of `var` are intentional for older V8 version that do not // support both `let compound assignments` and `const phi` _transform4() { - var out = this._out; - var size = this._csize; + var out = this._out + var size = this._csize // Initial step (permute and transform) - var width = this._width; - var step = 1 << width; - var len = (size / step) << 1; + var width = this._width + var step = 1 << width + var len = (size / step) << 1 - var outOff; - var t; - var bitrev = this._bitrev; + var outOff + var t + var bitrev = this._bitrev if (len === 4) { for (outOff = 0, t = 0; outOff < size; outOff += len, t++) { - const off = bitrev[t]; - this._singleTransform2(outOff, off, step); + const off = bitrev[t] + this._singleTransform2(outOff, off, step) } } else { // len === 8 for (outOff = 0, t = 0; outOff < size; outOff += len, t++) { - const off = bitrev[t]; - this._singleTransform4(outOff, off, step); + const off = bitrev[t] + this._singleTransform4(outOff, off, step) } } // Loop through steps in decreasing order - var inv = this._inv ? -1 : 1; - var table = this.table; + var inv = this._inv ? -1 : 1 + var table = this.table for (step >>= 2; step >= 2; step >>= 2) { - len = (size / step) << 1; - var quarterLen = len >>> 2; + len = (size / step) << 1 + var quarterLen = len >>> 2 // Loop through offsets in the data for (outOff = 0; outOff < size; outOff += len) { // Full case - var limit = outOff + quarterLen; + var limit = outOff + quarterLen for (var i = outOff, k = 0; i < limit; i += 2, k += step) { - const A = i; - const B = A + quarterLen; - const C = B + quarterLen; - const D = C + quarterLen; + const A = i + const B = A + quarterLen + const C = B + quarterLen + const D = C + quarterLen // Original values - const Ar = out[A]; - const Ai = out[A + 1]; - const Br = out[B]; - const Bi = out[B + 1]; - const Cr = out[C]; - const Ci = out[C + 1]; - const Dr = out[D]; - const Di = out[D + 1]; + const Ar = out[A] + const Ai = out[A + 1] + const Br = out[B] + const Bi = out[B + 1] + const Cr = out[C] + const Ci = out[C + 1] + const Dr = out[D] + const Di = out[D + 1] // Middle values - const MAr = Ar; - const MAi = Ai; + const MAr = Ar + const MAi = Ai - const tableBr = table[k]; - const tableBi = inv * table[k + 1]; - const MBr = Br * tableBr - Bi * tableBi; - const MBi = Br * tableBi + Bi * tableBr; + const tableBr = table[k] + const tableBi = inv * table[k + 1] + const MBr = Br * tableBr - Bi * tableBi + const MBi = Br * tableBi + Bi * tableBr - const tableCr = table[2 * k]; - const tableCi = inv * table[2 * k + 1]; - const MCr = Cr * tableCr - Ci * tableCi; - const MCi = Cr * tableCi + Ci * tableCr; + const tableCr = table[2 * k] + const tableCi = inv * table[2 * k + 1] + const MCr = Cr * tableCr - Ci * tableCi + const MCi = Cr * tableCi + Ci * tableCr - const tableDr = table[3 * k]; - const tableDi = inv * table[3 * k + 1]; - const MDr = Dr * tableDr - Di * tableDi; - const MDi = Dr * tableDi + Di * tableDr; + const tableDr = table[3 * k] + const tableDi = inv * table[3 * k + 1] + const MDr = Dr * tableDr - Di * tableDi + const MDi = Dr * tableDi + Di * tableDr // Pre-Final values - const T0r = MAr + MCr; - const T0i = MAi + MCi; - const T1r = MAr - MCr; - const T1i = MAi - MCi; - const T2r = MBr + MDr; - const T2i = MBi + MDi; - const T3r = inv * (MBr - MDr); - const T3i = inv * (MBi - MDi); + const T0r = MAr + MCr + const T0i = MAi + MCi + const T1r = MAr - MCr + const T1i = MAi - MCi + const T2r = MBr + MDr + const T2i = MBi + MDi + const T3r = inv * (MBr - MDr) + const T3i = inv * (MBi - MDi) // Final values - const FAr = T0r + T2r; - const FAi = T0i + T2i; + const FAr = T0r + T2r + const FAi = T0i + T2i - const FCr = T0r - T2r; - const FCi = T0i - T2i; + const FCr = T0r - T2r + const FCi = T0i - T2i - const FBr = T1r + T3i; - const FBi = T1i - T3r; + const FBr = T1r + T3i + const FBi = T1i - T3r - const FDr = T1r - T3i; - const FDi = T1i + T3r; + const FDr = T1r - T3i + const FDi = T1i + T3r - out[A] = FAr; - out[A + 1] = FAi; - out[B] = FBr; - out[B + 1] = FBi; - out[C] = FCr; - out[C + 1] = FCi; - out[D] = FDr; - out[D + 1] = FDi; + out[A] = FAr + out[A + 1] = FAi + out[B] = FBr + out[B + 1] = FBi + out[C] = FCr + out[C + 1] = FCi + out[D] = FDr + out[D + 1] = FDi } } } @@ -218,209 +218,209 @@ export default class FFT { // // NOTE: Only called for len=4 _singleTransform2(outOff, off, step) { - const out = this._out; - const data = this._data; + const out = this._out + const data = this._data - const evenR = data[off]; - const evenI = data[off + 1]; - const oddR = data[off + step]; - const oddI = data[off + step + 1]; + const evenR = data[off] + const evenI = data[off + 1] + const oddR = data[off + step] + const oddI = data[off + step + 1] - const leftR = evenR + oddR; - const leftI = evenI + oddI; - const rightR = evenR - oddR; - const rightI = evenI - oddI; + const leftR = evenR + oddR + const leftI = evenI + oddI + const rightR = evenR - oddR + const rightI = evenI - oddI - out[outOff] = leftR; - out[outOff + 1] = leftI; - out[outOff + 2] = rightR; - out[outOff + 3] = rightI; + out[outOff] = leftR + out[outOff + 1] = leftI + out[outOff + 2] = rightR + out[outOff + 3] = rightI } // radix-4 // // NOTE: Only called for len=8 _singleTransform4(outOff, off, step) { - const out = this._out; - const data = this._data; - const inv = this._inv ? -1 : 1; - const step2 = step * 2; - const step3 = step * 3; + const out = this._out + const data = this._data + const inv = this._inv ? -1 : 1 + const step2 = step * 2 + const step3 = step * 3 // Original values - const Ar = data[off]; - const Ai = data[off + 1]; - const Br = data[off + step]; - const Bi = data[off + step + 1]; - const Cr = data[off + step2]; - const Ci = data[off + step2 + 1]; - const Dr = data[off + step3]; - const Di = data[off + step3 + 1]; + const Ar = data[off] + const Ai = data[off + 1] + const Br = data[off + step] + const Bi = data[off + step + 1] + const Cr = data[off + step2] + const Ci = data[off + step2 + 1] + const Dr = data[off + step3] + const Di = data[off + step3 + 1] // Pre-Final values - const T0r = Ar + Cr; - const T0i = Ai + Ci; - const T1r = Ar - Cr; - const T1i = Ai - Ci; - const T2r = Br + Dr; - const T2i = Bi + Di; - const T3r = inv * (Br - Dr); - const T3i = inv * (Bi - Di); + const T0r = Ar + Cr + const T0i = Ai + Ci + const T1r = Ar - Cr + const T1i = Ai - Ci + const T2r = Br + Dr + const T2i = Bi + Di + const T3r = inv * (Br - Dr) + const T3i = inv * (Bi - Di) // Final values - const FAr = T0r + T2r; - const FAi = T0i + T2i; + const FAr = T0r + T2r + const FAi = T0i + T2i - const FBr = T1r + T3i; - const FBi = T1i - T3r; + const FBr = T1r + T3i + const FBi = T1i - T3r - const FCr = T0r - T2r; - const FCi = T0i - T2i; + const FCr = T0r - T2r + const FCi = T0i - T2i - const FDr = T1r - T3i; - const FDi = T1i + T3r; + const FDr = T1r - T3i + const FDi = T1i + T3r - out[outOff] = FAr; - out[outOff + 1] = FAi; - out[outOff + 2] = FBr; - out[outOff + 3] = FBi; - out[outOff + 4] = FCr; - out[outOff + 5] = FCi; - out[outOff + 6] = FDr; - out[outOff + 7] = FDi; + out[outOff] = FAr + out[outOff + 1] = FAi + out[outOff + 2] = FBr + out[outOff + 3] = FBi + out[outOff + 4] = FCr + out[outOff + 5] = FCi + out[outOff + 6] = FDr + out[outOff + 7] = FDi } // Real input radix-4 implementation _realTransform4() { - var out = this._out; - var size = this._csize; + var out = this._out + var size = this._csize // Initial step (permute and transform) - var width = this._width; - var step = 1 << width; - var len = (size / step) << 1; + var width = this._width + var step = 1 << width + var len = (size / step) << 1 - var outOff; - var t; - var bitrev = this._bitrev; + var outOff + var t + var bitrev = this._bitrev if (len === 4) { for (outOff = 0, t = 0; outOff < size; outOff += len, t++) { - const off = bitrev[t]; - this._singleRealTransform2(outOff, off >>> 1, step >>> 1); + const off = bitrev[t] + this._singleRealTransform2(outOff, off >>> 1, step >>> 1) } } else { // len === 8 for (outOff = 0, t = 0; outOff < size; outOff += len, t++) { - const off = bitrev[t]; - this._singleRealTransform4(outOff, off >>> 1, step >>> 1); + const off = bitrev[t] + this._singleRealTransform4(outOff, off >>> 1, step >>> 1) } } // Loop through steps in decreasing order - var inv = this._inv ? -1 : 1; - var table = this.table; + var inv = this._inv ? -1 : 1 + var table = this.table for (step >>= 2; step >= 2; step >>= 2) { - len = (size / step) << 1; - var halfLen = len >>> 1; - var quarterLen = halfLen >>> 1; - var hquarterLen = quarterLen >>> 1; + len = (size / step) << 1 + var halfLen = len >>> 1 + var quarterLen = halfLen >>> 1 + var hquarterLen = quarterLen >>> 1 // Loop through offsets in the data for (outOff = 0; outOff < size; outOff += len) { for (var i = 0, k = 0; i <= hquarterLen; i += 2, k += step) { - var A = outOff + i; - var B = A + quarterLen; - var C = B + quarterLen; - var D = C + quarterLen; + var A = outOff + i + var B = A + quarterLen + var C = B + quarterLen + var D = C + quarterLen // Original values - var Ar = out[A]; - var Ai = out[A + 1]; - var Br = out[B]; - var Bi = out[B + 1]; - var Cr = out[C]; - var Ci = out[C + 1]; - var Dr = out[D]; - var Di = out[D + 1]; + var Ar = out[A] + var Ai = out[A + 1] + var Br = out[B] + var Bi = out[B + 1] + var Cr = out[C] + var Ci = out[C + 1] + var Dr = out[D] + var Di = out[D + 1] // Middle values - var MAr = Ar; - var MAi = Ai; + var MAr = Ar + var MAi = Ai - var tableBr = table[k]; - var tableBi = inv * table[k + 1]; - var MBr = Br * tableBr - Bi * tableBi; - var MBi = Br * tableBi + Bi * tableBr; + var tableBr = table[k] + var tableBi = inv * table[k + 1] + var MBr = Br * tableBr - Bi * tableBi + var MBi = Br * tableBi + Bi * tableBr - var tableCr = table[2 * k]; - var tableCi = inv * table[2 * k + 1]; - var MCr = Cr * tableCr - Ci * tableCi; - var MCi = Cr * tableCi + Ci * tableCr; + var tableCr = table[2 * k] + var tableCi = inv * table[2 * k + 1] + var MCr = Cr * tableCr - Ci * tableCi + var MCi = Cr * tableCi + Ci * tableCr - var tableDr = table[3 * k]; - var tableDi = inv * table[3 * k + 1]; - var MDr = Dr * tableDr - Di * tableDi; - var MDi = Dr * tableDi + Di * tableDr; + var tableDr = table[3 * k] + var tableDi = inv * table[3 * k + 1] + var MDr = Dr * tableDr - Di * tableDi + var MDi = Dr * tableDi + Di * tableDr // Pre-Final values - var T0r = MAr + MCr; - var T0i = MAi + MCi; - var T1r = MAr - MCr; - var T1i = MAi - MCi; - var T2r = MBr + MDr; - var T2i = MBi + MDi; - var T3r = inv * (MBr - MDr); - var T3i = inv * (MBi - MDi); + var T0r = MAr + MCr + var T0i = MAi + MCi + var T1r = MAr - MCr + var T1i = MAi - MCi + var T2r = MBr + MDr + var T2i = MBi + MDi + var T3r = inv * (MBr - MDr) + var T3i = inv * (MBi - MDi) // Final values - var FAr = T0r + T2r; - var FAi = T0i + T2i; + var FAr = T0r + T2r + var FAi = T0i + T2i - var FBr = T1r + T3i; - var FBi = T1i - T3r; + var FBr = T1r + T3i + var FBi = T1i - T3r - out[A] = FAr; - out[A + 1] = FAi; - out[B] = FBr; - out[B + 1] = FBi; + out[A] = FAr + out[A + 1] = FAi + out[B] = FBr + out[B + 1] = FBi // Output final middle point if (i === 0) { - var FCr = T0r - T2r; - var FCi = T0i - T2i; - out[C] = FCr; - out[C + 1] = FCi; - continue; + var FCr = T0r - T2r + var FCi = T0i - T2i + out[C] = FCr + out[C + 1] = FCi + continue } // Do not overwrite ourselves - if (i === hquarterLen) continue; + if (i === hquarterLen) continue // In the flipped case: // MAi = -MAi // MBr=-MBi, MBi=-MBr // MCr=-MCr // MDr=MDi, MDi=MDr - var ST0r = T1r; - var ST0i = -T1i; - var ST1r = T0r; - var ST1i = -T0i; - var ST2r = -inv * T3i; - var ST2i = -inv * T3r; - var ST3r = -inv * T2i; - var ST3i = -inv * T2r; + var ST0r = T1r + var ST0i = -T1i + var ST1r = T0r + var ST1i = -T0i + var ST2r = -inv * T3i + var ST2i = -inv * T3r + var ST3r = -inv * T2i + var ST3i = -inv * T2r - var SFAr = ST0r + ST2r; - var SFAi = ST0i + ST2i; + var SFAr = ST0r + ST2r + var SFAi = ST0i + ST2i - var SFBr = ST1r + ST3i; - var SFBi = ST1i - ST3r; + var SFBr = ST1r + ST3i + var SFBi = ST1i - ST3r - var SA = outOff + quarterLen - i; - var SB = outOff + halfLen - i; + var SA = outOff + quarterLen - i + var SB = outOff + halfLen - i - out[SA] = SFAr; - out[SA + 1] = SFAi; - out[SB] = SFBr; - out[SB + 1] = SFBi; + out[SA] = SFAr + out[SA + 1] = SFAi + out[SB] = SFBr + out[SB + 1] = SFBi } } } @@ -429,60 +429,60 @@ export default class FFT { // // NOTE: Only called for len=4 _singleRealTransform2(outOff, off, step) { - const out = this._out; - const data = this._data; + const out = this._out + const data = this._data - const evenR = data[off]; - const oddR = data[off + step]; + const evenR = data[off] + const oddR = data[off + step] - const leftR = evenR + oddR; - const rightR = evenR - oddR; + const leftR = evenR + oddR + const rightR = evenR - oddR - out[outOff] = leftR; - out[outOff + 1] = 0; - out[outOff + 2] = rightR; - out[outOff + 3] = 0; + out[outOff] = leftR + out[outOff + 1] = 0 + out[outOff + 2] = rightR + out[outOff + 3] = 0 } // radix-4 // // NOTE: Only called for len=8 _singleRealTransform4(outOff, off, step) { - const out = this._out; - const data = this._data; - const inv = this._inv ? -1 : 1; - const step2 = step * 2; - const step3 = step * 3; + const out = this._out + const data = this._data + const inv = this._inv ? -1 : 1 + const step2 = step * 2 + const step3 = step * 3 // Original values - const Ar = data[off]; - const Br = data[off + step]; - const Cr = data[off + step2]; - const Dr = data[off + step3]; + const Ar = data[off] + const Br = data[off + step] + const Cr = data[off + step2] + const Dr = data[off + step3] // Pre-Final values - const T0r = Ar + Cr; - const T1r = Ar - Cr; - const T2r = Br + Dr; - const T3r = inv * (Br - Dr); + const T0r = Ar + Cr + const T1r = Ar - Cr + const T2r = Br + Dr + const T3r = inv * (Br - Dr) // Final values - const FAr = T0r + T2r; + const FAr = T0r + T2r - const FBr = T1r; - const FBi = -T3r; + const FBr = T1r + const FBi = -T3r - const FCr = T0r - T2r; + const FCr = T0r - T2r - const FDr = T1r; - const FDi = T3r; + const FDr = T1r + const FDi = T3r - out[outOff] = FAr; - out[outOff + 1] = 0; - out[outOff + 2] = FBr; - out[outOff + 3] = FBi; - out[outOff + 4] = FCr; - out[outOff + 5] = 0; - out[outOff + 6] = FDr; - out[outOff + 7] = FDi; + out[outOff] = FAr + out[outOff + 1] = 0 + out[outOff + 2] = FBr + out[outOff + 3] = FBi + out[outOff + 4] = FCr + out[outOff + 5] = 0 + out[outOff + 6] = FDr + out[outOff + 7] = FDi } } diff --git a/src/strudel/superdough/helpers.mjs b/src/strudel/superdough/helpers.mjs index a471da9..41440b0 100644 --- a/src/strudel/superdough/helpers.mjs +++ b/src/strudel/superdough/helpers.mjs @@ -1,39 +1,39 @@ -import { getAudioContext } from './audioContext.mjs'; -import { clamp, nanFallback, midiToFreq, noteToMidi } from './util.mjs'; -import { getNoiseBuffer } from './noise.mjs'; -import { logger } from './logger.mjs'; +import { getAudioContext } from './audioContext.mjs' +import { clamp, nanFallback, midiToFreq, noteToMidi } from './util.mjs' +import { getNoiseBuffer } from './noise.mjs' +import { logger } from './logger.mjs' -export const noises = ['pink', 'white', 'brown', 'crackle']; +export const noises = ['pink', 'white', 'brown', 'crackle'] export function gainNode(value) { - const node = getAudioContext().createGain(); - node.gain.value = value; - return node; + const node = getAudioContext().createGain() + node.gain.value = value + return node } export function effectSend(input, effect, wet) { - const send = gainNode(wet); - input.connect(send); - send.connect(effect); - return send; + const send = gainNode(wet) + input.connect(send) + send.connect(effect) + return send } const getSlope = (y1, y2, x1, x2) => { - const denom = x2 - x1; + const denom = x2 - x1 if (denom === 0) { - return 0; + return 0 } - return (y2 - y1) / (x2 - x1); -}; + return (y2 - y1) / (x2 - x1) +} export function getWorklet(ac, processor, params, config) { - const node = new AudioWorkletNode(ac, processor, config); + const node = new AudioWorkletNode(ac, processor, config) Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { - node.parameters.get(key).value = value; + node.parameters.get(key).value = value } - }); - return node; + }) + return node } export const getParamADSR = ( @@ -47,67 +47,67 @@ export const getParamADSR = ( begin, end, //exponential works better for frequency modulations (such as filter cutoff) due to human ear perception - curve = 'exponential', + curve = 'exponential' ) => { - attack = nanFallback(attack); - decay = nanFallback(decay); - sustain = nanFallback(sustain); - release = nanFallback(release); - const ramp = curve === 'exponential' ? 'exponentialRampToValueAtTime' : 'linearRampToValueAtTime'; + attack = nanFallback(attack) + decay = nanFallback(decay) + sustain = nanFallback(sustain) + release = nanFallback(release) + const ramp = curve === 'exponential' ? 'exponentialRampToValueAtTime' : 'linearRampToValueAtTime' if (curve === 'exponential') { - min = min === 0 ? 0.001 : min; - max = max === 0 ? 0.001 : max; + min = min === 0 ? 0.001 : min + max = max === 0 ? 0.001 : max } - const range = max - min; - const peak = max; - const sustainVal = min + sustain * range; - const duration = end - begin; + const range = max - min + const peak = max + const sustainVal = min + sustain * range + const duration = end - begin const envValAtTime = (time) => { - let val; + let val if (attack > time) { - let slope = getSlope(min, peak, 0, attack); - val = time * slope + (min > peak ? min : 0); + let slope = getSlope(min, peak, 0, attack) + val = time * slope + (min > peak ? min : 0) } else { - val = (time - attack) * getSlope(peak, sustainVal, 0, decay) + peak; + val = (time - attack) * getSlope(peak, sustainVal, 0, decay) + peak } if (curve === 'exponential') { - val = val || 0.001; + val = val || 0.001 } - return val; - }; + return val + } - param.setValueAtTime(min, begin); + param.setValueAtTime(min, begin) if (attack > duration) { //attack - param[ramp](envValAtTime(duration), end); + param[ramp](envValAtTime(duration), end) } else if (attack + decay > duration) { //attack - param[ramp](envValAtTime(attack), begin + attack); + param[ramp](envValAtTime(attack), begin + attack) //decay - param[ramp](envValAtTime(duration), end); + param[ramp](envValAtTime(duration), end) } else { //attack - param[ramp](envValAtTime(attack), begin + attack); + param[ramp](envValAtTime(attack), begin + attack) //decay - param[ramp](envValAtTime(attack + decay), begin + attack + decay); + param[ramp](envValAtTime(attack + decay), begin + attack + decay) //sustain - param.setValueAtTime(sustainVal, end); + param.setValueAtTime(sustainVal, end) } //release - param[ramp](min, end + release); -}; + param[ramp](min, end + release) +} function getModulationShapeInput(val) { if (typeof val === 'number') { - return val % 5; + return val % 5 } - return { tri: 0, triangle: 0, sine: 1, ramp: 2, saw: 3, square: 4 }[val] ?? 0; + return { tri: 0, triangle: 0, sine: 1, ramp: 2, saw: 3, square: 4 }[val] ?? 0 } export function getLfo(audioContext, begin, end, properties = {}) { - const { shape = 0, ...props } = properties; - const { dcoffset = -0.5, depth = 1 } = properties; + const { shape = 0, ...props } = properties + const { dcoffset = -0.5, depth = 1 } = properties const lfoprops = { frequency: 1, depth, @@ -122,9 +122,9 @@ export function getLfo(audioContext, begin, end, properties = {}) { max: dcoffset * depth + depth, curve: 1, ...props, - }; + } - return getWorklet(audioContext, 'lfo-processor', lfoprops); + return getWorklet(audioContext, 'lfo-processor', lfoprops) } export function getCompressor(ac, threshold, ratio, knee, attack, release) { @@ -134,8 +134,8 @@ export function getCompressor(ac, threshold, ratio, knee, attack, release) { knee: knee ?? 10, attack: attack ?? 0.005, release: release ?? 0.05, - }; - return new DynamicsCompressorNode(ac, options); + } + return new DynamicsCompressorNode(ac, options) } // changes the default values of the envelope based on what parameters the user has defined @@ -143,197 +143,238 @@ export function getCompressor(ac, threshold, ratio, knee, attack, release) { // ex: sound(val).decay(val) will behave as a decay only envelope. sound(val).attack(val).decay(val) will behave like an "ad" env, etc. export const getADSRValues = (params, curve = 'linear', defaultValues) => { - const envmin = curve === 'exponential' ? 0.001 : 0.001; - const releaseMin = 0.01; - const envmax = 1; - const [a, d, s, r] = params; + const envmin = curve === 'exponential' ? 0.001 : 0.001 + const releaseMin = 0.01 + const envmax = 1 + const [a, d, s, r] = params if (a == null && d == null && s == null && r == null) { - return defaultValues ?? [envmin, envmin, envmax, releaseMin]; + return defaultValues ?? [envmin, envmin, envmax, releaseMin] } - const sustain = s != null ? s : (a != null && d == null) || (a == null && d == null) ? envmax : envmin; - return [Math.max(a ?? 0, envmin), Math.max(d ?? 0, envmin), Math.min(sustain, envmax), Math.max(r ?? 0, releaseMin)]; -}; + const sustain = + s != null ? s : (a != null && d == null) || (a == null && d == null) ? envmax : envmin + return [ + Math.max(a ?? 0, envmin), + Math.max(d ?? 0, envmin), + Math.min(sustain, envmax), + Math.max(r ?? 0, releaseMin), + ] +} // helper utility for applying standard modulators to a parameter -export function applyParameterModulators(audioContext, param, start, end, envelopeValues, lfoValues) { - let { amount, offset, defaultAmount = 1, curve = 'linear', values, holdEnd, defaultValues } = envelopeValues; +export function applyParameterModulators( + audioContext, + param, + start, + end, + envelopeValues, + lfoValues +) { + let { + amount, + offset, + defaultAmount = 1, + curve = 'linear', + values, + holdEnd, + defaultValues, + } = envelopeValues if (amount == null) { - const hasADSRParams = values.some((p) => p != null); - amount = hasADSRParams ? defaultAmount : 0; + const hasADSRParams = values.some((p) => p != null) + amount = hasADSRParams ? defaultAmount : 0 } - const min = offset ?? 0; - const max = amount + min; - const diff = Math.abs(max - min); + const min = offset ?? 0 + const max = amount + min + const diff = Math.abs(max - min) if (diff) { - const [attack, decay, sustain, release] = getADSRValues(values, curve, defaultValues); - getParamADSR(param, attack, decay, sustain, release, min, max, start, holdEnd, curve); + const [attack, decay, sustain, release] = getADSRValues(values, curve, defaultValues) + getParamADSR(param, attack, decay, sustain, release, min, max, start, holdEnd, curve) } - let lfo; - let { defaultDepth = 1, depth, dcoffset, ...getLfoInputs } = lfoValues; + let lfo + let { defaultDepth = 1, depth, dcoffset, ...getLfoInputs } = lfoValues if (depth == null) { - const hasLFOParams = Object.values(getLfoInputs).some((v) => v != null); - depth = hasLFOParams ? defaultDepth : 0; + const hasLFOParams = Object.values(getLfoInputs).some((v) => v != null) + depth = hasLFOParams ? defaultDepth : 0 } if (depth) { lfo = getLfo(audioContext, start, end, { depth, dcoffset, ...getLfoInputs, - }); - lfo.connect(param); + }) + lfo.connect(param) } - return { lfo, disconnect: () => lfo?.disconnect() }; + return { lfo, disconnect: () => lfo?.disconnect() } } -export function createFilter(context, type, frequency, Q, att, dec, sus, rel, fenv, start, end, fanchor, model, drive) { - const curve = 'exponential'; - const [attack, decay, sustain, release] = getADSRValues([att, dec, sus, rel], curve, [0.005, 0.14, 0, 0.1]); - let filter; - let frequencyParam; +export function createFilter( + context, + type, + frequency, + Q, + att, + dec, + sus, + rel, + fenv, + start, + end, + fanchor, + model, + drive +) { + const curve = 'exponential' + const [attack, decay, sustain, release] = getADSRValues( + [att, dec, sus, rel], + curve, + [0.005, 0.14, 0, 0.1] + ) + let filter + let frequencyParam if (model === 'ladder') { - filter = getWorklet(context, 'ladder-processor', { frequency, q: Q, drive }); - frequencyParam = filter.parameters.get('frequency'); + filter = getWorklet(context, 'ladder-processor', { frequency, q: Q, drive }) + frequencyParam = filter.parameters.get('frequency') } else { - filter = context.createBiquadFilter(); - filter.type = type; - filter.Q.value = Q; - filter.frequency.value = frequency; - frequencyParam = filter.frequency; + filter = context.createBiquadFilter() + filter.type = type + filter.Q.value = Q + filter.frequency.value = frequency + frequencyParam = filter.frequency } // envelope is active when any of these values is set - const hasEnvelope = att ?? dec ?? sus ?? rel ?? fenv; + const hasEnvelope = att ?? dec ?? sus ?? rel ?? fenv // Apply ADSR to filter frequency if (hasEnvelope !== undefined) { - fenv = nanFallback(fenv, 1, true); - fanchor = nanFallback(fanchor, 0, true); - const fenvAbs = Math.abs(fenv); - const offset = fenvAbs * fanchor; - let min = clamp(2 ** -offset * frequency, 0, 20000); - let max = clamp(2 ** (fenvAbs - offset) * frequency, 0, 20000); - if (fenv < 0) [min, max] = [max, min]; - getParamADSR(frequencyParam, attack, decay, sustain, release, min, max, start, end, curve); - return filter; + fenv = nanFallback(fenv, 1, true) + fanchor = nanFallback(fanchor, 0, true) + const fenvAbs = Math.abs(fenv) + const offset = fenvAbs * fanchor + let min = clamp(2 ** -offset * frequency, 0, 20000) + let max = clamp(2 ** (fenvAbs - offset) * frequency, 0, 20000) + if (fenv < 0) [min, max] = [max, min] + getParamADSR(frequencyParam, attack, decay, sustain, release, min, max, start, end, curve) + return filter } - return filter; + return filter } // stays 1 until .5, then fades out -let wetfade = (d) => (d < 0.5 ? 1 : 1 - (d - 0.5) / 0.5); +let wetfade = (d) => (d < 0.5 ? 1 : 1 - (d - 0.5) / 0.5) // mix together dry and wet nodes. 0 = only dry 1 = only wet // still not too sure about how this could be used more generally... export function drywet(dry, wet, wetAmount = 0) { - const ac = getAudioContext(); + const ac = getAudioContext() if (!wetAmount) { - return dry; + return dry } - let dry_gain = ac.createGain(); - let wet_gain = ac.createGain(); - dry.connect(dry_gain); - wet.connect(wet_gain); - dry_gain.gain.value = wetfade(wetAmount); - wet_gain.gain.value = wetfade(1 - wetAmount); - let mix = ac.createGain(); - dry_gain.connect(mix); - wet_gain.connect(mix); - return mix; + let dry_gain = ac.createGain() + let wet_gain = ac.createGain() + dry.connect(dry_gain) + wet.connect(wet_gain) + dry_gain.gain.value = wetfade(wetAmount) + wet_gain.gain.value = wetfade(1 - wetAmount) + let mix = ac.createGain() + dry_gain.connect(mix) + wet_gain.connect(mix) + return mix } -let curves = ['linear', 'exponential']; +let curves = ['linear', 'exponential'] export function getPitchEnvelope(param, value, t, holdEnd) { // envelope is active when any of these values is set - const hasEnvelope = value.pattack ?? value.pdecay ?? value.psustain ?? value.prelease ?? value.penv; + const hasEnvelope = + value.pattack ?? value.pdecay ?? value.psustain ?? value.prelease ?? value.penv if (hasEnvelope === undefined) { - return; + return } - const penv = nanFallback(value.penv, 1, true); - const curve = curves[value.pcurve ?? 0]; + const penv = nanFallback(value.penv, 1, true) + const curve = curves[value.pcurve ?? 0] let [pattack, pdecay, psustain, prelease] = getADSRValues( [value.pattack, value.pdecay, value.psustain, value.prelease], curve, - [0.2, 0.001, 1, 0.001], - ); - let panchor = value.panchor ?? psustain; - const cents = penv * 100; // penv is in semitones - const min = 0 - cents * panchor; - const max = cents - cents * panchor; - getParamADSR(param, pattack, pdecay, psustain, prelease, min, max, t, holdEnd, curve); + [0.2, 0.001, 1, 0.001] + ) + let panchor = value.panchor ?? psustain + const cents = penv * 100 // penv is in semitones + const min = 0 - cents * panchor + const max = cents - cents * panchor + getParamADSR(param, pattack, pdecay, psustain, prelease, min, max, t, holdEnd, curve) } export function getVibratoOscillator(param, value, t) { - const { vibmod = 0.5, vib } = value; - let vibratoOscillator; + const { vibmod = 0.5, vib } = value + let vibratoOscillator if (vib > 0) { - vibratoOscillator = getAudioContext().createOscillator(); - vibratoOscillator.frequency.value = vib; - const gain = getAudioContext().createGain(); + vibratoOscillator = getAudioContext().createOscillator() + vibratoOscillator.frequency.value = vib + const gain = getAudioContext().createGain() // Vibmod is the amount of vibrato, in semitones - gain.gain.value = vibmod * 100; - vibratoOscillator.connect(gain); - gain.connect(param); - vibratoOscillator.start(t); - return vibratoOscillator; + gain.gain.value = vibmod * 100 + vibratoOscillator.connect(gain) + gain.connect(param) + vibratoOscillator.start(t) + return vibratoOscillator } } // ConstantSource inherits AudioScheduledSourceNode, which has scheduling abilities // a bit of a hack, but it works very well :) export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) { - const constantNode = new ConstantSourceNode(audioContext); + const constantNode = new ConstantSourceNode(audioContext) // Certain browsers requires audio nodes to be connected in order for their onended events // to fire, so we _mute it_ and then connect it to the destination - const zeroGain = gainNode(0); - zeroGain.connect(audioContext.destination); - constantNode.connect(zeroGain); + const zeroGain = gainNode(0) + zeroGain.connect(audioContext.destination) + constantNode.connect(zeroGain) // Schedule the `onComplete` callback to occur at `stopTime` constantNode.onended = () => { // Ensure garbage collection try { - zeroGain.disconnect(); + zeroGain.disconnect() } catch { // pass } try { - constantNode.disconnect(); + constantNode.disconnect() } catch { // pass } - onComplete(); - }; - constantNode.start(startTime); - constantNode.stop(stopTime); - return constantNode; + onComplete() + } + constantNode.start(startTime) + constantNode.stop(stopTime) + return constantNode } const mod = (freq, range = 1, type = 'sine') => { - const ctx = getAudioContext(); - let osc; + const ctx = getAudioContext() + let osc if (noises.includes(type)) { - osc = ctx.createBufferSource(); - osc.buffer = getNoiseBuffer(type, 2); - osc.loop = true; + osc = ctx.createBufferSource() + osc.buffer = getNoiseBuffer(type, 2) + osc.loop = true } else { - osc = ctx.createOscillator(); - osc.type = type; - osc.frequency.value = freq; + osc = ctx.createOscillator() + osc.type = type + osc.frequency.value = freq } - osc.start(); - const g = new GainNode(ctx, { gain: range }); - osc.connect(g); // -range, range - return { node: g, stop: (t) => osc.stop(t) }; -}; + osc.start() + const g = new GainNode(ctx, { gain: range }) + osc.connect(g) // -range, range + return { node: g, stop: (t) => osc.stop(t) } +} const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => { - const carrfreq = frequencyparam.value; - const modfreq = carrfreq * harmonicityRatio; - const modgain = modfreq * modulationIndex; - return mod(modfreq, modgain, wave); -}; + const carrfreq = frequencyparam.value + const modfreq = carrfreq * harmonicityRatio + const modgain = modfreq * modulationIndex + return mod(modfreq, modgain, wave) +} export function applyFM(param, value, begin) { const { fmh: fmHarmonicity = 1, @@ -346,23 +387,30 @@ export function applyFM(param, value, begin) { fmvelocity: fmVelocity, fmwave: fmWaveform = 'sine', duration, - } = value; - let modulator; - let stop = () => {}; + } = value + let modulator + let stop = () => { + return + } if (fmModulationIndex) { - const ac = getAudioContext(); - const envGain = ac.createGain(); - const fmmod = fm(param, fmHarmonicity, fmModulationIndex, fmWaveform); + const ac = getAudioContext() + const envGain = ac.createGain() + const fmmod = fm(param, fmHarmonicity, fmModulationIndex, fmWaveform) - modulator = fmmod.node; - stop = fmmod.stop; + modulator = fmmod.node + stop = fmmod.stop if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].some((v) => v !== undefined)) { // no envelope by default - modulator.connect(param); + modulator.connect(param) } else { - const [attack, decay, sustain, release] = getADSRValues([fmAttack, fmDecay, fmSustain, fmRelease]); - const holdEnd = begin + duration; + const [attack, decay, sustain, release] = getADSRValues([ + fmAttack, + fmDecay, + fmSustain, + fmRelease, + ]) + const holdEnd = begin + duration getParamADSR( envGain.gain, attack, @@ -373,78 +421,78 @@ export function applyFM(param, value, begin) { 1, begin, holdEnd, - fmEnvelopeType === 'exp' ? 'exponential' : 'linear', - ); - modulator.connect(envGain); - envGain.connect(param); + fmEnvelopeType === 'exp' ? 'exponential' : 'linear' + ) + modulator.connect(envGain) + envGain.connect(param) } } - return { stop }; + return { stop } } // Saturation curves -const __squash = (x) => x / (1 + x); // [0, inf) to [0, 1) -const _mod = (n, m) => ((n % m) + m) % m; +const __squash = (x) => x / (1 + x) // [0, inf) to [0, 1) +const _mod = (n, m) => ((n % m) + m) % m -const _scurve = (x, k) => ((1 + k) * x) / (1 + k * Math.abs(x)); -const _soft = (x, k) => Math.tanh(x * (1 + k)); -const _hard = (x, k) => clamp((1 + k) * x, -1, 1); +const _scurve = (x, k) => ((1 + k) * x) / (1 + k * Math.abs(x)) +const _soft = (x, k) => Math.tanh(x * (1 + k)) +const _hard = (x, k) => clamp((1 + k) * x, -1, 1) const _fold = (x, k) => { // Closed form folding for audio rate - let y = (1 + 0.5 * k) * x; - const window = _mod(y + 1, 4); - return 1 - Math.abs(window - 2); -}; + let y = (1 + 0.5 * k) * x + const window = _mod(y + 1, 4) + return 1 - Math.abs(window - 2) +} -const _sineFold = (x, k) => Math.sin((Math.PI / 2) * _fold(x, k)); +const _sineFold = (x, k) => Math.sin((Math.PI / 2) * _fold(x, k)) const _cubic = (x, k) => { - const t = __squash(Math.log1p(k)); - const cubic = (x - (t / 3) * x * x * x) / (1 - t / 3); // normalized to go from (-1, 1) - return _soft(cubic, k); -}; + const t = __squash(Math.log1p(k)) + const cubic = (x - (t / 3) * x * x * x) / (1 - t / 3) // normalized to go from (-1, 1) + return _soft(cubic, k) +} const _diode = (x, k, asym = false) => { - const g = 1 + 2 * k; // gain - const t = __squash(Math.log1p(k)); - const bias = 0.07 * t; - const pos = _soft(x + bias, 2 * k); - const neg = _soft(asym ? bias : -x + bias, 2 * k); - const y = pos - neg; + const g = 1 + 2 * k // gain + const t = __squash(Math.log1p(k)) + const bias = 0.07 * t + const pos = _soft(x + bias, 2 * k) + const neg = _soft(asym ? bias : -x + bias, 2 * k) + const y = pos - neg // We divide by the derivative at 0 so that the distortion is roughly // the identity map near 0 => small values are preserved and undistorted - const sech = 1 / Math.cosh(g * bias); - const sech2 = sech * sech; // derivative of soft (i.e. tanh) is sech^2 - const denom = Math.max(1e-8, (asym ? 1 : 2) * g * sech2); // g from chain rule; 2 if both pos/neg have x - return _soft(y / denom, k); -}; + const sech = 1 / Math.cosh(g * bias) + const sech2 = sech * sech // derivative of soft (i.e. tanh) is sech^2 + const denom = Math.max(1e-8, (asym ? 1 : 2) * g * sech2) // g from chain rule; 2 if both pos/neg have x + return _soft(y / denom, k) +} -const _asym = (x, k) => _diode(x, k, true); +const _asym = (x, k) => _diode(x, k, true) const _chebyshev = (x, k) => { - const kl = 10 * Math.log1p(k); - let tnm1 = 1; - let tnm2 = x; - let tn; - let y = 0; + const kl = 10 * Math.log1p(k) + let tnm1 = 1 + let tnm2 = x + let tn + let y = 0 for (let i = 1; i < 64; i++) { if (i < 2) { // Already set inital conditions - y += i == 0 ? tnm1 : tnm2; - continue; + y += i == 0 ? tnm1 : tnm2 + continue } - tn = 2 * x * tnm1 - tnm2; // https://en.wikipedia.org/wiki/Chebyshev_polynomials#Recurrence_definition - tnm2 = tnm1; - tnm1 = tn; + tn = 2 * x * tnm1 - tnm2 // https://en.wikipedia.org/wiki/Chebyshev_polynomials#Recurrence_definition + tnm2 = tnm1 + tnm1 = tn if (i % 2 === 0) { - y += Math.min((1.3 * kl) / i, 2) * tn; + y += Math.min((1.3 * kl) / i, 2) * tn } } // Soft clip - return _soft(y, kl / 20); -}; + return _soft(y, kl / 20) +} export const distortionAlgorithms = { scurve: _scurve, @@ -456,46 +504,51 @@ export const distortionAlgorithms = { fold: _fold, sinefold: _sineFold, chebyshev: _chebyshev, -}; -const _algoNames = Object.freeze(Object.keys(distortionAlgorithms)); +} +const _algoNames = Object.freeze(Object.keys(distortionAlgorithms)) export const getDistortionAlgorithm = (algo) => { - let index = algo; + let index = algo if (typeof algo === 'string') { - index = _algoNames.indexOf(algo); + index = _algoNames.indexOf(algo) if (index === -1) { logger(`[superdough] Could not find waveshaping algorithm ${algo}. Available options are ${_algoNames.join(', ')}. - Defaulting to ${_algoNames[0]}.`); - index = 0; + Defaulting to ${_algoNames[0]}.`) + index = 0 } } - const name = _algoNames[index % _algoNames.length]; // allow for wrapping if algo was a number - return distortionAlgorithms[name]; -}; + const name = _algoNames[index % _algoNames.length] // allow for wrapping if algo was a number + return distortionAlgorithms[name] +} export const getDistortion = (distort, postgain, algorithm) => { - return getWorklet(getAudioContext(), 'distort-processor', { distort, postgain }, { processorOptions: { algorithm } }); -}; + return getWorklet( + getAudioContext(), + 'distort-processor', + { distort, postgain }, + { processorOptions: { algorithm } } + ) +} export const getFrequencyFromValue = (value, defaultNote = 36) => { - let { note, freq } = value; - note = note || defaultNote; + let { note, freq } = value + note = note || defaultNote if (typeof note === 'string') { - note = noteToMidi(note); // e.g. c3 => 48 + note = noteToMidi(note) // e.g. c3 => 48 } // get frequency if (!freq && typeof note === 'number') { - freq = midiToFreq(note); // + 48); + freq = midiToFreq(note) // + 48); } - return Number(freq); -}; + return Number(freq) +} export const destroyAudioWorkletNode = (node) => { if (node == null) { - return; + return } - node.disconnect(); - node.parameters.get('end')?.setValueAtTime(0, 0); -}; + node.disconnect() + node.parameters.get('end')?.setValueAtTime(0, 0) +} diff --git a/src/strudel/superdough/index.mjs b/src/strudel/superdough/index.mjs index a382bd1..b2624a1 100644 --- a/src/strudel/superdough/index.mjs +++ b/src/strudel/superdough/index.mjs @@ -4,12 +4,12 @@ Copyright (C) 2022 Strudel contributors - see . */ -export * from './superdough.mjs'; -export * from './sampler.mjs'; -export * from './helpers.mjs'; -export * from './synth.mjs'; -export * from './zzfx.mjs'; -export * from './logger.mjs'; -export * from './dspworklet.mjs'; -export * from './audioContext.mjs'; -export * from './wavetable.mjs'; +export * from './superdough.mjs' +export * from './sampler.mjs' +export * from './helpers.mjs' +export * from './synth.mjs' +export * from './zzfx.mjs' +export * from './logger.mjs' +export * from './dspworklet.mjs' +export * from './audioContext.mjs' +export * from './wavetable.mjs' diff --git a/src/strudel/superdough/logger.mjs b/src/strudel/superdough/logger.mjs index 99fd0cf..405656e 100644 --- a/src/strudel/superdough/logger.mjs +++ b/src/strudel/superdough/logger.mjs @@ -1,14 +1,14 @@ -let log = (msg) => console.log(msg); +let log = (msg) => console.log(msg) export function errorLogger(e, origin = 'superdough') { if (process.env.NODE_ENV === 'development') { - console.error(e); + console.error(e) } - logger(`[${origin}] error: ${e.message}`); + logger(`[${origin}] error: ${e.message}`) } -export const logger = (...args) => log(...args); +export const logger = (...args) => log(...args) export const setLogger = (fn) => { - log = fn; -}; + log = fn +} diff --git a/src/strudel/superdough/noise.mjs b/src/strudel/superdough/noise.mjs index 816dd25..734a04c 100644 --- a/src/strudel/superdough/noise.mjs +++ b/src/strudel/superdough/noise.mjs @@ -1,72 +1,72 @@ -import { drywet } from './helpers.mjs'; -import { getAudioContext } from './audioContext.mjs'; +import { drywet } from './helpers.mjs' +import { getAudioContext } from './audioContext.mjs' -let noiseCache = {}; +let noiseCache = {} // lazy generates noise buffers and keeps them forever export function getNoiseBuffer(type, density) { - const ac = getAudioContext(); + const ac = getAudioContext() if (noiseCache[type]) { - return noiseCache[type]; + return noiseCache[type] } - const bufferSize = 2 * ac.sampleRate; - const noiseBuffer = ac.createBuffer(1, bufferSize, ac.sampleRate); - const output = noiseBuffer.getChannelData(0); - let lastOut = 0; - let b0, b1, b2, b3, b4, b5, b6; - b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0.0; + const bufferSize = 2 * ac.sampleRate + const noiseBuffer = ac.createBuffer(1, bufferSize, ac.sampleRate) + const output = noiseBuffer.getChannelData(0) + let lastOut = 0 + let b0, b1, b2, b3, b4, b5, b6 + b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0.0 for (let i = 0; i < bufferSize; i++) { if (type === 'white') { - output[i] = Math.random() * 2 - 1; + output[i] = Math.random() * 2 - 1 } else if (type === 'brown') { - let white = Math.random() * 2 - 1; - output[i] = (lastOut + 0.02 * white) / 1.02; - lastOut = output[i]; + let white = Math.random() * 2 - 1 + output[i] = (lastOut + 0.02 * white) / 1.02 + lastOut = output[i] } else if (type === 'pink') { - let white = Math.random() * 2 - 1; - b0 = 0.99886 * b0 + white * 0.0555179; - b1 = 0.99332 * b1 + white * 0.0750759; - b2 = 0.969 * b2 + white * 0.153852; - b3 = 0.8665 * b3 + white * 0.3104856; - b4 = 0.55 * b4 + white * 0.5329522; - b5 = -0.7616 * b5 - white * 0.016898; - output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362; - output[i] *= 0.11; - b6 = white * 0.115926; + let white = Math.random() * 2 - 1 + b0 = 0.99886 * b0 + white * 0.0555179 + b1 = 0.99332 * b1 + white * 0.0750759 + b2 = 0.969 * b2 + white * 0.153852 + b3 = 0.8665 * b3 + white * 0.3104856 + b4 = 0.55 * b4 + white * 0.5329522 + b5 = -0.7616 * b5 - white * 0.016898 + output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362 + output[i] *= 0.11 + b6 = white * 0.115926 } else if (type === 'crackle') { - const probability = density * 0.01; + const probability = density * 0.01 if (Math.random() < probability) { - output[i] = Math.random() * 2 - 1; + output[i] = Math.random() * 2 - 1 } else { - output[i] = 0; + output[i] = 0 } } } // Prevent caching to randomize crackles - if (type !== 'crackle') noiseCache[type] = noiseBuffer; - return noiseBuffer; + if (type !== 'crackle') noiseCache[type] = noiseBuffer + return noiseBuffer } // expects one of noises as type export function getNoiseOscillator(type = 'white', t, density = 0.02) { - const ac = getAudioContext(); - const o = ac.createBufferSource(); - o.buffer = getNoiseBuffer(type, density); - o.loop = true; - o.start(t); + const ac = getAudioContext() + const o = ac.createBufferSource() + o.buffer = getNoiseBuffer(type, density) + o.loop = true + o.start(t) return { node: o, stop: (time) => o.stop(time), - }; + } } export function getNoiseMix(inputNode, wet, t) { - const noiseOscillator = getNoiseOscillator('pink', t); - const noiseMix = drywet(inputNode, noiseOscillator.node, wet); + const noiseOscillator = getNoiseOscillator('pink', t) + const noiseMix = drywet(inputNode, noiseOscillator.node, wet) return { node: noiseMix, stop: (time) => noiseOscillator?.stop(time), - }; + } } diff --git a/src/strudel/superdough/ola-processor.js b/src/strudel/superdough/ola-processor.js index 38d45a2..34a996c 100644 --- a/src/strudel/superdough/ola-processor.js +++ b/src/strudel/superdough/ola-processor.js @@ -1,36 +1,36 @@ -'use strict'; +'use strict' // sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file -const WEBAUDIO_BLOCK_SIZE = 128; +const WEBAUDIO_BLOCK_SIZE = 128 /** Overlap-Add Node */ class OLAProcessor extends AudioWorkletProcessor { constructor(options) { - super(options); - this.started = false; - this.nbInputs = options.numberOfInputs; - this.nbOutputs = options.numberOfOutputs; + super(options) + this.started = false + this.nbInputs = options.numberOfInputs + this.nbOutputs = options.numberOfOutputs - this.blockSize = options.processorOptions.blockSize; + this.blockSize = options.processorOptions.blockSize // TODO for now, the only support hop size is the size of a web audio block - this.hopSize = WEBAUDIO_BLOCK_SIZE; + this.hopSize = WEBAUDIO_BLOCK_SIZE - this.nbOverlaps = this.blockSize / this.hopSize; + this.nbOverlaps = this.blockSize / this.hopSize // pre-allocate input buffers (will be reallocated if needed) - this.inputBuffers = new Array(this.nbInputs); - this.inputBuffersHead = new Array(this.nbInputs); - this.inputBuffersToSend = new Array(this.nbInputs); + this.inputBuffers = new Array(this.nbInputs) + this.inputBuffersHead = new Array(this.nbInputs) + this.inputBuffersToSend = new Array(this.nbInputs) // default to 1 channel per input until we know more for (let i = 0; i < this.nbInputs; i++) { - this.allocateInputChannels(i, 1); + this.allocateInputChannels(i, 1) } // pre-allocate input buffers (will be reallocated if needed) - this.outputBuffers = new Array(this.nbOutputs); - this.outputBuffersToRetrieve = new Array(this.nbOutputs); + this.outputBuffers = new Array(this.nbOutputs) + this.outputBuffersToRetrieve = new Array(this.nbOutputs) // default to 1 channel per output until we know more for (let i = 0; i < this.nbOutputs; i++) { - this.allocateOutputChannels(i, 1); + this.allocateOutputChannels(i, 1) } } @@ -38,16 +38,16 @@ class OLAProcessor extends AudioWorkletProcessor { (channel numbers may lety during lifecycle) **/ reallocateChannelsIfNeeded(inputs, outputs) { for (let i = 0; i < this.nbInputs; i++) { - let nbChannels = inputs[i].length; + let nbChannels = inputs[i].length if (nbChannels != this.inputBuffers[i].length) { - this.allocateInputChannels(i, nbChannels); + this.allocateInputChannels(i, nbChannels) } } for (let i = 0; i < this.nbOutputs; i++) { - let nbChannels = outputs[i].length; + let nbChannels = outputs[i].length if (nbChannels != this.outputBuffers[i].length) { - this.allocateOutputChannels(i, nbChannels); + this.allocateOutputChannels(i, nbChannels) } } } @@ -55,36 +55,39 @@ class OLAProcessor extends AudioWorkletProcessor { allocateInputChannels(inputIndex, nbChannels) { // allocate input buffers - this.inputBuffers[inputIndex] = new Array(nbChannels); + this.inputBuffers[inputIndex] = new Array(nbChannels) for (let i = 0; i < nbChannels; i++) { - this.inputBuffers[inputIndex][i] = new Float32Array(this.blockSize + WEBAUDIO_BLOCK_SIZE); - this.inputBuffers[inputIndex][i].fill(0); + this.inputBuffers[inputIndex][i] = new Float32Array(this.blockSize + WEBAUDIO_BLOCK_SIZE) + this.inputBuffers[inputIndex][i].fill(0) } // allocate input buffers to send and head pointers to copy from // (cannot directly send a pointer/subarray because input may be modified) - this.inputBuffersHead[inputIndex] = new Array(nbChannels); - this.inputBuffersToSend[inputIndex] = new Array(nbChannels); + this.inputBuffersHead[inputIndex] = new Array(nbChannels) + this.inputBuffersToSend[inputIndex] = new Array(nbChannels) for (let i = 0; i < nbChannels; i++) { - this.inputBuffersHead[inputIndex][i] = this.inputBuffers[inputIndex][i].subarray(0, this.blockSize); - this.inputBuffersToSend[inputIndex][i] = new Float32Array(this.blockSize); + this.inputBuffersHead[inputIndex][i] = this.inputBuffers[inputIndex][i].subarray( + 0, + this.blockSize + ) + this.inputBuffersToSend[inputIndex][i] = new Float32Array(this.blockSize) } } allocateOutputChannels(outputIndex, nbChannels) { // allocate output buffers - this.outputBuffers[outputIndex] = new Array(nbChannels); + this.outputBuffers[outputIndex] = new Array(nbChannels) for (let i = 0; i < nbChannels; i++) { - this.outputBuffers[outputIndex][i] = new Float32Array(this.blockSize); - this.outputBuffers[outputIndex][i].fill(0); + this.outputBuffers[outputIndex][i] = new Float32Array(this.blockSize) + this.outputBuffers[outputIndex][i].fill(0) } // allocate output buffers to retrieve // (cannot send a pointer/subarray because new output has to be add to exising output) - this.outputBuffersToRetrieve[outputIndex] = new Array(nbChannels); + this.outputBuffersToRetrieve[outputIndex] = new Array(nbChannels) for (let i = 0; i < nbChannels; i++) { - this.outputBuffersToRetrieve[outputIndex][i] = new Float32Array(this.blockSize); - this.outputBuffersToRetrieve[outputIndex][i].fill(0); + this.outputBuffersToRetrieve[outputIndex][i] = new Float32Array(this.blockSize) + this.outputBuffersToRetrieve[outputIndex][i].fill(0) } } @@ -94,16 +97,16 @@ class OLAProcessor extends AudioWorkletProcessor { if (inputs[0].length && inputs[0][0].length == 0) { for (let i = 0; i < this.nbInputs; i++) { for (let j = 0; j < this.inputBuffers[i].length; j++) { - this.inputBuffers[i][j].fill(0, this.blockSize); + this.inputBuffers[i][j].fill(0, this.blockSize) } } - return; + return } for (let i = 0; i < this.nbInputs; i++) { for (let j = 0; j < this.inputBuffers[i].length; j++) { - let webAudioBlock = inputs[i][j]; - this.inputBuffers[i][j].set(webAudioBlock, this.blockSize); + let webAudioBlock = inputs[i][j] + this.inputBuffers[i][j].set(webAudioBlock, this.blockSize) } } } @@ -112,8 +115,8 @@ class OLAProcessor extends AudioWorkletProcessor { writeOutputs(outputs) { for (let i = 0; i < this.nbInputs; i++) { for (let j = 0; j < this.inputBuffers[i].length; j++) { - let webAudioBlock = this.outputBuffers[i][j].subarray(0, WEBAUDIO_BLOCK_SIZE); - outputs[i][j].set(webAudioBlock); + let webAudioBlock = this.outputBuffers[i][j].subarray(0, WEBAUDIO_BLOCK_SIZE) + outputs[i][j].set(webAudioBlock) } } } @@ -122,7 +125,7 @@ class OLAProcessor extends AudioWorkletProcessor { shiftInputBuffers() { for (let i = 0; i < this.nbInputs; i++) { for (let j = 0; j < this.inputBuffers[i].length; j++) { - this.inputBuffers[i][j].copyWithin(0, WEBAUDIO_BLOCK_SIZE); + this.inputBuffers[i][j].copyWithin(0, WEBAUDIO_BLOCK_SIZE) } } } @@ -131,8 +134,8 @@ class OLAProcessor extends AudioWorkletProcessor { shiftOutputBuffers() { for (let i = 0; i < this.nbOutputs; i++) { for (let j = 0; j < this.outputBuffers[i].length; j++) { - this.outputBuffers[i][j].copyWithin(0, WEBAUDIO_BLOCK_SIZE); - this.outputBuffers[i][j].subarray(this.blockSize - WEBAUDIO_BLOCK_SIZE).fill(0); + this.outputBuffers[i][j].copyWithin(0, WEBAUDIO_BLOCK_SIZE) + this.outputBuffers[i][j].subarray(this.blockSize - WEBAUDIO_BLOCK_SIZE).fill(0) } } } @@ -141,7 +144,7 @@ class OLAProcessor extends AudioWorkletProcessor { prepareInputBuffersToSend() { for (let i = 0; i < this.nbInputs; i++) { for (let j = 0; j < this.inputBuffers[i].length; j++) { - this.inputBuffersToSend[i][j].set(this.inputBuffersHead[i][j]); + this.inputBuffersToSend[i][j].set(this.inputBuffersHead[i][j]) } } } @@ -151,35 +154,35 @@ class OLAProcessor extends AudioWorkletProcessor { for (let i = 0; i < this.nbOutputs; i++) { for (let j = 0; j < this.outputBuffers[i].length; j++) { for (let k = 0; k < this.blockSize; k++) { - this.outputBuffers[i][j][k] += this.outputBuffersToRetrieve[i][j][k] / this.nbOverlaps; + this.outputBuffers[i][j][k] += this.outputBuffersToRetrieve[i][j][k] / this.nbOverlaps } } } } process(inputs, outputs, params) { - const input = inputs[0]; - const hasInput = !(input[0] === undefined); + const input = inputs[0] + const hasInput = !(input[0] === undefined) if (this.started && !hasInput) { - return false; + return false } - this.started = hasInput; - this.reallocateChannelsIfNeeded(inputs, outputs); + this.started = hasInput + this.reallocateChannelsIfNeeded(inputs, outputs) - this.readInputs(inputs); - this.shiftInputBuffers(); - this.prepareInputBuffersToSend(); - this.processOLA(this.inputBuffersToSend, this.outputBuffersToRetrieve, params); - this.handleOutputBuffersToRetrieve(); - this.writeOutputs(outputs); - this.shiftOutputBuffers(); + this.readInputs(inputs) + this.shiftInputBuffers() + this.prepareInputBuffersToSend() + this.processOLA(this.inputBuffersToSend, this.outputBuffersToRetrieve, params) + this.handleOutputBuffersToRetrieve() + this.writeOutputs(outputs) + this.shiftOutputBuffers() - return true; + return true } processOLA(inputs, outputs, params) { - console.assert(false, 'Not overriden'); + console.assert(false, 'Not overriden') } } -export default OLAProcessor; +export default OLAProcessor diff --git a/src/strudel/superdough/package.json b/src/strudel/superdough/package.json deleted file mode 100644 index a821177..0000000 --- a/src/strudel/superdough/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "superdough", - "version": "1.2.5", - "description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.", - "main": "index.mjs", - "type": "module", - "publishConfig": { - "main": "dist/index.mjs" - }, - "directories": { - "example": "examples" - }, - "scripts": { - "build": "vite build", - "prepublishOnly": "npm run build" - }, - "repository": { - "type": "git", - "url": "git+https://codeberg.org/uzu/strudel.git" - }, - "keywords": [ - "tidalcycles", - "strudel", - "pattern", - "livecoding", - "algorave" - ], - "author": "Felix Roos ", - "license": "AGPL-3.0-or-later", - "bugs": { - "url": "https://codeberg.org/uzu/strudel/issues" - }, - "homepage": "https://codeberg.org/uzu/strudel#readme", - "devDependencies": { - "vite": "^6.0.11", - "vite-plugin-bundle-audioworklet": "workspace:*" - }, - "dependencies": { - "nanostores": "^0.11.3" - } -} diff --git a/src/strudel/superdough/reverb.mjs b/src/strudel/superdough/reverb.mjs index 2960b59..d6549a8 100644 --- a/src/strudel/superdough/reverb.mjs +++ b/src/strudel/superdough/reverb.mjs @@ -1,40 +1,40 @@ -import reverbGen from './reverbGen.mjs'; -import { clamp } from './util.mjs'; +import reverbGen from './reverbGen.mjs' +import { clamp } from './util.mjs' if (typeof AudioContext !== 'undefined') { AudioContext.prototype.adjustLength = function (duration, buffer, speed = 1, offsetAmount = 0) { - const sampleOffset = Math.floor(clamp(offsetAmount, 0, 1) * buffer.length); - const newLength = buffer.sampleRate * duration; - const newBuffer = this.createBuffer(buffer.numberOfChannels, buffer.length, buffer.sampleRate); + const sampleOffset = Math.floor(clamp(offsetAmount, 0, 1) * buffer.length) + const newLength = buffer.sampleRate * duration + const newBuffer = this.createBuffer(buffer.numberOfChannels, buffer.length, buffer.sampleRate) for (let channel = 0; channel < buffer.numberOfChannels; channel++) { - let oldData = buffer.getChannelData(channel); - let newData = newBuffer.getChannelData(channel); + let oldData = buffer.getChannelData(channel) + let newData = newBuffer.getChannelData(channel) for (let i = 0; i < newLength; i++) { // loop the buffer around to prevent - let position = (sampleOffset + i * Math.abs(speed)) % oldData.length; + let position = (sampleOffset + i * Math.abs(speed)) % oldData.length if (speed < 1) { - position = position * -1; + position = position * -1 } - newData[i] = oldData.at(position) || 0; + newData[i] = oldData.at(position) || 0 } } - return newBuffer; - }; + return newBuffer + } AudioContext.prototype.createReverb = function (duration, fade, lp, dim, ir, irspeed, irbegin) { - const convolver = this.createConvolver(); + const convolver = this.createConvolver() convolver.generate = (d = 2, fade = 0.1, lp = 15000, dim = 1000, ir, irspeed, irbegin) => { - convolver.duration = d; - convolver.fade = fade; - convolver.lp = lp; - convolver.dim = dim; - convolver.ir = ir; - convolver.irspeed = irspeed; - convolver.irbegin = irbegin; + convolver.duration = d + convolver.fade = fade + convolver.lp = lp + convolver.dim = dim + convolver.ir = ir + convolver.irspeed = irspeed + convolver.irbegin = irbegin if (ir) { - convolver.buffer = this.adjustLength(d, ir, irspeed, irbegin); + convolver.buffer = this.adjustLength(d, ir, irspeed, irbegin) } else { reverbGen.generateReverb( { @@ -46,12 +46,12 @@ if (typeof AudioContext !== 'undefined') { lpFreqEnd: dim, }, (buffer) => { - convolver.buffer = buffer; - }, - ); + convolver.buffer = buffer + } + ) } - }; - convolver.generate(duration, fade, lp, dim, ir, irspeed, irbegin); - return convolver; - }; + } + convolver.generate(duration, fade, lp, dim, ir, irspeed, irbegin) + return convolver + } } diff --git a/src/strudel/superdough/reverbGen.mjs b/src/strudel/superdough/reverbGen.mjs index d2533ca..926253c 100644 --- a/src/strudel/superdough/reverbGen.mjs +++ b/src/strudel/superdough/reverbGen.mjs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -var reverbGen = {}; +var reverbGen = {} /** Generates a reverb impulse response. @@ -22,29 +22,35 @@ var reverbGen = {}; is passed to this function as its parameter. May be called immediately within the current execution context, or later. */ reverbGen.generateReverb = function (params, callback) { - var audioContext = params.audioContext || new AudioContext(); - var sampleRate = audioContext.sampleRate; - var numChannels = params.numChannels || 2; + var audioContext = params.audioContext || new AudioContext() + var sampleRate = audioContext.sampleRate + var numChannels = params.numChannels || 2 // params.decayTime is the -60dB fade time. We let it go 50% longer to get to -90dB. - var totalTime = params.decayTime * 1.5; - var decaySampleFrames = Math.round(params.decayTime * sampleRate); - var numSampleFrames = Math.round(totalTime * sampleRate); - var fadeInSampleFrames = Math.round((params.fadeInTime || 0) * sampleRate); + var totalTime = params.decayTime * 1.5 + var decaySampleFrames = Math.round(params.decayTime * sampleRate) + var numSampleFrames = Math.round(totalTime * sampleRate) + var fadeInSampleFrames = Math.round((params.fadeInTime || 0) * sampleRate) // 60dB is a factor of 1 million in power, or 1000 in amplitude. - var decayBase = Math.pow(1 / 1000, 1 / decaySampleFrames); - var reverbIR = audioContext.createBuffer(numChannels, numSampleFrames, sampleRate); + var decayBase = Math.pow(1 / 1000, 1 / decaySampleFrames) + var reverbIR = audioContext.createBuffer(numChannels, numSampleFrames, sampleRate) for (var i = 0; i < numChannels; i++) { - var chan = reverbIR.getChannelData(i); + var chan = reverbIR.getChannelData(i) for (var j = 0; j < numSampleFrames; j++) { - chan[j] = randomSample() * Math.pow(decayBase, j); + chan[j] = randomSample() * Math.pow(decayBase, j) } for (var j = 0; j < fadeInSampleFrames; j++) { - chan[j] *= j / fadeInSampleFrames; + chan[j] *= j / fadeInSampleFrames } } - applyGradualLowpass(reverbIR, params.lpFreqStart || 0, params.lpFreqEnd || 0, params.decayTime, callback); -}; + applyGradualLowpass( + reverbIR, + params.lpFreqStart || 0, + params.lpFreqEnd || 0, + params.decayTime, + callback + ) +} /** Creates a canvas element showing a graph of the given data. @@ -56,20 +62,20 @@ reverbGen.generateReverb = function (params, callback) { @param {number} max Maximum value of data in the graph (upper edge). @return {!CanvasElement} The generated canvas element. */ reverbGen.generateGraph = function (data, width, height, min, max) { - var canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - var gc = canvas.getContext('2d'); - gc.fillStyle = '#000'; - gc.fillRect(0, 0, canvas.width, canvas.height); - gc.fillStyle = '#fff'; - var xscale = width / data.length; - var yscale = height / (max - min); + var canvas = document.createElement('canvas') + canvas.width = width + canvas.height = height + var gc = canvas.getContext('2d') + gc.fillStyle = '#000' + gc.fillRect(0, 0, canvas.width, canvas.height) + gc.fillStyle = '#fff' + var xscale = width / data.length + var yscale = height / (max - min) for (var i = 0; i < data.length; i++) { - gc.fillRect(i * xscale, height - (data[i] - min) * yscale, 1, 1); + gc.fillRect(i * xscale, height - (data[i] - min) * yscale, 1, 1) } - return canvas; -}; + return canvas +} /** Applies a constantly changing lowpass filter to the given sound. @@ -82,49 +88,53 @@ reverbGen.generateGraph = function (data, width, height, min, max) { immediately within the current execution context, or later.*/ var applyGradualLowpass = function (input, lpFreqStart, lpFreqEnd, lpFreqEndAt, callback) { if (lpFreqStart == 0) { - callback(input); - return; + callback(input) + return } - var channelData = getAllChannelData(input); - var context = new OfflineAudioContext(input.numberOfChannels, channelData[0].length, input.sampleRate); - var player = context.createBufferSource(); - player.buffer = input; - var filter = context.createBiquadFilter(); + var channelData = getAllChannelData(input) + var context = new OfflineAudioContext( + input.numberOfChannels, + channelData[0].length, + input.sampleRate + ) + var player = context.createBufferSource() + player.buffer = input + var filter = context.createBiquadFilter() - lpFreqStart = Math.min(lpFreqStart, input.sampleRate / 2); - lpFreqEnd = Math.min(lpFreqEnd, input.sampleRate / 2); + lpFreqStart = Math.min(lpFreqStart, input.sampleRate / 2) + lpFreqEnd = Math.min(lpFreqEnd, input.sampleRate / 2) - filter.type = 'lowpass'; - filter.Q.value = 0.0001; - filter.frequency.setValueAtTime(lpFreqStart, 0); - filter.frequency.linearRampToValueAtTime(lpFreqEnd, lpFreqEndAt); + filter.type = 'lowpass' + filter.Q.value = 0.0001 + filter.frequency.setValueAtTime(lpFreqStart, 0) + filter.frequency.linearRampToValueAtTime(lpFreqEnd, lpFreqEndAt) - player.connect(filter); - filter.connect(context.destination); - player.start(); + player.connect(filter) + filter.connect(context.destination) + player.start() context.oncomplete = function (event) { - callback(event.renderedBuffer); - }; - context.startRendering(); + callback(event.renderedBuffer) + } + context.startRendering() - window.filterNode = filter; -}; + window.filterNode = filter +} /** @private @param {!AudioBuffer} buffer @return {!Array.} An array containing the Float32Array of each channel's samples. */ var getAllChannelData = function (buffer) { - var channels = []; + var channels = [] for (var i = 0; i < buffer.numberOfChannels; i++) { - channels[i] = buffer.getChannelData(i); + channels[i] = buffer.getChannelData(i) } - return channels; -}; + return channels +} /** @private @return {number} A random number from -1 to 1. */ var randomSample = function () { - return Math.random() * 2 - 1; -}; + return Math.random() * 2 - 1 +} -export default reverbGen; +export default reverbGen diff --git a/src/strudel/superdough/sampler.mjs b/src/strudel/superdough/sampler.mjs index 3232fa4..8934b0c 100644 --- a/src/strudel/superdough/sampler.mjs +++ b/src/strudel/superdough/sampler.mjs @@ -1,230 +1,232 @@ -import { getCommonSampleInfo } from './util.mjs'; -import { registerSound, registerWaveTable } from './index.mjs'; -import { getAudioContext } from './audioContext.mjs'; -import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs'; -import { logger } from './logger.mjs'; +import { getCommonSampleInfo } from './util.mjs' +import { registerSound, registerWaveTable } from './index.mjs' +import { getAudioContext } from './audioContext.mjs' +import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs' +import { logger } from './logger.mjs' -const bufferCache = {}; // string: Promise -const loadCache = {}; // string: Promise +const bufferCache = {} // string: Promise +const loadCache = {} // string: Promise -export const getCachedBuffer = (url) => bufferCache[url]; +export const getCachedBuffer = (url) => bufferCache[url] function humanFileSize(bytes, si) { - var thresh = si ? 1000 : 1024; - if (bytes < thresh) return bytes + ' B'; + var thresh = si ? 1000 : 1024 + if (bytes < thresh) return bytes + ' B' var units = si ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] - : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; - var u = -1; + : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] + var u = -1 do { - bytes /= thresh; - ++u; - } while (bytes >= thresh); - return bytes.toFixed(1) + ' ' + units[u]; + bytes /= thresh + ++u + } while (bytes >= thresh) + return bytes.toFixed(1) + ' ' + units[u] } export function getSampleInfo(hapValue, bank) { - const { speed = 1.0 } = hapValue; - const { transpose, url, index, midi, label } = getCommonSampleInfo(hapValue, bank); - let playbackRate = Math.abs(speed) * Math.pow(2, transpose / 12); - return { transpose, url, index, midi, label, playbackRate }; + const { speed = 1.0 } = hapValue + const { transpose, url, index, midi, label } = getCommonSampleInfo(hapValue, bank) + let playbackRate = Math.abs(speed) * Math.pow(2, transpose / 12) + return { transpose, url, index, midi, label, playbackRate } } // takes hapValue and returns buffer + playbackRate. export const getSampleBuffer = async (hapValue, bank, resolveUrl) => { - let { url: sampleUrl, label, playbackRate } = getSampleInfo(hapValue, bank); + let { url: sampleUrl, label, playbackRate } = getSampleInfo(hapValue, bank) if (resolveUrl) { - sampleUrl = await resolveUrl(sampleUrl); + sampleUrl = await resolveUrl(sampleUrl) } - const ac = getAudioContext(); - const buffer = await loadBuffer(sampleUrl, ac, label); + const ac = getAudioContext() + const buffer = await loadBuffer(sampleUrl, ac, label) if (hapValue.unit === 'c') { - playbackRate = playbackRate * buffer.duration; + playbackRate = playbackRate * buffer.duration } - return { buffer, playbackRate }; -}; + return { buffer, playbackRate } +} // creates playback ready AudioBufferSourceNode from hapValue export const getSampleBufferSource = async (hapValue, bank, resolveUrl) => { - let { buffer, playbackRate } = await getSampleBuffer(hapValue, bank, resolveUrl); + let { buffer, playbackRate } = await getSampleBuffer(hapValue, bank, resolveUrl) if (hapValue.speed < 0) { // should this be cached? - buffer = reverseBuffer(buffer); + buffer = reverseBuffer(buffer) } - const ac = getAudioContext(); - const bufferSource = ac.createBufferSource(); - bufferSource.buffer = buffer; - bufferSource.playbackRate.value = playbackRate; + const ac = getAudioContext() + const bufferSource = ac.createBufferSource() + bufferSource.buffer = buffer + bufferSource.playbackRate.value = playbackRate - const { loopBegin = 0, loopEnd = 1, begin = 0, end = 1 } = hapValue; + const { loopBegin = 0, loopEnd = 1, begin = 0, end = 1 } = hapValue // "The computation of the offset into the sound is performed using the sound buffer's natural sample rate, // rather than the current playback rate, so even if the sound is playing at twice its normal speed, // the midway point through a 10-second audio buffer is still 5." - const offset = begin * bufferSource.buffer.duration; + const offset = begin * bufferSource.buffer.duration - const loop = hapValue.loop; + const loop = hapValue.loop if (loop) { - bufferSource.loop = true; - bufferSource.loopStart = loopBegin * bufferSource.buffer.duration - offset; - bufferSource.loopEnd = loopEnd * bufferSource.buffer.duration - offset; + bufferSource.loop = true + bufferSource.loopStart = loopBegin * bufferSource.buffer.duration - offset + bufferSource.loopEnd = loopEnd * bufferSource.buffer.duration - offset } - const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value; - const sliceDuration = (end - begin) * bufferDuration; - return { bufferSource, offset, bufferDuration, sliceDuration }; -}; + const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value + const sliceDuration = (end - begin) * bufferDuration + return { bufferSource, offset, bufferDuration, sliceDuration } +} export const loadBuffer = (url, ac, s, n = 0) => { - const label = s ? `sound "${s}:${n}"` : 'sample'; - url = url.replace('#', '%23'); + const label = s ? `sound "${s}:${n}"` : 'sample' + url = url.replace('#', '%23') if (!loadCache[url]) { - logger(`[sampler] load ${label}..`, 'load-sample', { url }); - const timestamp = Date.now(); + logger(`[sampler] load ${label}..`, 'load-sample', { url }) + const timestamp = Date.now() loadCache[url] = fetch(url) .then((res) => res.arrayBuffer()) .then(async (res) => { - const took = Date.now() - timestamp; - const size = humanFileSize(res.byteLength); + const took = Date.now() - timestamp + const size = humanFileSize(res.byteLength) // const downSpeed = humanFileSize(res.byteLength / took); - logger(`[sampler] load ${label}... done! loaded ${size} in ${took}ms`, 'loaded-sample', { url }); - const decoded = await ac.decodeAudioData(res); - bufferCache[url] = decoded; - return decoded; - }); + logger(`[sampler] load ${label}... done! loaded ${size} in ${took}ms`, 'loaded-sample', { + url, + }) + const decoded = await ac.decodeAudioData(res) + bufferCache[url] = decoded + return decoded + }) } - return loadCache[url]; -}; + return loadCache[url] +} export function reverseBuffer(buffer) { - const ac = getAudioContext(); - const reversed = ac.createBuffer(buffer.numberOfChannels, buffer.length, ac.sampleRate); + const ac = getAudioContext() + const reversed = ac.createBuffer(buffer.numberOfChannels, buffer.length, ac.sampleRate) for (let channel = 0; channel < buffer.numberOfChannels; channel++) { - reversed.copyToChannel(buffer.getChannelData(channel).slice().reverse(), channel, channel); + reversed.copyToChannel(buffer.getChannelData(channel).slice().reverse(), channel, channel) } - return reversed; + return reversed } export const getLoadedBuffer = (url) => { - return bufferCache[url]; -}; + return bufferCache[url] +} function resolveSpecialPaths(base) { if (base.startsWith('bubo:')) { - const [_, repo] = base.split(':'); - base = `github:Bubobubobubobubo/dough-${repo}`; + const [_, repo] = base.split(':') + base = `github:Bubobubobubobubo/dough-${repo}` } - return base; + return base } function githubPath(base, subpath = '') { if (!base.startsWith('github:')) { - throw new Error('expected "github:" at the start of pseudoUrl'); + throw new Error('expected "github:" at the start of pseudoUrl') } - let path = base.slice('github:'.length); - path = path.endsWith('/') ? path.slice(0, -1) : path; + let path = base.slice('github:'.length) + path = path.endsWith('/') ? path.slice(0, -1) : path - let components = path.split('/'); - let user = components[0]; - let repo = components.length >= 2 ? components[1] : 'samples'; - let branch = components.length >= 3 ? components[2] : 'main'; - let other = components.slice(3); + let components = path.split('/') + let user = components[0] + let repo = components.length >= 2 ? components[1] : 'samples' + let branch = components.length >= 3 ? components[2] : 'main' + let other = components.slice(3) if (subpath) { - other.push(subpath); + other.push(subpath) } - other = other.join('/'); + other = other.join('/') - return `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${other}`; + return `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${other}` } export const processSampleMap = (sampleMap, fn, baseUrl = sampleMap._base || '') => { return Object.entries(sampleMap).forEach(([key, value]) => { if (typeof value === 'string') { - value = [value]; + value = [value] } if (typeof value !== 'object') { - throw new Error('wrong sample map format for ' + key); + throw new Error('wrong sample map format for ' + key) } - baseUrl = value._base || baseUrl; - baseUrl = resolveSpecialPaths(baseUrl); + baseUrl = value._base || baseUrl + baseUrl = resolveSpecialPaths(baseUrl) if (baseUrl.startsWith('github:')) { - baseUrl = githubPath(baseUrl, ''); + baseUrl = githubPath(baseUrl, '') } - const fullUrl = (v) => baseUrl + v; + const fullUrl = (v) => baseUrl + v if (Array.isArray(value)) { //return [key, value.map(replaceUrl)]; - value = value.map(fullUrl); + value = value.map(fullUrl) } else { // must be object value = Object.fromEntries( Object.entries(value).map(([note, samples]) => { - return [note, (typeof samples === 'string' ? [samples] : samples).map(fullUrl)]; - }), - ); + return [note, (typeof samples === 'string' ? [samples] : samples).map(fullUrl)] + }) + ) } - fn(key, value); - }); -}; + fn(key, value) + }) +} // allows adding a custom url prefix handler // for example, it is used by the desktop app to load samples starting with '~/music' -let resourcePrefixHandlers = {}; +let resourcePrefixHandlers = {} export function registerSamplesPrefix(prefix, resolve) { - resourcePrefixHandlers[prefix] = resolve; + resourcePrefixHandlers[prefix] = resolve } // finds a prefix handler for the given url (if any) function getSamplesPrefixHandler(url) { - const handler = Object.entries(resourcePrefixHandlers).find(([key]) => url.startsWith(key)); + const handler = Object.entries(resourcePrefixHandlers).find(([key]) => url.startsWith(key)) if (handler) { - return handler[1]; + return handler[1] } - return; + return } export async function fetchSampleMap(url) { // check if custom prefix handler - const handler = getSamplesPrefixHandler(url); + const handler = getSamplesPrefixHandler(url) if (handler) { - return handler(url); + return handler(url) } - url = resolveSpecialPaths(url); + url = resolveSpecialPaths(url) if (url.startsWith('github:')) { - url = githubPath(url, 'strudel.json'); + url = githubPath(url, 'strudel.json') } if (url.startsWith('local:')) { - url = `http://localhost:5432`; + url = `http://localhost:5432` } if (url.startsWith('shabda:')) { - let [_, path] = url.split('shabda:'); - url = `https://shabda.ndre.gr/${path}.json?strudel=1`; + let [_, path] = url.split('shabda:') + url = `https://shabda.ndre.gr/${path}.json?strudel=1` } if (url.startsWith('shabda/speech')) { - let [_, path] = url.split('shabda/speech'); - path = path.startsWith('/') ? path.substring(1) : path; - let [params, words] = path.split(':'); - let gender = 'f'; - let language = 'en-GB'; + let [_, path] = url.split('shabda/speech') + path = path.startsWith('/') ? path.substring(1) : path + let [params, words] = path.split(':') + let gender = 'f' + let language = 'en-GB' if (params) { - [language, gender] = params.split('/'); + ;[language, gender] = params.split('/') } - url = `https://shabda.ndre.gr/speech/${words}.json?gender=${gender}&language=${language}&strudel=1'`; + url = `https://shabda.ndre.gr/speech/${words}.json?gender=${gender}&language=${language}&strudel=1'` } if (typeof fetch !== 'function') { // not a browser - return; + return } - const base = url.split('/').slice(0, -1).join('/'); + const base = url.split('/').slice(0, -1).join('/') if (typeof fetch === 'undefined') { // skip fetch when in node / testing - return; + return } const json = await fetch(url) .then((res) => res.json()) .catch((error) => { - console.error(error); - throw new Error(`error loading "${url}"`); - }); - return [json, json._base || base]; + console.error(error) + throw new Error(`error loading "${url}"`) + }) + return [json, json._base || base] } /** @@ -248,21 +250,21 @@ export async function fetchSampleMap(url) { export const samples = async (sampleMap, baseUrl = sampleMap._base || '', options = {}) => { if (typeof sampleMap === 'string') { - const [json, base] = await fetchSampleMap(sampleMap); - return samples(json, baseUrl || base, options); + const [json, base] = await fetchSampleMap(sampleMap) + return samples(json, baseUrl || base, options) } - const { prebake, tag } = options; + const { prebake, tag } = options processSampleMap( sampleMap, (key, bank) => { - registerSampleSource(key, bank, { baseUrl, prebake, tag }); + registerSampleSource(key, bank, { baseUrl, prebake, tag }) }, - baseUrl, - ); -}; + baseUrl + ) +} -const cutGroups = []; +const cutGroups = [] export async function onTriggerSample(t, value, onended, bank, resolveUrl) { let { @@ -274,78 +276,87 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { n = 0, speed = 1, // sample playback speed duration, - } = value; + } = value // load sample if (speed === 0) { // no playback - return; + return } - const ac = getAudioContext(); + const ac = getAudioContext() // destructure adsr here, because the default should be different for synths and samples - let [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]); + let [attack, decay, sustain, release] = getADSRValues([ + value.attack, + value.decay, + value.sustain, + value.release, + ]) - const { bufferSource, sliceDuration, offset } = await getSampleBufferSource(value, bank, resolveUrl); + const { bufferSource, sliceDuration, offset } = await getSampleBufferSource( + value, + bank, + resolveUrl + ) // asny stuff above took too long? if (ac.currentTime > t) { - logger(`[sampler] still loading sound "${s}:${n}"`, 'highlight'); + logger(`[sampler] still loading sound "${s}:${n}"`, 'highlight') // console.warn('sample still loading:', s, n); - return; + return } if (!bufferSource) { - logger(`[sampler] could not load "${s}:${n}"`, 'error'); - return; + logger(`[sampler] could not load "${s}:${n}"`, 'error') + return } // vibrato - let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t); + let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t) - const time = t + nudge; - bufferSource.start(time, offset); + const time = t + nudge + bufferSource.start(time, offset) - const envGain = ac.createGain(); - const node = bufferSource.connect(envGain); + const envGain = ac.createGain() + const node = bufferSource.connect(envGain) // if none of these controls is set, the duration of the sound will be set to the duration of the sample slice if (clip == null && loop == null && value.release == null) { - duration = sliceDuration; + duration = sliceDuration } - let holdEnd = t + duration; + let holdEnd = t + duration - getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear'); + getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear') // pitch envelope - getPitchEnvelope(bufferSource.detune, value, t, holdEnd); + getPitchEnvelope(bufferSource.detune, value, t, holdEnd) - const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox... - node.connect(out); + const out = ac.createGain() // we need a separate gain for the cutgroups because firefox... + node.connect(out) bufferSource.onended = function () { - bufferSource.disconnect(); - vibratoOscillator?.stop(); - node.disconnect(); - out.disconnect(); - onended(); - }; - let envEnd = holdEnd + release + 0.01; - bufferSource.stop(envEnd); + bufferSource.disconnect() + vibratoOscillator?.stop() + node.disconnect() + out.disconnect() + onended() + } + let envEnd = holdEnd + release + 0.01 + bufferSource.stop(envEnd) const stop = (endTime) => { - bufferSource.stop(endTime); - }; - const handle = { node: out, bufferSource, stop }; + bufferSource.stop(endTime) + } + const handle = { node: out, bufferSource, stop } // cut groups if (cut !== undefined) { - const prev = cutGroups[cut]; + const prev = cutGroups[cut] if (prev) { - prev.node.gain.setValueAtTime(1, time); - prev.node.gain.linearRampToValueAtTime(0, time + 0.01); + prev.node.gain.setValueAtTime(1, time) + prev.node.gain.linearRampToValueAtTime(0, time + 0.01) } - cutGroups[cut] = handle; + cutGroups[cut] = handle } - return handle; + return handle } function registerSample(key, bank, params) { @@ -353,14 +364,14 @@ function registerSample(key, bank, params) { type: 'sample', samples: bank, ...params, - }); + }) } export function registerSampleSource(key, bank, params) { - const isWavetable = key.startsWith('wt_'); + const isWavetable = key.startsWith('wt_') if (isWavetable) { - registerWaveTable(key, bank, params); + registerWaveTable(key, bank, params) } else { - registerSample(key, bank, params); + registerSample(key, bank, params) } } diff --git a/src/strudel/superdough/superdough.mjs b/src/strudel/superdough/superdough.mjs index a37504b..ed8a7a7 100644 --- a/src/strudel/superdough/superdough.mjs +++ b/src/strudel/superdough/superdough.mjs @@ -4,72 +4,80 @@ Copyright (C) 2022 Strudel contributors - see . */ -import './feedbackdelay.mjs'; -import './reverb.mjs'; -import './vowel.mjs'; -import { nanFallback, _mod, cycleToSeconds } from './util.mjs'; -import workletsUrl from './worklets.mjs?audioworklet'; -import { createFilter, gainNode, getCompressor, getDistortion, getLfo, getWorklet, effectSend } from './helpers.mjs'; -import { map } from 'nanostores'; -import { logger } from './logger.mjs'; -import { loadBuffer } from './sampler.mjs'; -import { getAudioContext } from './audioContext.mjs'; -import { SuperdoughAudioController } from './superdoughoutput.mjs'; +import './feedbackdelay.mjs' +import './reverb.mjs' +import './vowel.mjs' +import { nanFallback, _mod, cycleToSeconds } from './util.mjs' +import workletsUrl from './worklets.mjs?audioworklet' +import { + createFilter, + gainNode, + getCompressor, + getDistortion, + getLfo, + getWorklet, + effectSend, +} from './helpers.mjs' +import { map } from 'nanostores' +import { logger } from './logger.mjs' +import { loadBuffer } from './sampler.mjs' +import { getAudioContext } from './audioContext.mjs' +import { SuperdoughAudioController } from './superdoughoutput.mjs' -export const DEFAULT_MAX_POLYPHONY = 128; -const DEFAULT_AUDIO_DEVICE_NAME = 'System Standard'; +export const DEFAULT_MAX_POLYPHONY = 128 +const DEFAULT_AUDIO_DEVICE_NAME = 'System Standard' -let maxPolyphony = DEFAULT_MAX_POLYPHONY; +let maxPolyphony = DEFAULT_MAX_POLYPHONY export function setMaxPolyphony(polyphony) { - maxPolyphony = parseInt(polyphony) ?? DEFAULT_MAX_POLYPHONY; + maxPolyphony = parseInt(polyphony) ?? DEFAULT_MAX_POLYPHONY } -let multiChannelOrbits = false; +let multiChannelOrbits = false export function setMultiChannelOrbits(bool) { - multiChannelOrbits = bool == true; + multiChannelOrbits = bool == true } -export const soundMap = map(); +export const soundMap = map() export function registerSound(key, onTrigger, data = {}) { - key = key.toLowerCase().replace(/\s+/g, '_'); - soundMap.setKey(key, { onTrigger, data }); + key = key.toLowerCase().replace(/\s+/g, '_') + soundMap.setKey(key, { onTrigger, data }) } -let gainCurveFunc = (val) => val; +let gainCurveFunc = (val) => val export function applyGainCurve(val) { - return gainCurveFunc(val); + return gainCurveFunc(val) } export function setGainCurve(newGainCurveFunc) { - gainCurveFunc = newGainCurveFunc; + gainCurveFunc = newGainCurveFunc } function aliasBankMap(aliasMap) { // Make all bank keys lower case for case insensitivity for (const key in aliasMap) { - aliasMap[key.toLowerCase()] = aliasMap[key]; + aliasMap[key.toLowerCase()] = aliasMap[key] } // Look through every sound... - const soundDictionary = soundMap.get(); + const soundDictionary = soundMap.get() for (const key in soundDictionary) { // Check if the sound is part of a bank... - const [bank, suffix] = key.split('_'); - if (!suffix) continue; + const [bank, suffix] = key.split('_') + if (!suffix) continue // Check if the bank is aliased... - const aliasValue = aliasMap[bank]; + const aliasValue = aliasMap[bank] if (aliasValue) { if (typeof aliasValue === 'string') { // Alias a single alias - soundDictionary[`${aliasValue}_${suffix}`.toLowerCase()] = soundDictionary[key]; + soundDictionary[`${aliasValue}_${suffix}`.toLowerCase()] = soundDictionary[key] } else if (Array.isArray(aliasValue)) { // Alias multiple aliases for (const alias of aliasValue) { - soundDictionary[`${alias}_${suffix}`.toLowerCase()] = soundDictionary[key]; + soundDictionary[`${alias}_${suffix}`.toLowerCase()] = soundDictionary[key] } } } @@ -77,13 +85,13 @@ function aliasBankMap(aliasMap) { // Update the sound map! // We need to destructure here to trigger the update - soundMap.set({ ...soundDictionary }); + soundMap.set({ ...soundDictionary }) } async function aliasBankPath(path) { - const response = await fetch(path); - const aliasMap = await response.json(); - aliasBankMap(aliasMap); + const response = await fetch(path) + const aliasMap = await response.json() + aliasBankMap(aliasMap) } /** @@ -97,14 +105,14 @@ export async function aliasBank(...args) { switch (args.length) { case 1: if (typeof args[0] === 'string') { - return aliasBankPath(args[0]); + return aliasBankPath(args[0]) } else { - return aliasBankMap(args[0]); + return aliasBankMap(args[0]) } case 2: - return aliasBankMap({ [args[0]]: args[1] }); + return aliasBankMap({ [args[0]]: args[1] }) default: - throw new Error('aliasMap expects 1 or 2 arguments, received ' + args.length); + throw new Error('aliasMap expects 1 or 2 arguments, received ' + args.length) } } @@ -115,31 +123,33 @@ export async function aliasBank(...args) { */ export function soundAlias(original, alias) { if (getSound(original) == null) { - logger('soundAlias: original sound not found'); - return; + logger('soundAlias: original sound not found') + return } - soundMap.setKey(alias, getSound(original)); + soundMap.setKey(alias, getSound(original)) } export function getSound(s) { if (typeof s !== 'string') { - console.warn(`getSound: expected string got "${s}". fall back to triangle`); - return soundMap.get().triangle; // is this good? + console.warn(`getSound: expected string got "${s}". fall back to triangle`) + return soundMap.get().triangle // is this good? } - return soundMap.get()[s.toLowerCase()]; + return soundMap.get()[s.toLowerCase()] } export const getAudioDevices = async () => { - await navigator.mediaDevices.getUserMedia({ audio: true }); - let mediaDevices = await navigator.mediaDevices.enumerateDevices(); - mediaDevices = mediaDevices.filter((device) => device.kind === 'audiooutput' && device.deviceId !== 'default'); - const devicesMap = new Map(); - devicesMap.set(DEFAULT_AUDIO_DEVICE_NAME, ''); + await navigator.mediaDevices.getUserMedia({ audio: true }) + let mediaDevices = await navigator.mediaDevices.enumerateDevices() + mediaDevices = mediaDevices.filter( + (device) => device.kind === 'audiooutput' && device.deviceId !== 'default' + ) + const devicesMap = new Map() + devicesMap.set(DEFAULT_AUDIO_DEVICE_NAME, '') mediaDevices.forEach((device) => { - devicesMap.set(device.label, device.deviceId); - }); - return devicesMap; -}; + devicesMap.set(device.label, device.deviceId) + }) + return devicesMap +} let defaultDefaultValues = { s: 'triangle', @@ -164,58 +174,60 @@ let defaultDefaultValues = { i: 1, velocity: 1, fft: 8, -}; +} -const defaultDefaultDefaultValues = Object.freeze({ ...defaultDefaultValues }); +const defaultDefaultDefaultValues = Object.freeze({ ...defaultDefaultValues }) export function setDefault(control, value) { // const main = getControlName(control); // we cant do this because superdough is independent of strudel/core - defaultDefaultValues[control] = value; + defaultDefaultValues[control] = value } export function resetDefaults() { - defaultDefaultValues = { ...defaultDefaultDefaultValues }; + defaultDefaultValues = { ...defaultDefaultDefaultValues } } -let defaultControls = new Map(Object.entries(defaultDefaultValues)); +let defaultControls = new Map(Object.entries(defaultDefaultValues)) export function setDefaultValue(key, value) { - defaultControls.set(key, value); + defaultControls.set(key, value) } export function getDefaultValue(key) { - return defaultControls.get(key); + return defaultControls.get(key) } export function setDefaultValues(defaultsobj) { Object.keys(defaultsobj).forEach((key) => { - setDefaultValue(key, defaultsobj[key]); - }); + setDefaultValue(key, defaultsobj[key]) + }) } export function resetDefaultValues() { - defaultControls = new Map(Object.entries(defaultDefaultValues)); + defaultControls = new Map(Object.entries(defaultDefaultValues)) } export function setVersionDefaults(version) { - resetDefaultValues(); + resetDefaultValues() if (version === '1.0') { - setDefaultValue('fanchor', 0.5); + setDefaultValue('fanchor', 0.5) } } -export const resetLoadedSounds = () => soundMap.set({}); +export const resetLoadedSounds = () => soundMap.set({}) -let externalWorklets = []; +let externalWorklets = [] export function registerWorklet(url) { - externalWorklets.push(url); + externalWorklets.push(url) } -let workletsLoading; +let workletsLoading function loadWorklets() { if (!workletsLoading) { - const audioCtx = getAudioContext(); - const allWorkletURLs = externalWorklets.concat([workletsUrl]); - workletsLoading = Promise.all(allWorkletURLs.map((workletURL) => audioCtx.audioWorklet.addModule(workletURL))); + const audioCtx = getAudioContext() + const allWorkletURLs = externalWorklets.concat([workletsUrl]) + workletsLoading = Promise.all( + allWorkletURLs.map((workletURL) => audioCtx.audioWorklet.addModule(workletURL)) + ) } - return workletsLoading; + return workletsLoading } // this function should be called on first user interaction (to avoid console warning) @@ -225,174 +237,178 @@ export async function initAudio(options = {}) { maxPolyphony, audioDeviceName = DEFAULT_AUDIO_DEVICE_NAME, multiChannelOrbits = false, - } = options; + } = options - setMaxPolyphony(maxPolyphony); - setMultiChannelOrbits(multiChannelOrbits); + setMaxPolyphony(maxPolyphony) + setMultiChannelOrbits(multiChannelOrbits) if (typeof window === 'undefined') { - return; + return } - const audioCtx = getAudioContext(); + const audioCtx = getAudioContext() if (audioDeviceName != null && audioDeviceName != DEFAULT_AUDIO_DEVICE_NAME) { try { - const devices = await getAudioDevices(); - const id = devices.get(audioDeviceName); - const isValidID = (id ?? '').length > 0; + const devices = await getAudioDevices() + const id = devices.get(audioDeviceName) + const isValidID = (id ?? '').length > 0 if (audioCtx.sinkId !== id && isValidID) { - await audioCtx.setSinkId(id); + await audioCtx.setSinkId(id) } logger( - `[superdough] Audio Device set to ${audioDeviceName}, it might take a few seconds before audio plays on all output channels`, - ); + `[superdough] Audio Device set to ${audioDeviceName}, it might take a few seconds before audio plays on all output channels` + ) } catch { - logger('[superdough] failed to set audio interface', 'warning'); + logger('[superdough] failed to set audio interface', 'warning') } } - await audioCtx.resume(); + await audioCtx.resume() if (disableWorklets) { - logger('[superdough]: AudioWorklets disabled with disableWorklets'); - return; + logger('[superdough]: AudioWorklets disabled with disableWorklets') + return } try { - await loadWorklets(); - logger('[superdough] AudioWorklets loaded'); + await loadWorklets() + logger('[superdough] AudioWorklets loaded') } catch (err) { - console.warn('could not load AudioWorklet effects', err); + console.warn('could not load AudioWorklet effects', err) } - logger('[superdough] ready'); + logger('[superdough] ready') } -let audioReady; +let audioReady export async function initAudioOnFirstClick(options) { if (!audioReady) { audioReady = new Promise((resolve) => { document.addEventListener('click', async function listener() { - document.removeEventListener('click', listener); - await initAudio(options); - resolve(); - }); - }); + document.removeEventListener('click', listener) + await initAudio(options) + resolve() + }) + }) } - return audioReady; + return audioReady } -let controller; +let controller function getSuperdoughAudioController() { if (controller == null) { - controller = new SuperdoughAudioController(getAudioContext()); + controller = new SuperdoughAudioController(getAudioContext()) } - return controller; + return controller } export function connectToDestination(input, channels) { - const controller = getSuperdoughAudioController(); - controller.output.connectToDestination(input, channels); + const controller = getSuperdoughAudioController() + controller.output.connectToDestination(input, channels) } function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) { - const ac = getAudioContext(); - const lfoGain = getLfo(ac, time, end, { frequency, depth: sweep * 2 }); + const ac = getAudioContext() + const lfoGain = getLfo(ac, time, end, { frequency, depth: sweep * 2 }) //filters - const numStages = 2; //num of filters in series - let fOffset = 0; - const filterChain = []; + const numStages = 2 //num of filters in series + let fOffset = 0 + const filterChain = [] for (let i = 0; i < numStages; i++) { - const filter = ac.createBiquadFilter(); - filter.type = 'notch'; - filter.gain.value = 1; - filter.frequency.value = centerFrequency + fOffset; - filter.Q.value = 2 - Math.min(Math.max(depth * 2, 0), 1.9); + const filter = ac.createBiquadFilter() + filter.type = 'notch' + filter.gain.value = 1 + filter.frequency.value = centerFrequency + fOffset + filter.Q.value = 2 - Math.min(Math.max(depth * 2, 0), 1.9) - lfoGain.connect(filter.detune); - fOffset += 282; + lfoGain.connect(filter.detune) + fOffset += 282 if (i > 0) { - filterChain[i - 1].connect(filter); + filterChain[i - 1].connect(filter) } - filterChain.push(filter); + filterChain.push(filter) } - return filterChain[filterChain.length - 1]; + return filterChain[filterChain.length - 1] } function getFilterType(ftype) { - ftype = ftype ?? 0; - const filterTypes = ['12db', 'ladder', '24db']; - return typeof ftype === 'number' ? filterTypes[Math.floor(_mod(ftype, filterTypes.length))] : ftype; + ftype = ftype ?? 0 + const filterTypes = ['12db', 'ladder', '24db'] + return typeof ftype === 'number' + ? filterTypes[Math.floor(_mod(ftype, filterTypes.length))] + : ftype } export let analysers = {}, - analysersData = {}; + analysersData = {} export function getAnalyserById(id, fftSize = 1024, smoothingTimeConstant = 0.5) { if (!analysers[id]) { // make sure this doesn't happen too often as it piles up garbage - const analyserNode = getAudioContext().createAnalyser(); - analyserNode.fftSize = fftSize; - analyserNode.smoothingTimeConstant = smoothingTimeConstant; + const analyserNode = getAudioContext().createAnalyser() + analyserNode.fftSize = fftSize + analyserNode.smoothingTimeConstant = smoothingTimeConstant // getDestination().connect(analyserNode); - analysers[id] = analyserNode; - analysersData[id] = new Float32Array(analysers[id].frequencyBinCount); + analysers[id] = analyserNode + analysersData[id] = new Float32Array(analysers[id].frequencyBinCount) } if (analysers[id].fftSize !== fftSize) { - analysers[id].fftSize = fftSize; - analysersData[id] = new Float32Array(analysers[id].frequencyBinCount); + analysers[id].fftSize = fftSize + analysersData[id] = new Float32Array(analysers[id].frequencyBinCount) } - return analysers[id]; + return analysers[id] } export function getAnalyzerData(type = 'time', id = 1) { const getter = { time: () => analysers[id]?.getFloatTimeDomainData(analysersData[id]), frequency: () => analysers[id]?.getFloatFrequencyData(analysersData[id]), - }[type]; + }[type] if (!getter) { - throw new Error(`getAnalyzerData: ${type} not supported. use one of ${Object.keys(getter).join(', ')}`); + throw new Error( + `getAnalyzerData: ${type} not supported. use one of ${Object.keys(getter).join(', ')}` + ) } - getter(); - return analysersData[id]; + getter() + return analysersData[id] } export function resetGlobalEffects() { - controller?.reset(); - analysers = {}; - analysersData = {}; + controller?.reset() + analysers = {} + analysersData = {} } -let activeSoundSources = new Map(); +let activeSoundSources = new Map() //music programs/audio gear usually increments inputs/outputs from 1, we need to subtract 1 from the input because the webaudio API channels start at 0 function mapChannelNumbers(channels) { - return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1); + return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1) } export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) => { // new: t is always expected to be the absolute target onset time - const ac = getAudioContext(); - const audioController = getSuperdoughAudioController(); + const ac = getAudioContext() + const audioController = getSuperdoughAudioController() - let { stretch } = value; + let { stretch } = value if (stretch != null) { //account for phase vocoder latency - const latency = 0.04; - t = t - latency; + const latency = 0.04 + t = t - latency } if (typeof value !== 'object') { throw new Error( `expected hap.value to be an object, but got "${value}". Hint: append .note() or .s() to the end`, - 'error', - ); + 'error' + ) } // duration is passed as value too.. - value.duration = hapDuration; + value.duration = hapDuration // calculate absolute time if (t < ac.currentTime) { console.warn( - `[superdough]: cannot schedule sounds in the past (target: ${t.toFixed(2)}, now: ${ac.currentTime.toFixed(2)})`, - ); - return; + `[superdough]: cannot schedule sounds in the past (target: ${t.toFixed(2)}, now: ${ac.currentTime.toFixed(2)})` + ) + return } // destructure let { @@ -481,92 +497,93 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) compressorKnee, compressorAttack, compressorRelease, - } = value; + } = value - delaytime = delaytime ?? cycleToSeconds(delaysync, cps); + delaytime = delaytime ?? cycleToSeconds(delaysync, cps) const orbitChannels = mapChannelNumbers( - multiChannelOrbits && orbit > 0 ? [orbit * 2 - 1, orbit * 2] : getDefaultValue('channels'), - ); + multiChannelOrbits && orbit > 0 ? [orbit * 2 - 1, orbit * 2] : getDefaultValue('channels') + ) - const channels = value.channels != null ? mapChannelNumbers(value.channels) : orbitChannels; - const orbitBus = audioController.getOrbit(orbit, channels); + const channels = value.channels != null ? mapChannelNumbers(value.channels) : orbitChannels + const orbitBus = audioController.getOrbit(orbit, channels) if (duckorbit != null) { - audioController.duck(duckorbit, t, duckonset, duckattack, duckdepth); + audioController.duck(duckorbit, t, duckonset, duckattack, duckdepth) } - gain = applyGainCurve(nanFallback(gain, 1)); - postgain = applyGainCurve(postgain); - shapevol = applyGainCurve(shapevol); - distortvol = applyGainCurve(distortvol); - delay = applyGainCurve(delay); - velocity = applyGainCurve(velocity); - tremolodepth = applyGainCurve(tremolodepth); - gain *= velocity; // velocity currently only multiplies with gain. it might do other things in the future + gain = applyGainCurve(nanFallback(gain, 1)) + postgain = applyGainCurve(postgain) + shapevol = applyGainCurve(shapevol) + distortvol = applyGainCurve(distortvol) + delay = applyGainCurve(delay) + velocity = applyGainCurve(velocity) + tremolodepth = applyGainCurve(tremolodepth) + gain *= velocity // velocity currently only multiplies with gain. it might do other things in the future - const end = t + hapDuration; - const endWithRelease = end + release; - const chainID = Math.round(Math.random() * 1000000); + const end = t + hapDuration + const endWithRelease = end + release + const chainID = Math.round(Math.random() * 1000000) // oldest audio nodes will be destroyed if maximum polyphony is exceeded for (let i = 0; i <= activeSoundSources.size - maxPolyphony; i++) { - const ch = activeSoundSources.entries().next(); - const source = ch.value[1]; - const chainID = ch.value[0]; - const endTime = t + 0.25; - source?.node?.gain?.linearRampToValueAtTime(0, endTime); - source?.stop?.(endTime); - activeSoundSources.delete(chainID); + const ch = activeSoundSources.entries().next() + const source = ch.value[1] + const chainID = ch.value[0] + const endTime = t + 0.25 + source?.node?.gain?.linearRampToValueAtTime(0, endTime) + source?.stop?.(endTime) + activeSoundSources.delete(chainID) } - let audioNodes = []; + let audioNodes = [] if (['-', '~', '_'].includes(s)) { - return; + return } if (bank && s) { - s = `${bank}_${s}`; - value.s = s; + s = `${bank}_${s}` + value.s = s } // get source AudioNode - let sourceNode; + let sourceNode if (source) { - sourceNode = source(t, value, hapDuration, cps); + sourceNode = source(t, value, hapDuration, cps) } else if (getSound(s)) { - const { onTrigger } = getSound(s); + const { onTrigger } = getSound(s) const onEnded = () => { - audioNodes.forEach((n) => n?.disconnect()); - activeSoundSources.delete(chainID); - }; - const soundHandle = await onTrigger(t, value, onEnded, cps); + audioNodes.forEach((n) => n?.disconnect()) + activeSoundSources.delete(chainID) + } + const soundHandle = await onTrigger(t, value, onEnded, cps) if (soundHandle) { - sourceNode = soundHandle.node; - activeSoundSources.set(chainID, soundHandle); + sourceNode = soundHandle.node + activeSoundSources.set(chainID, soundHandle) } } else { - throw new Error(`sound ${s} not found! Is it loaded?`); + throw new Error(`sound ${s} not found! Is it loaded?`) } if (!sourceNode) { // if onTrigger does not return anything, we will just silently skip // this can be used for things like speed(0) in the sampler - return; + return } if (ac.currentTime > t) { - logger('[webaudio] skip hap: still loading', ac.currentTime - t); - return; + logger('[webaudio] skip hap: still loading', ac.currentTime - t) + return } - const chain = []; // audio nodes that will be connected to each other sequentially - chain.push(sourceNode); - stretch !== undefined && chain.push(getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch })); + const chain = [] // audio nodes that will be connected to each other sequentially + chain.push(sourceNode) + stretch !== undefined && + chain.push(getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch })) // gain stage - chain.push(gainNode(gain)); + chain.push(gainNode(gain)) //filter - const ftype = getFilterType(value.ftype); + const ftype = getFilterType(value.ftype) if (cutoff !== undefined) { let lp = () => createFilter( @@ -583,11 +600,11 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) end, fanchor, ftype, - drive, - ); - chain.push(lp()); + drive + ) + chain.push(lp()) if (ftype === '24db') { - chain.push(lp()); + chain.push(lp()) } } @@ -605,52 +622,65 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) hpenv, t, end, - fanchor, - ); - chain.push(hp()); + fanchor + ) + chain.push(hp()) if (ftype === '24db') { - chain.push(hp()); + chain.push(hp()) } } if (bandf !== undefined) { let bp = () => - createFilter(ac, 'bandpass', bandf, bandq, bpattack, bpdecay, bpsustain, bprelease, bpenv, t, end, fanchor); - chain.push(bp()); + createFilter( + ac, + 'bandpass', + bandf, + bandq, + bpattack, + bpdecay, + bpsustain, + bprelease, + bpenv, + t, + end, + fanchor + ) + chain.push(bp()) if (ftype === '24db') { - chain.push(bp()); + chain.push(bp()) } } if (vowel !== undefined) { - const vowelFilter = ac.createVowelFilter(vowel); - chain.push(vowelFilter); + const vowelFilter = ac.createVowelFilter(vowel) + chain.push(vowelFilter) } // effects - coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse })); - crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush })); - distort !== undefined && chain.push(getDistortion(distort, distortvol, distorttype)); + coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse })) + crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush })) + distort !== undefined && chain.push(getDistortion(distort, distortvol, distorttype)) if (tremolosync != null) { - tremolo = cps * tremolosync; + tremolo = cps * tremolosync } if (value.wtPosSynced != null) { - value.wtPosRate /= cps; + value.wtPosRate /= cps } if (value.wtWarpSynced != null) { - value.wtWarpRate /= cps; + value.wtWarpRate /= cps } if (tremolo !== undefined) { // Allow clipping of modulator for more dynamic possiblities, and to prevent speaker overload // EX: a triangle waveform will clip like this /-\ when the depth is above 1 - const gain = Math.max(1 - tremolodepth, 0); - const amGain = new GainNode(ac, { gain }); + const gain = Math.max(1 - tremolodepth, 0) + const amGain = new GainNode(ac, { gain }) - const time = cycle / cps; + const time = cycle / cps const lfo = getLfo(ac, t, endWithRelease, { skew: tremoloskew ?? (tremoloshape != null ? 0.5 : 1), frequency: tremolo, @@ -662,78 +692,87 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) min: 0, max: 1, curve: 1.5, - }); - lfo.connect(amGain.gain); - chain.push(amGain); + }) + lfo.connect(amGain.gain) + chain.push(amGain) } compressorThreshold !== undefined && chain.push( - getCompressor(ac, compressorThreshold, compressorRatio, compressorKnee, compressorAttack, compressorRelease), - ); + getCompressor( + ac, + compressorThreshold, + compressorRatio, + compressorKnee, + compressorAttack, + compressorRelease + ) + ) // panning if (pan !== undefined) { - const panner = ac.createStereoPanner(); - panner.pan.value = 2 * pan - 1; - chain.push(panner); + const panner = ac.createStereoPanner() + panner.pan.value = 2 * pan - 1 + chain.push(panner) } // phaser if (phaser !== undefined && phaserdepth > 0) { - const phaserFX = getPhaser(t, endWithRelease, phaser, phaserdepth, phasercenter, phasersweep); - chain.push(phaserFX); + const phaserFX = getPhaser(t, endWithRelease, phaser, phaserdepth, phasercenter, phasersweep) + chain.push(phaserFX) } // last gain - const post = new GainNode(ac, { gain: postgain }); - chain.push(post); + const post = new GainNode(ac, { gain: postgain }) + chain.push(post) // delay if (delay > 0 && delaytime > 0 && delayfeedback > 0) { - orbitBus.getDelay(delaytime, delayfeedback, t); - orbitBus.sendDelay(post, delay); + orbitBus.getDelay(delaytime, delayfeedback, t) + orbitBus.sendDelay(post, delay) } // reverb if (room > 0) { - let roomIR; + let roomIR if (ir !== undefined) { - let url; - let sample = getSound(ir); + let url + let sample = getSound(ir) if (Array.isArray(sample)) { - url = sample.data.samples[i % sample.data.samples.length]; + url = sample.data.samples[i % sample.data.samples.length] } else if (typeof sample === 'object') { - url = Object.values(sample.data.samples).flat()[i % Object.values(sample.data.samples).length]; + url = Object.values(sample.data.samples).flat()[ + i % Object.values(sample.data.samples).length + ] } - roomIR = await loadBuffer(url, ac, ir, 0); + roomIR = await loadBuffer(url, ac, ir, 0) } - orbitBus.getReverb(roomsize, roomfade, roomlp, roomdim, roomIR, irspeed, irbegin); - orbitBus.sendReverb(post, room); + orbitBus.getReverb(roomsize, roomfade, roomlp, roomdim, roomIR, irspeed, irbegin) + orbitBus.sendReverb(post, room) } if (djf != null) { - orbitBus.getDjf(djf, t); + orbitBus.getDjf(djf, t) } // analyser if (analyze) { - const analyserNode = getAnalyserById(analyze, 2 ** (fft + 5)); - const analyserSend = effectSend(post, analyserNode, 1); - audioNodes.push(analyserSend); + const analyserNode = getAnalyserById(analyze, 2 ** (fft + 5)) + const analyserSend = effectSend(post, analyserNode, 1) + audioNodes.push(analyserSend) } if (dry != null) { - dry = applyGainCurve(dry); - const dryGain = new GainNode(ac, { gain: dry }); - chain.push(dryGain); - orbitBus.connectToOutput(dryGain); + dry = applyGainCurve(dry) + const dryGain = new GainNode(ac, { gain: dry }) + chain.push(dryGain) + orbitBus.connectToOutput(dryGain) } else { - orbitBus.connectToOutput(post); + orbitBus.connectToOutput(post) } // connect chain elements together - chain.slice(1).reduce((last, current) => last.connect(current), chain[0]); - audioNodes = audioNodes.concat(chain); -}; + chain.slice(1).reduce((last, current) => last.connect(current), chain[0]) + audioNodes = audioNodes.concat(chain) +} export const superdoughTrigger = (t, hap, ct, cps) => { - superdough(hap, t - ct, hap.duration / cps, cps); -}; + superdough(hap, t - ct, hap.duration / cps, cps) +} diff --git a/src/strudel/superdough/superdoughoutput.mjs b/src/strudel/superdough/superdoughoutput.mjs index afe91e7..63fd1f4 100644 --- a/src/strudel/superdough/superdoughoutput.mjs +++ b/src/strudel/superdough/superdoughoutput.mjs @@ -1,62 +1,80 @@ -import { effectSend, getWorklet, webAudioTimeout } from './helpers.mjs'; -import { errorLogger } from './logger.mjs'; -import { clamp } from './util.mjs'; +import { effectSend, getWorklet, webAudioTimeout } from './helpers.mjs' +import { errorLogger } from './logger.mjs' +import { clamp } from './util.mjs' -let hasChanged = (now, before) => now !== undefined && now !== before; +let hasChanged = (now, before) => now !== undefined && now !== before export class Orbit { - reverbNode; - delayNode; - output; - summingNode; - djfNode; - audioContext; + reverbNode + delayNode + output + summingNode + djfNode + audioContext constructor(audioContext) { - this.audioContext = audioContext; - this.output = new GainNode(audioContext, { gain: 1, channelCount: 2, channelCountMode: 'explicit' }); - this.summingNode = new GainNode(audioContext, { gain: 1, channelCount: 2, channelCountMode: 'explicit' }); - this.summingNode.connect(this.output); + this.audioContext = audioContext + this.output = new GainNode(audioContext, { + gain: 1, + channelCount: 2, + channelCountMode: 'explicit', + }) + this.summingNode = new GainNode(audioContext, { + gain: 1, + channelCount: 2, + channelCountMode: 'explicit', + }) + this.summingNode.connect(this.output) } disconnect() { - this.output.disconnect(); - this.summingNode.disconnect(); - this.delayNode?.disconnect(); - this.reverbNode?.disconnect(); + this.output.disconnect() + this.summingNode.disconnect() + this.delayNode?.disconnect() + this.reverbNode?.disconnect() } getDjf(value, t = 0) { if (this.djfNode == null) { - this.djfNode = getWorklet(this.audioContext, 'djf-processor', { value }); - this.summingNode.disconnect(); - this.summingNode.connect(this.djfNode); - this.djfNode.connect(this.output); + this.djfNode = getWorklet(this.audioContext, 'djf-processor', { value }) + this.summingNode.disconnect() + this.summingNode.connect(this.djfNode) + this.djfNode.connect(this.output) } - const val = this.djfNode.parameters.get('value'); - val.setValueAtTime(value, t); + const val = this.djfNode.parameters.get('value') + val.setValueAtTime(value, t) } getDelay(delaytime = 0, feedback = 0.5, t) { - const maxfeedback = 0.98; + const maxfeedback = 0.98 if (feedback > maxfeedback) { //logger(`feedback was clamped to ${maxfeedback} to save your ears`); } - feedback = clamp(feedback, 0, 0.98); + feedback = clamp(feedback, 0, 0.98) if (this.delayNode == null) { - this.delayNode = this.audioContext.createFeedbackDelay(1, delaytime, feedback); - this.delayNode.connect(this.summingNode); - this.delayNode.start?.(t); // for some reason, this throws when audion extension is installed.. + this.delayNode = this.audioContext.createFeedbackDelay(1, delaytime, feedback) + this.delayNode.connect(this.summingNode) + this.delayNode.start?.(t) // for some reason, this throws when audion extension is installed.. } - this.delayNode.delayTime.value !== delaytime && this.delayNode.delayTime.setValueAtTime(delaytime, t); - this.delayNode.feedback.value !== feedback && this.delayNode.feedback.setValueAtTime(feedback, t); - return this.delayNode; + this.delayNode.delayTime.value !== delaytime && + this.delayNode.delayTime.setValueAtTime(delaytime, t) + this.delayNode.feedback.value !== feedback && + this.delayNode.feedback.setValueAtTime(feedback, t) + return this.delayNode } getReverb(duration, fade, lp, dim, ir, irspeed, irbegin) { // If no reverb has been created for a given orbit, create one if (this.reverbNode == null) { - this.reverbNode = this.audioContext.createReverb(duration, fade, lp, dim, ir, irspeed, irbegin); - this.reverbNode.connect(this.summingNode); + this.reverbNode = this.audioContext.createReverb( + duration, + fade, + lp, + dim, + ir, + irspeed, + irbegin + ) + this.reverbNode.connect(this.summingNode) } if ( @@ -73,137 +91,143 @@ export class Orbit { // stack(s("a"), s("b").rsize(8)).room(.5) // this only works when args may stay undefined until here // setting default values breaks this - this.reverbNode.generate(duration, fade, lp, dim, ir, irspeed, irbegin); + this.reverbNode.generate(duration, fade, lp, dim, ir, irspeed, irbegin) } - return this.reverbNode; + return this.reverbNode } sendReverb(node, amount) { - effectSend(node, this.reverbNode, amount); + effectSend(node, this.reverbNode, amount) } sendDelay(node, amount) { - effectSend(node, this.delayNode, amount); + effectSend(node, this.delayNode, amount) } duck(t, onsettime = 0, attacktime = 0.1, depth = 1) { - const onset = onsettime; - const attack = Math.max(attacktime, 0.002); - const gainParam = this.output.gain; + const onset = onsettime + const attack = Math.max(attacktime, 0.002) + const gainParam = this.output.gain webAudioTimeout( this.audioContext, () => { - const now = this.audioContext.currentTime; + const now = this.audioContext.currentTime // cancelScheduledValues and setValueAtTime together emulate cancelAndHoldAtTime // on browsers which lack that method - const currVal = gainParam.value; - gainParam.cancelScheduledValues(now); - gainParam.setValueAtTime(currVal, now); + const currVal = gainParam.value + gainParam.cancelScheduledValues(now) + gainParam.setValueAtTime(currVal, now) - const t0 = Math.max(t, now); // guard against now > t - const duckedVal = clamp(1 - Math.sqrt(depth), 0.01, currVal); - gainParam.exponentialRampToValueAtTime(duckedVal, t0 + onset); - gainParam.exponentialRampToValueAtTime(1, t0 + onset + attack); + const t0 = Math.max(t, now) // guard against now > t + const duckedVal = clamp(1 - Math.sqrt(depth), 0.01, currVal) + gainParam.exponentialRampToValueAtTime(duckedVal, t0 + onset) + gainParam.exponentialRampToValueAtTime(1, t0 + onset + attack) }, 0, - t - 0.01, - ); + t - 0.01 + ) } connectToOutput(node) { - node.connect(this.summingNode); + node.connect(this.summingNode) } } export class SuperdoughOutput { - channelMerger; - destinationGain; + channelMerger + destinationGain constructor(audioContext) { - this.audioContext = audioContext; - this.initializeAudio(); + this.audioContext = audioContext + this.initializeAudio() } initializeAudio() { - const audioContext = this.audioContext; - const maxChannelCount = audioContext.destination.maxChannelCount; - this.audioContext.destination.channelCount = maxChannelCount; - this.channelMerger = new ChannelMergerNode(audioContext, { numberOfInputs: audioContext.destination.channelCount }); - this.destinationGain = new GainNode(audioContext); - this.channelMerger.connect(this.destinationGain); - this.destinationGain.connect(audioContext.destination); + const audioContext = this.audioContext + const maxChannelCount = audioContext.destination.maxChannelCount + this.audioContext.destination.channelCount = maxChannelCount + this.channelMerger = new ChannelMergerNode(audioContext, { + numberOfInputs: audioContext.destination.channelCount, + }) + this.destinationGain = new GainNode(audioContext) + this.channelMerger.connect(this.destinationGain) + this.destinationGain.connect(audioContext.destination) } reset() { - this.disconnect(); - this.initializeAudio(); + this.disconnect() + this.initializeAudio() } disconnect() { - this.channelMerger.disconnect(); - this.destinationGain.disconnect(); - this.destinationGain = null; - this.channelMerger = null; + this.channelMerger.disconnect() + this.destinationGain.disconnect() + this.destinationGain = null + this.channelMerger = null } connectToDestination = (input, channels = [0, 1]) => { //This upmix can be removed if correct channel counts are set throughout the app, // and then strudel could theoretically support surround sound audio files - const stereoMix = new StereoPannerNode(this.audioContext); - input.connect(stereoMix); + const stereoMix = new StereoPannerNode(this.audioContext) + input.connect(stereoMix) const splitter = new ChannelSplitterNode(this.audioContext, { numberOfOutputs: stereoMix.channelCount, - }); - stereoMix.connect(splitter); + }) + stereoMix.connect(splitter) channels.forEach((ch, i) => { - splitter.connect(this.channelMerger, i % stereoMix.channelCount, ch % this.audioContext.destination.channelCount); - }); - }; + splitter.connect( + this.channelMerger, + i % stereoMix.channelCount, + ch % this.audioContext.destination.channelCount + ) + }) + } } export class SuperdoughAudioController { - audioContext; - output; - nodes = {}; + audioContext + output + nodes = {} constructor(audioContext) { - this.audioContext = audioContext; - this.output = new SuperdoughOutput(audioContext); + this.audioContext = audioContext + this.output = new SuperdoughOutput(audioContext) } reset() { Array.from(this.nodes).forEach((node) => { - node.disconnect(); - }); - this.nodes = {}; - this.output.reset(); + node.disconnect() + }) + this.nodes = {} + this.output.reset() } duck(targetOrbits, t, onsettime = 0, attacktime = 0.1, depth = 1) { - const targetArr = [targetOrbits].flat(); - const onsetArr = [onsettime].flat(); - const attackArr = [attacktime].flat(); - const depthArr = [depth].flat(); + const targetArr = [targetOrbits].flat() + const onsetArr = [onsettime].flat() + const attackArr = [attacktime].flat() + const depthArr = [depth].flat() targetArr.forEach((target, idx) => { - const orbit = this.nodes[target]; + const orbit = this.nodes[target] if (orbit == null) { - errorLogger(new Error(`duck target orbit ${target} does not exist`), 'superdough'); - return; + errorLogger(new Error(`duck target orbit ${target} does not exist`), 'superdough') + return } - const onset = onsetArr[idx] ?? onsetArr[0]; - const attack = Math.max(attackArr[idx] ?? attackArr[0], 0.002); - const depth = depthArr[idx] ?? depthArr[0]; + const onset = onsetArr[idx] ?? onsetArr[0] + const attack = Math.max(attackArr[idx] ?? attackArr[0], 0.002) + const depth = depthArr[idx] ?? depthArr[0] - orbit.duck(t, onset, attack, depth); - }); + orbit.duck(t, onset, attack, depth) + }) } getOrbit(orbitNum, channels) { if (this.nodes[orbitNum] == null) { - this.nodes[orbitNum] = new Orbit(this.audioContext); - this.output.connectToDestination(this.nodes[orbitNum].output, channels); + this.nodes[orbitNum] = new Orbit(this.audioContext) + this.output.connectToDestination(this.nodes[orbitNum].output, channels) } - return this.nodes[orbitNum]; + return this.nodes[orbitNum] } } diff --git a/src/strudel/superdough/synth.mjs b/src/strudel/superdough/synth.mjs index e35b988..e0dff09 100644 --- a/src/strudel/superdough/synth.mjs +++ b/src/strudel/superdough/synth.mjs @@ -1,6 +1,6 @@ -import { clamp } from './util.mjs'; -import { registerSound, soundMap } from './superdough.mjs'; -import { getAudioContext } from './audioContext.mjs'; +import { clamp } from './util.mjs' +import { registerSound, soundMap } from './superdough.mjs' +import { getAudioContext } from './audioContext.mjs' import { applyFM, destroyAudioWorkletNode, @@ -14,157 +14,157 @@ import { getWorklet, noises, webAudioTimeout, -} from './helpers.mjs'; -import { getNoiseMix, getNoiseOscillator } from './noise.mjs'; +} from './helpers.mjs' +import { getNoiseMix, getNoiseOscillator } from './noise.mjs' -const waveforms = ['triangle', 'square', 'sawtooth', 'sine']; +const waveforms = ['triangle', 'square', 'sawtooth', 'sine'] const waveformAliases = [ ['tri', 'triangle'], ['sqr', 'square'], ['saw', 'sawtooth'], ['sin', 'sine'], -]; +] function makeSaturationCurve(amount, n_samples) { - const k = typeof amount === 'number' ? amount : 50; - const curve = new Float32Array(n_samples); + const k = typeof amount === 'number' ? amount : 50 + const curve = new Float32Array(n_samples) for (let i = 0; i < n_samples; i++) { - const x = (i * 2) / n_samples - 1; - curve[i] = Math.tanh(x * k); + const x = (i * 2) / n_samples - 1 + curve[i] = Math.tanh(x * k) } - return curve; + return curve } export function registerSynthSounds() { - [...waveforms].forEach((s) => { + ;[...waveforms].forEach((s) => { registerSound( s, (t, value, onended) => { const [attack, decay, sustain, release] = getADSRValues( [value.attack, value.decay, value.sustain, value.release], 'linear', - [0.001, 0.05, 0.6, 0.01], - ); + [0.001, 0.05, 0.6, 0.01] + ) - let sound = getOscillator(s, t, value); - let { node: o, stop, triggerRelease } = sound; + let sound = getOscillator(s, t, value) + let { node: o, stop, triggerRelease } = sound // turn down - const g = gainNode(0.3); + const g = gainNode(0.3) - const { duration } = value; + const { duration } = value o.onended = () => { - o.disconnect(); - g.disconnect(); - onended(); - }; + o.disconnect() + g.disconnect() + onended() + } - const envGain = gainNode(1); - let node = o.connect(g).connect(envGain); - const holdEnd = t + duration; - getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear'); - const envEnd = holdEnd + release + 0.01; - triggerRelease?.(envEnd); - stop(envEnd); + const envGain = gainNode(1) + let node = o.connect(g).connect(envGain) + const holdEnd = t + duration + getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear') + const envEnd = holdEnd + release + 0.01 + triggerRelease?.(envEnd) + stop(envEnd) return { node, stop: (endTime) => { - stop(endTime); + stop(endTime) }, - }; + } }, - { type: 'synth', prebake: true }, - ); - }); + { type: 'synth', prebake: true } + ) + }) registerSound( 'sbd', (t, value, onended) => { - const { duration, decay = 0.5, pdecay = 0.5, penv = 36, clip } = value; - const ctx = getAudioContext(); - const attackhold = 0.02; - const noiselvl = 1.2; - const noisedecay = 0.025; - const mixGain = 1; + const { duration, decay = 0.5, pdecay = 0.5, penv = 36, clip } = value + const ctx = getAudioContext() + const attackhold = 0.02 + const noiselvl = 1.2 + const noisedecay = 0.025 + const mixGain = 1 - const o = ctx.createOscillator(); - o.type = 'triangle'; - o.frequency.value = getFrequencyFromValue(value, 29); - o.detune.setValueAtTime(penv * 100, 0); - o.detune.setValueAtTime(penv * 100, t); - o.detune.exponentialRampToValueAtTime(0.001, t + pdecay); - const g = gainNode(1); - g.gain.setValueAtTime(1, t + attackhold); - g.gain.exponentialRampToValueAtTime(0.001, t + attackhold + decay); - o.start(t); + const o = ctx.createOscillator() + o.type = 'triangle' + o.frequency.value = getFrequencyFromValue(value, 29) + o.detune.setValueAtTime(penv * 100, 0) + o.detune.setValueAtTime(penv * 100, t) + o.detune.exponentialRampToValueAtTime(0.001, t + pdecay) + const g = gainNode(1) + g.gain.setValueAtTime(1, t + attackhold) + g.gain.exponentialRampToValueAtTime(0.001, t + attackhold + decay) + o.start(t) - const noise = getNoiseOscillator('brown', t, 2); - const noiseGain = gainNode(1); - noiseGain.gain.setValueAtTime(noiselvl, t); - noiseGain.gain.exponentialRampToValueAtTime(0.001, t + noisedecay); + const noise = getNoiseOscillator('brown', t, 2) + const noiseGain = gainNode(1) + noiseGain.gain.setValueAtTime(noiselvl, t) + noiseGain.gain.exponentialRampToValueAtTime(0.001, t + noisedecay) - const sat = new WaveShaperNode(ctx); + const sat = new WaveShaperNode(ctx) // tri to sine diode shaper emulation - sat.curve = makeSaturationCurve(2, ctx.sampleRate); + sat.curve = makeSaturationCurve(2, ctx.sampleRate) - const mix = gainNode(mixGain); + const mix = gainNode(mixGain) o.onended = () => { - o.disconnect(); - g.disconnect(); - sat.disconnect(); - noise.node.disconnect(); - noiseGain.disconnect(); - mix.disconnect(); - onended(); - }; + o.disconnect() + g.disconnect() + sat.disconnect() + noise.node.disconnect() + noiseGain.disconnect() + mix.disconnect() + onended() + } - const node = o.connect(sat).connect(g).connect(mix); - noise.node.connect(noiseGain).connect(mix); + const node = o.connect(sat).connect(g).connect(mix) + noise.node.connect(noiseGain).connect(mix) - const holdEnd = t + decay; - let end = holdEnd + 0.01; + const holdEnd = t + decay + let end = holdEnd + 0.01 if (clip != null) { - end = Math.min(t + clip * duration, end); + end = Math.min(t + clip * duration, end) } // prevent clicking - mix.gain.setValueAtTime(mixGain, end - 0.01); - mix.gain.linearRampToValueAtTime(0, end); + mix.gain.setValueAtTime(mixGain, end - 0.01) + mix.gain.linearRampToValueAtTime(0, end) - o.stop(end); - noise.stop(end); + o.stop(end) + noise.stop(end) return { node, stop: (endTime) => { - o.stop(endTime); + o.stop(endTime) }, - }; + } }, - { type: 'synth', prebake: true }, - ); + { type: 'synth', prebake: true } + ) registerSound( 'supersaw', (begin, value, onended) => { - const ac = getAudioContext(); - let { duration, n, unison = 5, spread = 0.6, detune } = value; - detune = detune ?? n ?? 0.18; - const frequency = getFrequencyFromValue(value); + const ac = getAudioContext() + let { duration, n, unison = 5, spread = 0.6, detune } = value + detune = detune ?? n ?? 0.18 + const frequency = getFrequencyFromValue(value) const [attack, decay, sustain, release] = getADSRValues( [value.attack, value.decay, value.sustain, value.release], 'linear', - [0.001, 0.05, 0.6, 0.01], - ); + [0.001, 0.05, 0.6, 0.01] + ) - const holdend = begin + duration; - const end = holdend + release + 0.01; - const voices = clamp(unison, 1, 100); - let panspread = voices > 1 ? clamp(spread, 0, 1) : 0; + const holdend = begin + duration + const end = holdend + release + 0.01 + const voices = clamp(unison, 1, 100) + let panspread = voices > 1 ? clamp(spread, 0, 1) : 0 let o = getWorklet( ac, 'supersaw-oscillator', @@ -178,40 +178,51 @@ export function registerSynthSounds() { }, { outputChannelCount: [2], - }, - ); + } + ) - const gainAdjustment = 1 / Math.sqrt(voices); - getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend); - const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin); - const fm = applyFM(o.parameters.get('frequency'), value, begin); - let envGain = gainNode(1); - envGain = o.connect(envGain); + const gainAdjustment = 1 / Math.sqrt(voices) + getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend) + const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin) + const fm = applyFM(o.parameters.get('frequency'), value, begin) + let envGain = gainNode(1) + envGain = o.connect(envGain) - getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 0.3 * gainAdjustment, begin, holdend, 'linear'); + getParamADSR( + envGain.gain, + attack, + decay, + sustain, + release, + 0, + 0.3 * gainAdjustment, + begin, + holdend, + 'linear' + ) let timeoutNode = webAudioTimeout( ac, () => { - destroyAudioWorkletNode(o); - envGain.disconnect(); - onended(); - fm?.stop(); - vibratoOscillator?.stop(); + destroyAudioWorkletNode(o) + envGain.disconnect() + onended() + fm?.stop() + vibratoOscillator?.stop() }, begin, - end, - ); + end + ) return { node: envGain, stop: (time) => { - timeoutNode.stop(time); + timeoutNode.stop(time) }, - }; + } }, - { prebake: true, type: 'synth' }, - ); + { prebake: true, type: 'synth' } + ) registerSound( 'bytebeat', @@ -232,21 +243,22 @@ export function registerSynthSounds() { '((t^t/2+t+64)%128*2)', '(((t * .25)^(t * .25)/100+(t * .25))%128)*2', '((t^t/2+t+64)%7 * 24)', - ]; - const { n = 0 } = value; - const frequency = getFrequencyFromValue(value); - const { byteBeatExpression = defaultBeats[n % defaultBeats.length], byteBeatStartTime } = value; + ] + const { n = 0 } = value + const frequency = getFrequencyFromValue(value) + const { byteBeatExpression = defaultBeats[n % defaultBeats.length], byteBeatStartTime } = + value - const ac = getAudioContext(); + const ac = getAudioContext() - let { duration } = value; + let { duration } = value const [attack, decay, sustain, release] = getADSRValues( [value.attack, value.decay, value.sustain, value.release], 'linear', - [0.001, 0.05, 0.6, 0.01], - ); - const holdend = begin + duration; - const end = holdend + release + 0.01; + [0.001, 0.05, 0.6, 0.01] + ) + const holdend = begin + duration + const end = holdend + release + 0.01 let o = getWorklet( ac, @@ -258,64 +270,64 @@ export function registerSynthSounds() { }, { outputChannelCount: [2], - }, - ); + } + ) - o.port.postMessage({ codeText: byteBeatExpression, byteBeatStartTime, frequency }); + o.port.postMessage({ codeText: byteBeatExpression, byteBeatStartTime, frequency }) - let envGain = gainNode(1); - envGain = o.connect(envGain); + let envGain = gainNode(1) + envGain = o.connect(envGain) - getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear'); + getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear') let timeoutNode = webAudioTimeout( ac, () => { - destroyAudioWorkletNode(o); - envGain.disconnect(); - onended(); + destroyAudioWorkletNode(o) + envGain.disconnect() + onended() }, begin, - end, - ); + end + ) return { node: envGain, stop: (time) => { - timeoutNode.stop(time); + timeoutNode.stop(time) }, - }; + } }, - { prebake: true, type: 'synth' }, - ); + { prebake: true, type: 'synth' } + ) registerSound( 'pulse', (begin, value, onended) => { - const ac = getAudioContext(); - let { pwrate, pwsweep } = value; + const ac = getAudioContext() + let { pwrate, pwsweep } = value if (pwsweep == null) { if (pwrate != null) { - pwsweep = 0.3; + pwsweep = 0.3 } else { - pwsweep = 0; + pwsweep = 0 } } if (pwrate == null && pwsweep != null) { - pwrate = 1; + pwrate = 1 } - let { duration, pw: pulsewidth = 0.5 } = value; - const frequency = getFrequencyFromValue(value); + let { duration, pw: pulsewidth = 0.5 } = value + const frequency = getFrequencyFromValue(value) const [attack, decay, sustain, release] = getADSRValues( [value.attack, value.decay, value.sustain, value.release], 'linear', - [0.001, 0.05, 0.6, 0.01], - ); - const holdend = begin + duration; - const end = holdend + release + 0.01; + [0.001, 0.05, 0.6, 0.01] + ) + const holdend = begin + duration + const end = holdend + release + 0.01 let o = getWorklet( ac, 'pulse-oscillator', @@ -327,162 +339,164 @@ export function registerSynthSounds() { }, { outputChannelCount: [2], - }, - ); + } + ) - getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend); - const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin); - const fm = applyFM(o.parameters.get('frequency'), value, begin); - let envGain = gainNode(1); - envGain = o.connect(envGain); + getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend) + const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin) + const fm = applyFM(o.parameters.get('frequency'), value, begin) + let envGain = gainNode(1) + envGain = o.connect(envGain) - getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear'); - let lfo; + getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear') + let lfo if (pwsweep != 0) { - lfo = getLfo(ac, begin, end, { frequency: pwrate, depth: pwsweep }); - lfo.connect(o.parameters.get('pulsewidth')); + lfo = getLfo(ac, begin, end, { frequency: pwrate, depth: pwsweep }) + lfo.connect(o.parameters.get('pulsewidth')) } let timeoutNode = webAudioTimeout( ac, () => { - destroyAudioWorkletNode(o); - destroyAudioWorkletNode(lfo); - envGain.disconnect(); - onended(); - fm?.stop(); - vibratoOscillator?.stop(); + destroyAudioWorkletNode(o) + destroyAudioWorkletNode(lfo) + envGain.disconnect() + onended() + fm?.stop() + vibratoOscillator?.stop() }, begin, - end, - ); + end + ) return { node: envGain, stop: (time) => { - timeoutNode.stop(time); + timeoutNode.stop(time) }, - }; + } }, - { prebake: true, type: 'synth' }, - ); + { prebake: true, type: 'synth' } + ) - [...noises].forEach((s) => { + ;[...noises].forEach((s) => { registerSound( s, (t, value, onended) => { const [attack, decay, sustain, release] = getADSRValues( [value.attack, value.decay, value.sustain, value.release], 'linear', - [0.001, 0.05, 0.6, 0.01], - ); + [0.001, 0.05, 0.6, 0.01] + ) - let sound; + let sound - let { density } = value; - sound = getNoiseOscillator(s, t, density); + let { density } = value + sound = getNoiseOscillator(s, t, density) - let { node: o, stop, triggerRelease } = sound; + let { node: o, stop, triggerRelease } = sound // turn down - const g = gainNode(0.3); + const g = gainNode(0.3) - const { duration } = value; + const { duration } = value o.onended = () => { - o.disconnect(); - g.disconnect(); - onended(); - }; + o.disconnect() + g.disconnect() + onended() + } - const envGain = gainNode(1); - let node = o.connect(g).connect(envGain); - const holdEnd = t + duration; - getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear'); - const envEnd = holdEnd + release + 0.01; - triggerRelease?.(envEnd); - stop(envEnd); + const envGain = gainNode(1) + let node = o.connect(g).connect(envGain) + const holdEnd = t + duration + getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear') + const envEnd = holdEnd + release + 0.01 + triggerRelease?.(envEnd) + stop(envEnd) return { node, stop: (endTime) => { - stop(endTime); + stop(endTime) }, - }; + } }, - { type: 'synth', prebake: true }, - ); - }); - waveformAliases.forEach(([alias, actual]) => soundMap.set({ ...soundMap.get(), [alias]: soundMap.get()[actual] })); + { type: 'synth', prebake: true } + ) + }) + waveformAliases.forEach(([alias, actual]) => + soundMap.set({ ...soundMap.get(), [alias]: soundMap.get()[actual] }) + ) } export function waveformN(partials, type) { - const real = new Float32Array(partials + 1); - const imag = new Float32Array(partials + 1); - const ac = getAudioContext(); - const osc = ac.createOscillator(); + const real = new Float32Array(partials + 1) + const imag = new Float32Array(partials + 1) + const ac = getAudioContext() + const osc = ac.createOscillator() const terms = { sawtooth: (n) => [0, -1 / n], square: (n) => [0, n % 2 === 0 ? 0 : 1 / n], triangle: (n) => [n % 2 === 0 ? 0 : 1 / (n * n), 0], - }; + } if (!terms[type]) { - throw new Error(`unknown wave type ${type}`); + throw new Error(`unknown wave type ${type}`) } - real[0] = 0; // dc offset - imag[0] = 0; - let n = 1; + real[0] = 0 // dc offset + imag[0] = 0 + let n = 1 while (n <= partials) { - const [r, i] = terms[type](n); - real[n] = r; - imag[n] = i; - n++; + const [r, i] = terms[type](n) + real[n] = r + imag[n] = i + n++ } - const wave = ac.createPeriodicWave(real, imag); - osc.setPeriodicWave(wave); - return osc; + const wave = ac.createPeriodicWave(real, imag) + osc.setPeriodicWave(wave) + return osc } // expects one of waveforms as s export function getOscillator(s, t, value) { - let { n: partials, duration, noise = 0 } = value; - let o; + let { n: partials, duration, noise = 0 } = value + let o // If no partials are given, use stock waveforms if (!partials || s === 'sine') { - o = getAudioContext().createOscillator(); - o.type = s || 'triangle'; + o = getAudioContext().createOscillator() + o.type = s || 'triangle' } // generate custom waveform if partials are given else { - o = waveformN(partials, s); + o = waveformN(partials, s) } // set frequency - o.frequency.value = getFrequencyFromValue(value); - o.start(t); + o.frequency.value = getFrequencyFromValue(value) + o.start(t) - let vibratoOscillator = getVibratoOscillator(o.detune, value, t); + let vibratoOscillator = getVibratoOscillator(o.detune, value, t) // pitch envelope - getPitchEnvelope(o.detune, value, t, t + duration); - const fmModulator = applyFM(o.frequency, value, t); + getPitchEnvelope(o.detune, value, t, t + duration) + const fmModulator = applyFM(o.frequency, value, t) - let noiseMix; + let noiseMix if (noise) { - noiseMix = getNoiseMix(o, noise, t); + noiseMix = getNoiseMix(o, noise, t) } return { node: noiseMix?.node || o, stop: (time) => { - fmModulator.stop(time); - vibratoOscillator?.stop(time); - noiseMix?.stop(time); - o.stop(time); + fmModulator.stop(time) + vibratoOscillator?.stop(time) + noiseMix?.stop(time) + o.stop(time) }, triggerRelease: (time) => { // envGain?.stop(time); }, - }; + } } diff --git a/src/strudel/superdough/util.mjs b/src/strudel/superdough/util.mjs index 475c05f..1b79714 100644 --- a/src/strudel/superdough/util.mjs +++ b/src/strudel/superdough/util.mjs @@ -1,107 +1,108 @@ -import { logger } from './logger.mjs'; +import { logger } from './logger.mjs' // currently duplicate with core util.mjs to skip dependency // TODO: add separate util module? export const tokenizeNote = (note) => { if (typeof note !== 'string') { - return []; + return [] } - const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)(-?[0-9]*)$/)?.slice(1) || []; + const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)(-?[0-9]*)$/)?.slice(1) || [] if (!pc) { - return []; + return [] } - return [pc, acc, oct ? Number(oct) : undefined]; -}; -const chromas = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }; -const accs = { '#': 1, b: -1, s: 1, f: -1 }; + return [pc, acc, oct ? Number(oct) : undefined] +} +const chromas = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 } +const accs = { '#': 1, b: -1, s: 1, f: -1 } export const noteToMidi = (note, defaultOctave = 3) => { - const [pc, acc, oct = defaultOctave] = tokenizeNote(note); + const [pc, acc, oct = defaultOctave] = tokenizeNote(note) if (!pc) { - throw new Error('not a note: "' + note + '"'); + throw new Error('not a note: "' + note + '"') } - const chroma = chromas[pc.toLowerCase()]; - const offset = acc?.split('').reduce((o, char) => o + accs[char], 0) || 0; - return (Number(oct) + 1) * 12 + chroma + offset; -}; + const chroma = chromas[pc.toLowerCase()] + const offset = acc?.split('').reduce((o, char) => o + accs[char], 0) || 0 + return (Number(oct) + 1) * 12 + chroma + offset +} export const midiToFreq = (n) => { - return Math.pow(2, (n - 69) / 12) * 440; -}; -export const clamp = (num, min, max) => Math.min(Math.max(num, min), max); + return Math.pow(2, (n - 69) / 12) * 440 +} +export const clamp = (num, min, max) => Math.min(Math.max(num, min), max) export const freqToMidi = (freq) => { - return (12 * Math.log(freq / 440)) / Math.LN2 + 69; -}; + return (12 * Math.log(freq / 440)) / Math.LN2 + 69 +} export const valueToMidi = (value, fallbackValue) => { if (typeof value !== 'object') { - throw new Error('valueToMidi: expected object value'); + throw new Error('valueToMidi: expected object value') } - let { freq, note } = value; + let { freq, note } = value if (typeof freq === 'number') { - return freqToMidi(freq); + return freqToMidi(freq) } if (typeof note === 'string') { - return noteToMidi(note); + return noteToMidi(note) } if (typeof note === 'number') { - return note; + return note } if (!fallbackValue) { - throw new Error('valueToMidi: expected freq or note to be set'); + throw new Error('valueToMidi: expected freq or note to be set') } - return fallbackValue; -}; + return fallbackValue +} export function nanFallback(value, fallback = 0, silent) { if (isNaN(Number(value))) { - !silent && logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning'); - return fallback; + !silent && logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning') + return fallback } - return value; + return value } // modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does) -export const _mod = (n, m) => ((n % m) + m) % m; +export const _mod = (n, m) => ((n % m) + m) % m // round to nearest int, negative numbers will output a subtracted index export const getSoundIndex = (n, numSounds) => { - return _mod(Math.round(nanFallback(n, 0)), numSounds); -}; + return _mod(Math.round(nanFallback(n, 0)), numSounds) +} export function cycleToSeconds(cycle, cps) { - return cycle / cps; + return cycle / cps } export function secondsToCycle(t, cps) { - return t * cps; + return t * cps } // deduces relevant info for sample loading from hap.value and sample definition // it encapsulates the core sampler logic into a pure and synchronous function // hapValue: Hap.value, bank: sample bank definition for sound "s" (values in strudel.json format) export function getCommonSampleInfo(hapValue, bank) { - const { s, n = 0 } = hapValue; - let midi = valueToMidi(hapValue, 36); - let transpose = midi - 36; // C3 is middle C; - let url; - let index = 0; + const { s, n = 0 } = hapValue + let midi = valueToMidi(hapValue, 36) + let transpose = midi - 36 // C3 is middle C; + let url + let index = 0 if (Array.isArray(bank)) { - index = getSoundIndex(n, bank.length); - url = bank[index]; + index = getSoundIndex(n, bank.length) + url = bank[index] } else { - const midiDiff = (noteA) => noteToMidi(noteA) - midi; + const midiDiff = (noteA) => noteToMidi(noteA) - midi // object format will expect keys as notes const closest = Object.keys(bank) .filter((k) => !k.startsWith('_')) .reduce( - (closest, key, j) => (!closest || Math.abs(midiDiff(key)) < Math.abs(midiDiff(closest)) ? key : closest), - null, - ); - transpose = -midiDiff(closest); // semitones to repitch - index = getSoundIndex(n, bank[closest].length); - url = bank[closest][index]; + (closest, key, j) => + !closest || Math.abs(midiDiff(key)) < Math.abs(midiDiff(closest)) ? key : closest, + null + ) + transpose = -midiDiff(closest) // semitones to repitch + index = getSoundIndex(n, bank[closest].length) + url = bank[closest][index] } - const label = `${s}:${index}`; - return { transpose, url, index, midi, label }; + const label = `${s}:${index}` + return { transpose, url, index, midi, label } } diff --git a/src/strudel/superdough/vite.config.js b/src/strudel/superdough/vite.config.js deleted file mode 100644 index 10ac0f0..0000000 --- a/src/strudel/superdough/vite.config.js +++ /dev/null @@ -1,20 +0,0 @@ -import { defineConfig } from 'vite'; -import { dependencies } from './package.json'; -import { resolve } from 'path'; -import bundleAudioWorkletPlugin from 'vite-plugin-bundle-audioworklet'; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [bundleAudioWorkletPlugin()], - build: { - lib: { - entry: resolve(__dirname, 'index.mjs'), - formats: ['es'], - fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.cjs' })[ext], - }, - rollupOptions: { - external: [...Object.keys(dependencies)], - }, - target: 'esnext', - }, -}); diff --git a/src/strudel/superdough/vowel.mjs b/src/strudel/superdough/vowel.mjs index 3f30aef..70280d6 100644 --- a/src/strudel/superdough/vowel.mjs +++ b/src/strudel/superdough/vowel.mjs @@ -1,69 +1,129 @@ // credits to webdirt: https://github.com/dktr0/WebDirt/blob/41342e81d6ad694a2310d491fef7b7e8b0929efe/js-src/Graph.js#L597 export var vowelFormant = { - a: { freqs: [660, 1120, 2750, 3000, 3350], gains: [1, 0.5012, 0.0708, 0.0631, 0.0126], qs: [80, 90, 120, 130, 140] }, - e: { freqs: [440, 1800, 2700, 3000, 3300], gains: [1, 0.1995, 0.1259, 0.1, 0.1], qs: [70, 80, 100, 120, 120] }, - i: { freqs: [270, 1850, 2900, 3350, 3590], gains: [1, 0.0631, 0.0631, 0.0158, 0.0158], qs: [40, 90, 100, 120, 120] }, - o: { freqs: [430, 820, 2700, 3000, 3300], gains: [1, 0.3162, 0.0501, 0.0794, 0.01995], qs: [40, 80, 100, 120, 120] }, - u: { freqs: [370, 630, 2750, 3000, 3400], gains: [1, 0.1, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] }, - ae: { freqs: [650, 1515, 2400, 3000, 3350], gains: [1, 0.5, 0.1008, 0.0631, 0.0126], qs: [80, 90, 120, 130, 140] }, - aa: { freqs: [560, 900, 2570, 3000, 3300], gains: [1, 0.5, 0.0708, 0.0631, 0.0126], qs: [80, 90, 120, 130, 140] }, - oe: { freqs: [500, 1430, 2300, 3000, 3300], gains: [1, 0.2, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] }, - ue: { freqs: [250, 1750, 2150, 3200, 3300], gains: [1, 0.1, 0.0708, 0.0316, 0.01995], qs: [40, 60, 100, 120, 120] }, - y: { freqs: [400, 1460, 2400, 3000, 3300], gains: [1, 0.2, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] }, - uh: { freqs: [600, 1250, 2100, 3100, 3500], gains: [1, 0.3, 0.0608, 0.0316, 0.01995], qs: [40, 70, 100, 120, 130] }, - un: { freqs: [500, 1240, 2280, 3000, 3500], gains: [1, 0.1, 0.1708, 0.0216, 0.02995], qs: [40, 60, 100, 120, 120] }, - en: { freqs: [600, 1480, 2450, 3200, 3300], gains: [1, 0.15, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] }, - an: { freqs: [700, 1050, 2500, 3000, 3300], gains: [1, 0.1, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] }, - on: { freqs: [500, 1080, 2350, 3000, 3300], gains: [1, 0.1, 0.0708, 0.0316, 0.02995], qs: [40, 60, 100, 120, 120] }, + a: { + freqs: [660, 1120, 2750, 3000, 3350], + gains: [1, 0.5012, 0.0708, 0.0631, 0.0126], + qs: [80, 90, 120, 130, 140], + }, + e: { + freqs: [440, 1800, 2700, 3000, 3300], + gains: [1, 0.1995, 0.1259, 0.1, 0.1], + qs: [70, 80, 100, 120, 120], + }, + i: { + freqs: [270, 1850, 2900, 3350, 3590], + gains: [1, 0.0631, 0.0631, 0.0158, 0.0158], + qs: [40, 90, 100, 120, 120], + }, + o: { + freqs: [430, 820, 2700, 3000, 3300], + gains: [1, 0.3162, 0.0501, 0.0794, 0.01995], + qs: [40, 80, 100, 120, 120], + }, + u: { + freqs: [370, 630, 2750, 3000, 3400], + gains: [1, 0.1, 0.0708, 0.0316, 0.01995], + qs: [40, 60, 100, 120, 120], + }, + ae: { + freqs: [650, 1515, 2400, 3000, 3350], + gains: [1, 0.5, 0.1008, 0.0631, 0.0126], + qs: [80, 90, 120, 130, 140], + }, + aa: { + freqs: [560, 900, 2570, 3000, 3300], + gains: [1, 0.5, 0.0708, 0.0631, 0.0126], + qs: [80, 90, 120, 130, 140], + }, + oe: { + freqs: [500, 1430, 2300, 3000, 3300], + gains: [1, 0.2, 0.0708, 0.0316, 0.01995], + qs: [40, 60, 100, 120, 120], + }, + ue: { + freqs: [250, 1750, 2150, 3200, 3300], + gains: [1, 0.1, 0.0708, 0.0316, 0.01995], + qs: [40, 60, 100, 120, 120], + }, + y: { + freqs: [400, 1460, 2400, 3000, 3300], + gains: [1, 0.2, 0.0708, 0.0316, 0.02995], + qs: [40, 60, 100, 120, 120], + }, + uh: { + freqs: [600, 1250, 2100, 3100, 3500], + gains: [1, 0.3, 0.0608, 0.0316, 0.01995], + qs: [40, 70, 100, 120, 130], + }, + un: { + freqs: [500, 1240, 2280, 3000, 3500], + gains: [1, 0.1, 0.1708, 0.0216, 0.02995], + qs: [40, 60, 100, 120, 120], + }, + en: { + freqs: [600, 1480, 2450, 3200, 3300], + gains: [1, 0.15, 0.0708, 0.0316, 0.02995], + qs: [40, 60, 100, 120, 120], + }, + an: { + freqs: [700, 1050, 2500, 3000, 3300], + gains: [1, 0.1, 0.0708, 0.0316, 0.02995], + qs: [40, 60, 100, 120, 120], + }, + on: { + freqs: [500, 1080, 2350, 3000, 3300], + gains: [1, 0.1, 0.0708, 0.0316, 0.02995], + qs: [40, 60, 100, 120, 120], + }, get æ() { - return this.ae; + return this.ae }, get ø() { - return this.oe; + return this.oe }, get ɑ() { - return this.aa; + return this.aa }, get å() { - return this.aa; + return this.aa }, get ö() { - return this.oe; + return this.oe }, get ü() { - return this.ue; + return this.ue }, get ı() { - return this.y; + return this.y }, -}; +} if (typeof GainNode !== 'undefined') { class VowelNode extends GainNode { constructor(ac, letter) { - super(ac); + super(ac) if (!vowelFormant[letter]) { - throw new Error('vowel: unknown vowel ' + letter); + throw new Error('vowel: unknown vowel ' + letter) } - const { gains, qs, freqs } = vowelFormant[letter]; - const makeupGain = ac.createGain(); + const { gains, qs, freqs } = vowelFormant[letter] + const makeupGain = ac.createGain() for (let i = 0; i < 5; i++) { - const gain = ac.createGain(); - gain.gain.value = gains[i]; - const filter = ac.createBiquadFilter(); - filter.type = 'bandpass'; - filter.Q.value = qs[i]; - filter.frequency.value = freqs[i]; - this.connect(filter); - filter.connect(gain); - gain.connect(makeupGain); + const gain = ac.createGain() + gain.gain.value = gains[i] + const filter = ac.createBiquadFilter() + filter.type = 'bandpass' + filter.Q.value = qs[i] + filter.frequency.value = freqs[i] + this.connect(filter) + filter.connect(gain) + gain.connect(makeupGain) } - makeupGain.gain.value = 8; // how much makeup gain to add? - this.connect = (target) => makeupGain.connect(target); - return this; + makeupGain.gain.value = 8 // how much makeup gain to add? + this.connect = (target) => makeupGain.connect(target) + return this } } AudioContext.prototype.createVowelFilter = function (letter) { - return new VowelNode(this, letter); - }; + return new VowelNode(this, letter) + } } diff --git a/src/strudel/superdough/wavetable.mjs b/src/strudel/superdough/wavetable.mjs index 01d4eb8..90f04a4 100644 --- a/src/strudel/superdough/wavetable.mjs +++ b/src/strudel/superdough/wavetable.mjs @@ -1,5 +1,5 @@ -import { getAudioContext, registerSound } from './index.mjs'; -import { getCommonSampleInfo } from './util.mjs'; +import { getAudioContext, registerSound } from './index.mjs' +import { getCommonSampleInfo } from './util.mjs' import { applyFM, applyParameterModulators, @@ -11,8 +11,8 @@ import { getVibratoOscillator, getWorklet, webAudioTimeout, -} from './helpers.mjs'; -import { logger } from './logger.mjs'; +} from './helpers.mjs' +import { logger } from './logger.mjs' export const Warpmode = Object.freeze({ NONE: 0, @@ -37,144 +37,153 @@ export const Warpmode = Object.freeze({ SIGMOID: 19, FRACTAL: 20, FLIP: 21, -}); +}) -const seenKeys = new Set(); +const seenKeys = new Set() async function getPayload(url, label, frameLen = 2048) { - const key = `${url},${frameLen}`; + const key = `${url},${frameLen}` if (!seenKeys.has(key)) { - const buf = await loadBuffer(url, label); - const ch0 = buf.getChannelData(0); - const total = ch0.length; - const numFrames = Math.max(1, Math.floor(total / frameLen)); - const frames = new Array(numFrames); + const buf = await loadBuffer(url, label) + const ch0 = buf.getChannelData(0) + const total = ch0.length + const numFrames = Math.max(1, Math.floor(total / frameLen)) + const frames = new Array(numFrames) for (let i = 0; i < numFrames; i++) { - const start = i * frameLen; - frames[i] = ch0.subarray(start, start + frameLen); + const start = i * frameLen + frames[i] = ch0.subarray(start, start + frameLen) } - seenKeys.add(key); - return { frames, frameLen, numFrames, key }; + seenKeys.add(key) + return { frames, frameLen, numFrames, key } } - return { frameLen, key }; // worklet will use the cached version + return { frameLen, key } // worklet will use the cached version } function humanFileSize(bytes, si) { - var thresh = si ? 1000 : 1024; - if (bytes < thresh) return bytes + ' B'; + var thresh = si ? 1000 : 1024 + if (bytes < thresh) return bytes + ' B' var units = si ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] - : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; - var u = -1; + : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] + var u = -1 do { - bytes /= thresh; - ++u; - } while (bytes >= thresh); - return bytes.toFixed(1) + ' ' + units[u]; + bytes /= thresh + ++u + } while (bytes >= thresh) + return bytes.toFixed(1) + ' ' + units[u] } // Extract the sample rate of a .wav file function parseWavSampleRate(arrBuf) { - const dv = new DataView(arrBuf); + const dv = new DataView(arrBuf) // Header is "RIFFWAVE", so 12 bytes - let p = 12; + let p = 12 // Look through chunks for the format header // (they will always have an 8 byte header (id and size) followed by a payload) while (p + 8 <= dv.byteLength) { // Parse id - const id = String.fromCharCode(dv.getUint8(p), dv.getUint8(p + 1), dv.getUint8(p + 2), dv.getUint8(p + 3)); + const id = String.fromCharCode( + dv.getUint8(p), + dv.getUint8(p + 1), + dv.getUint8(p + 2), + dv.getUint8(p + 3) + ) // Parse chunk size - const size = dv.getUint32(p + 4, true); + const size = dv.getUint32(p + 4, true) if (id === 'fmt ') { // The format chunk contains the sample rate after // 8 bytes of header, 2 bytes of format tag, 2 bytes of num channels // (for a total of 12) - return dv.getUint32(p + 12, true); + return dv.getUint32(p + 12, true) } // Advance to next chunk - p += 8 + size + (size & 1); + p += 8 + size + (size & 1) } - return null; + return null } async function decodeAtNativeRate(arr) { - const sr = parseWavSampleRate(arr) || 44100; - const tempAC = new OfflineAudioContext(1, 1, sr); - return await tempAC.decodeAudioData(arr); + const sr = parseWavSampleRate(arr) || 44100 + const tempAC = new OfflineAudioContext(1, 1, sr) + return await tempAC.decodeAudioData(arr) } -const loadCache = {}; +const loadCache = {} const loadBuffer = (url, label) => { - url = url.replace('#', '%23'); + url = url.replace('#', '%23') if (!loadCache[url]) { - logger(`[wavetable] load table ${label}..`, 'load-table', { url }); - const timestamp = Date.now(); + logger(`[wavetable] load table ${label}..`, 'load-table', { url }) + const timestamp = Date.now() loadCache[url] = fetch(url) .then((res) => res.arrayBuffer()) .then(async (res) => { - const took = Date.now() - timestamp; - const size = humanFileSize(res.byteLength); - logger(`[wavetable] load table ${label}... done! loaded ${size} in ${took}ms`, 'loaded-table', { url }); - const decoded = await decodeAtNativeRate(res); - return decoded; - }); + const took = Date.now() - timestamp + const size = humanFileSize(res.byteLength) + logger( + `[wavetable] load table ${label}... done! loaded ${size} in ${took}ms`, + 'loaded-table', + { url } + ) + const decoded = await decodeAtNativeRate(res) + return decoded + }) } - return loadCache[url]; -}; + return loadCache[url] +} function githubPath(base, subpath = '') { if (!base.startsWith('github:')) { - throw new Error('expected "github:" at the start of pseudoUrl'); + throw new Error('expected "github:" at the start of pseudoUrl') } - let [_, path] = base.split('github:'); - path = path.endsWith('/') ? path.slice(0, -1) : path; + 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'; + path += '/main' } - return `https://raw.githubusercontent.com/${path}/${subpath}`; + return `https://raw.githubusercontent.com/${path}/${subpath}` } const _processTables = (json, baseUrl, frameLen, options = {}) => { - baseUrl = json._base || baseUrl; + baseUrl = json._base || baseUrl return Object.entries(json).forEach(([key, tables]) => { - if (key === '_base') return false; + if (key === '_base') return false if (typeof tables === 'string') { - tables = [tables]; + tables = [tables] } if (typeof tables !== 'object') { - throw new Error('wrong json format for ' + key); + throw new Error('wrong json format for ' + key) } - let resolvedUrl = baseUrl; + let resolvedUrl = baseUrl if (resolvedUrl.startsWith('github:')) { - resolvedUrl = githubPath(resolvedUrl, ''); + resolvedUrl = githubPath(resolvedUrl, '') } tables = tables .map((t) => resolvedUrl + t) .filter((t) => { if (!t.toLowerCase().endsWith('.wav')) { - logger(`[wavetable] skipping ${t} -- wavetables must be ".wav" format`); - return false; + logger(`[wavetable] skipping ${t} -- wavetables must be ".wav" format`) + return false } - return true; - }); + return true + }) if (tables.length) { - registerWaveTable(key, tables, { baseUrl, frameLen }); + registerWaveTable(key, tables, { baseUrl, frameLen }) } - }); -}; + }) +} export function registerWaveTable(key, tables, params) { registerSound( key, (t, hapValue, onended, cps) => { - return onTriggerSynth(t, hapValue, onended, tables, cps, params?.frameLen ?? 2048); + return onTriggerSynth(t, hapValue, onended, tables, cps, params?.frameLen ?? 2048) }, { type: 'wavetable', tables, ...params, - }, - ); + } + ) } /** @@ -183,47 +192,52 @@ export function registerWaveTable(key, tables, params) { * @name tables */ export const tables = async (url, frameLen, json, options = {}) => { - if (json !== undefined) return _processTables(json, url, frameLen); + if (json !== undefined) return _processTables(json, url, frameLen) if (url.startsWith('github:')) { - url = githubPath(url, 'strudel.json'); + url = githubPath(url, 'strudel.json') } if (url.startsWith('local:')) { - url = `http://localhost:5432`; + url = `http://localhost:5432` } if (typeof fetch !== 'function') { // not a browser - return; + return } if (typeof fetch === 'undefined') { // skip fetch when in node / testing - return; + return } return fetch(url) .then((res) => res.json()) .then((json) => _processTables(json, url, frameLen, options)) .catch((error) => { - console.error(error); - throw new Error(`error loading "${url}"`); - }); -}; + console.error(error) + throw new Error(`error loading "${url}"`) + }) +} export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { - const { s, n = 0, duration, clip } = value; - const ac = getAudioContext(); - const [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]); - let { warpmode } = value; + const { s, n = 0, duration, clip } = value + const ac = getAudioContext() + const [attack, decay, sustain, release] = getADSRValues([ + value.attack, + value.decay, + value.sustain, + value.release, + ]) + let { warpmode } = value if (typeof warpmode === 'string') { - warpmode = Warpmode[warpmode.toUpperCase()] ?? Warpmode.NONE; + warpmode = Warpmode[warpmode.toUpperCase()] ?? Warpmode.NONE } - const frequency = getFrequencyFromValue(value); - const { url, label } = getCommonSampleInfo(value, tables); - const payload = await getPayload(url, label, frameLen); - let holdEnd = t + duration; + const frequency = getFrequencyFromValue(value) + const { url, label } = getCommonSampleInfo(value, tables) + const payload = await getPayload(url, label, frameLen) + let holdEnd = t + duration if (clip !== undefined) { - holdEnd = Math.min(t + clip * duration, holdEnd); + holdEnd = Math.min(t + clip * duration, holdEnd) } - const endWithRelease = holdEnd + release; - const envEnd = endWithRelease + 0.01; + const endWithRelease = holdEnd + release + const envEnd = endWithRelease + 0.01 const source = getWorklet( ac, 'wavetable-oscillator-processor', @@ -239,22 +253,22 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { panspread: value.spread, phaserand: (value.wtphaserand ?? value.unison > 1) ? 1 : 0, }, - { outputChannelCount: [2] }, - ); - source.port.postMessage({ type: 'table', payload }); + { outputChannelCount: [2] } + ) + source.port.postMessage({ type: 'table', payload }) if (ac.currentTime > t) { - logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight'); - return; + logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight') + return } - const posADSRParams = [value.wtattack, value.wtdecay, value.wtsustain, value.wtrelease]; - const warpADSRParams = [value.warpattack, value.warpdecay, value.warpsustain, value.warprelease]; - const wtParams = source.parameters; - const positionParam = wtParams.get('position'); - const warpParam = wtParams.get('warp'); + const posADSRParams = [value.wtattack, value.wtdecay, value.wtsustain, value.wtrelease] + const warpADSRParams = [value.warpattack, value.warpdecay, value.warpsustain, value.warprelease] + const wtParams = source.parameters + const positionParam = wtParams.get('position') + const warpParam = wtParams.get('warp') - let wtrate = value.wtrate; + let wtrate = value.wtrate if (value.wtsync != null) { - wtrate = cps * value.wtsync; + wtrate = cps * value.wtsync } const wtPosModulators = applyParameterModulators( @@ -278,12 +292,12 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { shape: value.wtshape, skew: value.wtskew, dcoffset: value.wtdc ?? 0, - }, - ); + } + ) - let warprate = value.warprate; + let warprate = value.warprate if (value.warpsync != null) { - warprate = warprate = cps * value.warpsync; + warprate = warprate = cps * value.warpsync } const wtWarpModulators = applyParameterModulators( ac, @@ -306,31 +320,31 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { shape: value.warpshape, skew: value.warpskew, dcoffset: value.warpdc ?? 0, - }, - ); - const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t); - const fm = applyFM(source.parameters.get('frequency'), value, t); - const envGain = ac.createGain(); - const node = source.connect(envGain); - getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear'); - getPitchEnvelope(source.parameters.get('detune'), value, t, holdEnd); - const handle = { node, source }; + } + ) + const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t) + const fm = applyFM(source.parameters.get('frequency'), value, t) + const envGain = ac.createGain() + const node = source.connect(envGain) + getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear') + getPitchEnvelope(source.parameters.get('detune'), value, t, holdEnd) + const handle = { node, source } const timeoutNode = webAudioTimeout( ac, () => { - destroyAudioWorkletNode(source); - vibratoOscillator?.stop(); - fm?.stop(); - node.disconnect(); - wtPosModulators?.disconnect(); - wtWarpModulators?.disconnect(); - onended(); + destroyAudioWorkletNode(source) + vibratoOscillator?.stop() + fm?.stop() + node.disconnect() + wtPosModulators?.disconnect() + wtWarpModulators?.disconnect() + onended() }, t, - envEnd, - ); + envEnd + ) handle.stop = (time) => { - timeoutNode.stop(time); - }; - return handle; + timeoutNode.stop(time) + } + return handle } diff --git a/src/strudel/superdough/worklets.mjs b/src/strudel/superdough/worklets.mjs index 7d7ef8e..15f9af1 100644 --- a/src/strudel/superdough/worklets.mjs +++ b/src/strudel/superdough/worklets.mjs @@ -2,112 +2,112 @@ // LICENSE GNU General Public License v3.0 see https://github.com/dktr0/WebDirt/blob/main/LICENSE // TOFIX: THIS FILE DOES NOT SUPPORT IMPORTS ON DEPOLYMENT -import OLAProcessor from './ola-processor'; -import FFT from './fft.js'; -import { getDistortionAlgorithm } from './helpers.mjs'; +import OLAProcessor from './ola-processor' +import FFT from './fft.js' +import { getDistortionAlgorithm } from './helpers.mjs' -const clamp = (num, min, max) => Math.min(Math.max(num, min), max); -const mod = (n, m) => ((n % m) + m) % m; -const lerp = (a, b, n) => n * (b - a) + a; -const pv = (arr, n) => arr[n] ?? arr[0]; -const frac = (x) => x - Math.floor(x); -const ffloor = (x) => x | 0; // fast floor for non-negative +const clamp = (num, min, max) => Math.min(Math.max(num, min), max) +const mod = (n, m) => ((n % m) + m) % m +const lerp = (a, b, n) => n * (b - a) + a +const pv = (arr, n) => arr[n] ?? arr[0] +const frac = (x) => x - Math.floor(x) +const ffloor = (x) => x | 0 // fast floor for non-negative const getUnisonDetune = (unison, detune, voiceIndex) => { if (unison < 2) { - return 0; + return 0 } - return lerp(-detune * 0.5, detune * 0.5, voiceIndex / (unison - 1)); -}; + return lerp(-detune * 0.5, detune * 0.5, voiceIndex / (unison - 1)) +} const applySemitoneDetuneToFrequency = (frequency, detune) => { - return frequency * Math.pow(2, detune / 12); -}; + return frequency * Math.pow(2, detune / 12) +} // Restrict phase to the range [0, maxPhase) via wrapping function wrapPhase(phase, maxPhase = 1) { if (phase >= maxPhase) { - phase -= maxPhase; + phase -= maxPhase } else if (phase < 0) { - phase += maxPhase; + phase += maxPhase } - return phase; + return phase } -const blockSize = 128; +const blockSize = 128 // Smooth waveshape near discontinuities to remove frequencies above Nyquist and prevent aliasing // referenced from https://www.kvraudio.com/forum/viewtopic.php?t=375517 function polyBlep(phase, dt) { - dt = Math.min(dt, 1 - dt); + dt = Math.min(dt, 1 - dt) // Start of cycle if (phase < dt) { - phase /= dt; + phase /= dt // 2 * (phase - phase^2/2 - 0.5) - return phase + phase - phase * phase - 1; + return phase + phase - phase * phase - 1 } // End of cycle else if (phase > 1 - dt) { - phase = (phase - 1) / dt; + phase = (phase - 1) / dt // 2 * (phase^2/2 + phase + 0.5) - return phase * phase + phase + phase + 1; + return phase * phase + phase + phase + 1 } // 0 otherwise else { - return 0; + return 0 } } // The order is important for dough integration const waveshapes = { tri(phase, skew = 0.5) { - const x = 1 - skew; + const x = 1 - skew if (phase >= skew) { - return 1 / x - phase / x; + return 1 / x - phase / x } - return phase / skew; + return phase / skew }, sine(phase) { - return Math.sin(Math.PI * 2 * phase) * 0.5 + 0.5; + return Math.sin(Math.PI * 2 * phase) * 0.5 + 0.5 }, ramp(phase) { - return phase; + return phase }, saw(phase) { - return 1 - phase; + return 1 - phase }, square(phase, skew = 0.5) { if (phase >= skew) { - return 0; + return 0 } - return 1; + return 1 }, custom(phase, values = [0, 1]) { - const numParts = values.length - 1; - const currPart = Math.floor(phase * numParts); + const numParts = values.length - 1 + const currPart = Math.floor(phase * numParts) - const partLength = 1 / numParts; - const startVal = clamp(values[currPart], 0, 1); - const endVal = clamp(values[currPart + 1], 0, 1); - const y2 = endVal; - const y1 = startVal; - const x1 = 0; - const x2 = partLength; - const slope = (y2 - y1) / (x2 - x1); - return slope * (phase - partLength * currPart) + startVal; + const partLength = 1 / numParts + const startVal = clamp(values[currPart], 0, 1) + const endVal = clamp(values[currPart + 1], 0, 1) + const y2 = endVal + const y1 = startVal + const x1 = 0 + const x2 = partLength + const slope = (y2 - y1) / (x2 - x1) + return slope * (phase - partLength * currPart) + startVal }, sawblep(phase, dt) { - const v = 2 * phase - 1; - return v - polyBlep(phase, dt); + const v = 2 * phase - 1 + return v - polyBlep(phase, dt) }, -}; +} function getParamValue(block, param) { if (param.length > 1) { - return param[block]; + return param[block] } - return param[0]; + return param[0] } -const waveShapeNames = Object.keys(waveshapes); +const waveShapeNames = Object.keys(waveshapes) class LFOProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { return [ @@ -123,241 +123,242 @@ class LFOProcessor extends AudioWorkletProcessor { { name: 'dcoffset', defaultValue: 0 }, { name: 'min', defaultValue: 0 }, { name: 'max', defaultValue: 1 }, - ]; + ] } constructor() { - super(); - this.phase; + super() + this.phase } incrementPhase(dt) { - this.phase += dt; + this.phase += dt if (this.phase > 1.0) { - this.phase = this.phase - 1; + this.phase = this.phase - 1 } } process(_inputs, outputs, parameters) { - const begin = parameters['begin'][0]; + const begin = parameters['begin'][0] if (currentTime >= parameters.end[0]) { - return false; + return false } if (currentTime <= begin) { - return true; + return true } - const output = outputs[0]; - const frequency = parameters['frequency'][0]; + const output = outputs[0] + const frequency = parameters['frequency'][0] - const time = parameters['time'][0]; - const depth = parameters['depth'][0]; - const skew = parameters['skew'][0]; - const phaseoffset = parameters['phaseoffset'][0]; + const time = parameters['time'][0] + const depth = parameters['depth'][0] + const skew = parameters['skew'][0] + const phaseoffset = parameters['phaseoffset'][0] - const curve = parameters['curve'][0]; + const curve = parameters['curve'][0] - const dcoffset = parameters['dcoffset'][0]; - const min = parameters['min'][0]; - const max = parameters['max'][0]; - const shape = waveShapeNames[parameters['shape'][0]]; + const dcoffset = parameters['dcoffset'][0] + const min = parameters['min'][0] + const max = parameters['max'][0] + const shape = waveShapeNames[parameters['shape'][0]] - const blockSize = output[0].length ?? 0; + const blockSize = output[0].length ?? 0 if (this.phase == null) { - this.phase = mod(time * frequency + phaseoffset, 1); + this.phase = mod(time * frequency + phaseoffset, 1) } - const dt = frequency / sampleRate; + const dt = frequency / sampleRate for (let n = 0; n < blockSize; n++) { for (let i = 0; i < output.length; i++) { - let modval = (waveshapes[shape](this.phase, skew) + dcoffset) * depth; - modval = Math.pow(modval, curve); - output[i][n] = clamp(modval, min, max); + let modval = (waveshapes[shape](this.phase, skew) + dcoffset) * depth + modval = Math.pow(modval, curve) + output[i][n] = clamp(modval, min, max) } - this.incrementPhase(dt); + this.incrementPhase(dt) } - return true; + return true } } -registerProcessor('lfo-processor', LFOProcessor); +registerProcessor('lfo-processor', LFOProcessor) class CoarseProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { - return [{ name: 'coarse', defaultValue: 1 }]; + return [{ name: 'coarse', defaultValue: 1 }] } constructor() { - super(); - this.started = false; + super() + this.started = false } process(inputs, outputs, parameters) { - const input = inputs[0]; - const output = outputs[0]; + const input = inputs[0] + const output = outputs[0] - const hasInput = !(input[0] === undefined); + const hasInput = !(input[0] === undefined) if (this.started && !hasInput) { - return false; + return false } - this.started = hasInput; + this.started = hasInput - let coarse = parameters.coarse[0] ?? 0; - coarse = Math.max(1, coarse); + let coarse = parameters.coarse[0] ?? 0 + coarse = Math.max(1, coarse) for (let n = 0; n < blockSize; n++) { for (let i = 0; i < input.length; i++) { - output[i][n] = n % coarse === 0 ? input[i][n] : output[i][n - 1]; + output[i][n] = n % coarse === 0 ? input[i][n] : output[i][n - 1] } } - return true; + return true } } -registerProcessor('coarse-processor', CoarseProcessor); +registerProcessor('coarse-processor', CoarseProcessor) class CrushProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { - return [{ name: 'crush', defaultValue: 0 }]; + return [{ name: 'crush', defaultValue: 0 }] } constructor() { - super(); - this.started = false; + super() + this.started = false } process(inputs, outputs, parameters) { - const input = inputs[0]; - const output = outputs[0]; + const input = inputs[0] + const output = outputs[0] - const hasInput = !(input[0] === undefined); + const hasInput = !(input[0] === undefined) if (this.started && !hasInput) { - return false; + return false } - this.started = hasInput; + this.started = hasInput - let crush = parameters.crush[0] ?? 8; - crush = Math.max(1, crush); + let crush = parameters.crush[0] ?? 8 + crush = Math.max(1, crush) for (let n = 0; n < blockSize; n++) { for (let i = 0; i < input.length; i++) { - const x = Math.pow(2, crush - 1); - output[i][n] = Math.round(input[i][n] * x) / x; + const x = Math.pow(2, crush - 1) + output[i][n] = Math.round(input[i][n] * x) / x } } - return true; + return true } } -registerProcessor('crush-processor', CrushProcessor); +registerProcessor('crush-processor', CrushProcessor) class ShapeProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { return [ { name: 'shape', defaultValue: 0 }, { name: 'postgain', defaultValue: 1 }, - ]; + ] } constructor() { - super(); - this.started = false; + super() + this.started = false } process(inputs, outputs, parameters) { - const input = inputs[0]; - const output = outputs[0]; + const input = inputs[0] + const output = outputs[0] - const hasInput = !(input[0] === undefined); + const hasInput = !(input[0] === undefined) if (this.started && !hasInput) { - return false; + return false } - this.started = hasInput; + this.started = hasInput - let shape = parameters.shape[0]; - shape = shape < 1 ? shape : 1.0 - 4e-10; - shape = (2.0 * shape) / (1.0 - shape); - const postgain = Math.max(0.001, Math.min(1, parameters.postgain[0])); + let shape = parameters.shape[0] + shape = shape < 1 ? shape : 1.0 - 4e-10 + shape = (2.0 * shape) / (1.0 - shape) + const postgain = Math.max(0.001, Math.min(1, parameters.postgain[0])) for (let n = 0; n < blockSize; n++) { for (let i = 0; i < input.length; i++) { - output[i][n] = (((1 + shape) * input[i][n]) / (1 + shape * Math.abs(input[i][n]))) * postgain; + output[i][n] = + (((1 + shape) * input[i][n]) / (1 + shape * Math.abs(input[i][n]))) * postgain } } - return true; + return true } } -registerProcessor('shape-processor', ShapeProcessor); +registerProcessor('shape-processor', ShapeProcessor) class TwoPoleFilter { - s0 = 0; - s1 = 0; + s0 = 0 + s1 = 0 update(s, cutoff, resonance = 0) { // Out of bound values can produce NaNs - resonance = clamp(resonance, 0, 1); - cutoff = clamp(cutoff, 0, sampleRate / 2 - 1); - const c = clamp(2 * Math.sin(cutoff * (_PI / sampleRate)), 0, 1.14); - const r = Math.pow(0.5, (resonance + 0.125) / 0.125); - const mrc = 1 - r * c; - this.s0 = mrc * this.s0 - c * this.s1 + c * s; // bpf - this.s1 = mrc * this.s1 + c * this.s0; // lpf - return this.s1; // return lpf by default + resonance = clamp(resonance, 0, 1) + cutoff = clamp(cutoff, 0, sampleRate / 2 - 1) + const c = clamp(2 * Math.sin(cutoff * (_PI / sampleRate)), 0, 1.14) + const r = Math.pow(0.5, (resonance + 0.125) / 0.125) + const mrc = 1 - r * c + this.s0 = mrc * this.s0 - c * this.s1 + c * s // bpf + this.s1 = mrc * this.s1 + c * this.s0 // lpf + return this.s1 // return lpf by default } } class DJFProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { - return [{ name: 'value', defaultValue: 0.5 }]; + return [{ name: 'value', defaultValue: 0.5 }] } constructor() { - super(); - this.filters = [new TwoPoleFilter(), new TwoPoleFilter()]; + super() + this.filters = [new TwoPoleFilter(), new TwoPoleFilter()] } process(inputs, outputs, parameters) { - const input = inputs[0]; - const output = outputs[0]; + const input = inputs[0] + const output = outputs[0] - const hasInput = !(input[0] === undefined); - this.started = hasInput; + const hasInput = !(input[0] === undefined) + this.started = hasInput - const value = clamp(parameters.value[0], 0, 1); - let filterType = 'none'; - let cutoff; - let v = 1; + const value = clamp(parameters.value[0], 0, 1) + let filterType = 'none' + let cutoff + let v = 1 if (value > 0.51) { - filterType = 'hipass'; - v = (value - 0.5) * 2; + filterType = 'hipass' + v = (value - 0.5) * 2 } else if (value < 0.49) { - filterType = 'lopass'; - v = value * 2; + filterType = 'lopass' + v = value * 2 } - cutoff = Math.pow(v * 11, 4); + cutoff = Math.pow(v * 11, 4) for (let i = 0; i < input.length; i++) { for (let n = 0; n < blockSize; n++) { if (filterType == 'none') { - output[i][n] = input[i][n]; + output[i][n] = input[i][n] } else { - this.filters[i].update(input[i][n], cutoff, 0.1); + this.filters[i].update(input[i][n], cutoff, 0.1) if (filterType === 'lopass') { - output[i][n] = this.filters[i].s1; + output[i][n] = this.filters[i].s1 } else if (filterType === 'hipass') { - output[i][n] = input[i][n] - this.filters[i].s1; + output[i][n] = input[i][n] - this.filters[i].s1 } else { - output[i][n] = input[i][n]; + output[i][n] = input[i][n] } } } } - return true; + return true } } -registerProcessor('djf-processor', DJFProcessor); +registerProcessor('djf-processor', DJFProcessor) function fast_tanh(x) { - const x2 = x * x; - return (x * (27.0 + x2)) / (27.0 + 9.0 * x2); + const x2 = x * x + return (x * (27.0 + x2)) / (27.0 + 9.0 * x2) } -const _PI = 3.14159265359; +const _PI = 3.14159265359 //adapted from https://github.com/TheBouteillacBear/webaudioworklet-wasm?tab=MIT-1-ov-file class LadderProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { @@ -365,105 +366,109 @@ class LadderProcessor extends AudioWorkletProcessor { { name: 'frequency', defaultValue: 500 }, { name: 'q', defaultValue: 1 }, { name: 'drive', defaultValue: 0.69 }, - ]; + ] } constructor() { - super(); - this.started = false; - this.p0 = [0, 0]; - this.p1 = [0, 0]; - this.p2 = [0, 0]; - this.p3 = [0, 0]; - this.p32 = [0, 0]; - this.p33 = [0, 0]; - this.p34 = [0, 0]; + super() + this.started = false + this.p0 = [0, 0] + this.p1 = [0, 0] + this.p2 = [0, 0] + this.p3 = [0, 0] + this.p32 = [0, 0] + this.p33 = [0, 0] + this.p34 = [0, 0] } process(inputs, outputs, parameters) { - const input = inputs[0]; - const output = outputs[0]; + const input = inputs[0] + const output = outputs[0] - const hasInput = !(input[0] === undefined); + const hasInput = !(input[0] === undefined) if (this.started && !hasInput) { - return false; + return false } - this.started = hasInput; + this.started = hasInput - const resonance = parameters.q[0]; - const drive = clamp(Math.exp(parameters.drive[0]), 0.1, 2000); + const resonance = parameters.q[0] + const drive = clamp(Math.exp(parameters.drive[0]), 0.1, 2000) - let cutoff = parameters.frequency[0]; - cutoff = (cutoff * 2 * _PI) / sampleRate; - cutoff = cutoff > 1 ? 1 : cutoff; + let cutoff = parameters.frequency[0] + cutoff = (cutoff * 2 * _PI) / sampleRate + cutoff = cutoff > 1 ? 1 : cutoff - const k = Math.min(8, resonance * 0.13); + const k = Math.min(8, resonance * 0.13) // drive makeup * resonance volume loss makeup - let makeupgain = (1 / drive) * Math.min(1.75, 1 + k); + let makeupgain = (1 / drive) * Math.min(1.75, 1 + k) for (let n = 0; n < blockSize; n++) { for (let i = 0; i < input.length; i++) { - const out = this.p3[i] * 0.360891 + this.p32[i] * 0.41729 + this.p33[i] * 0.177896 + this.p34[i] * 0.0439725; + const out = + this.p3[i] * 0.360891 + + this.p32[i] * 0.41729 + + this.p33[i] * 0.177896 + + this.p34[i] * 0.0439725 - this.p34[i] = this.p33[i]; - this.p33[i] = this.p32[i]; - this.p32[i] = this.p3[i]; + this.p34[i] = this.p33[i] + this.p33[i] = this.p32[i] + this.p32[i] = this.p3[i] - this.p0[i] += (fast_tanh(input[i][n] * drive - k * out) - fast_tanh(this.p0[i])) * cutoff; - this.p1[i] += (fast_tanh(this.p0[i]) - fast_tanh(this.p1[i])) * cutoff; - this.p2[i] += (fast_tanh(this.p1[i]) - fast_tanh(this.p2[i])) * cutoff; - this.p3[i] += (fast_tanh(this.p2[i]) - fast_tanh(this.p3[i])) * cutoff; + this.p0[i] += (fast_tanh(input[i][n] * drive - k * out) - fast_tanh(this.p0[i])) * cutoff + this.p1[i] += (fast_tanh(this.p0[i]) - fast_tanh(this.p1[i])) * cutoff + this.p2[i] += (fast_tanh(this.p1[i]) - fast_tanh(this.p2[i])) * cutoff + this.p3[i] += (fast_tanh(this.p2[i]) - fast_tanh(this.p3[i])) * cutoff - output[i][n] = out * makeupgain; + output[i][n] = out * makeupgain } } - return true; + return true } } -registerProcessor('ladder-processor', LadderProcessor); +registerProcessor('ladder-processor', LadderProcessor) class DistortProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { return [ { name: 'distort', defaultValue: 0 }, { name: 'postgain', defaultValue: 1 }, - ]; + ] } constructor({ processorOptions }) { - super(); - this.started = false; - this.algorithm = getDistortionAlgorithm(processorOptions.algorithm); + super() + this.started = false + this.algorithm = getDistortionAlgorithm(processorOptions.algorithm) } process(inputs, outputs, parameters) { - const input = inputs[0]; - const output = outputs[0]; + const input = inputs[0] + const output = outputs[0] - const hasInput = !(input[0] === undefined); + const hasInput = !(input[0] === undefined) if (this.started && !hasInput) { - return false; + return false } - this.started = hasInput; + this.started = hasInput for (let n = 0; n < blockSize; n++) { - const postgain = clamp(pv(parameters.postgain, n), 0.001, 1); - const shape = Math.expm1(pv(parameters.distort, n)); + const postgain = clamp(pv(parameters.postgain, n), 0.001, 1) + const shape = Math.expm1(pv(parameters.distort, n)) for (let ch = 0; ch < input.length; ch++) { - const x = input[ch][n]; - output[ch][n] = postgain * this.algorithm(x, shape); + const x = input[ch][n] + output[ch][n] = postgain * this.algorithm(x, shape) } } - return true; + return true } } -registerProcessor('distort-processor', DistortProcessor); +registerProcessor('distort-processor', DistortProcessor) // SUPERSAW class SuperSawOscillatorProcessor extends AudioWorkletProcessor { constructor() { - super(); - this.phase = []; + super() + this.phase = [] } static get parameterDescriptors() { return [ @@ -509,67 +514,70 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor { defaultValue: 5, min: 1, }, - ]; + ] } process(_input, outputs, params) { if (currentTime <= params.begin[0]) { - return true; + return true } if (currentTime >= params.end[0]) { // this.port.postMessage({ type: 'onended' }); - return false; + return false } - const output = outputs[0]; + const output = outputs[0] for (let i = 0; i < output[0].length; i++) { - const detune = pv(params.detune, i); - const voices = pv(params.voices, i); - const freqspread = pv(params.freqspread, i); - const panspread = pv(params.panspread, i) * 0.5 + 0.5; - const gain1 = Math.sqrt(1 - panspread); - const gain2 = Math.sqrt(panspread); - let freq = pv(params.frequency, i); + const detune = pv(params.detune, i) + const voices = pv(params.voices, i) + const freqspread = pv(params.freqspread, i) + const panspread = pv(params.panspread, i) * 0.5 + 0.5 + const gain1 = Math.sqrt(1 - panspread) + const gain2 = Math.sqrt(panspread) + let freq = pv(params.frequency, i) // Main detuning - freq = applySemitoneDetuneToFrequency(freq, detune / 100); + freq = applySemitoneDetuneToFrequency(freq, detune / 100) for (let n = 0; n < voices; n++) { - const isOdd = (n & 1) == 1; - let gainL = gain1; - let gainR = gain2; + const isOdd = (n & 1) == 1 + let gainL = gain1 + let gainR = gain2 // invert right and left gain if (isOdd) { - gainL = gain2; - gainR = gain1; + gainL = gain2 + gainR = gain1 } // Individual voice detuning - const freqVoice = applySemitoneDetuneToFrequency(freq, getUnisonDetune(voices, freqspread, n)); + const freqVoice = applySemitoneDetuneToFrequency( + freq, + getUnisonDetune(voices, freqspread, n) + ) // We must wrap this here because it is passed into sawblep below which // has domain [0, 1] - const dt = mod(freqVoice / sampleRate, 1); - this.phase[n] = this.phase[n] ?? Math.random(); - const v = waveshapes.sawblep(this.phase[n], dt); + const dt = mod(freqVoice / sampleRate, 1) + this.phase[n] = this.phase[n] ?? Math.random() + const v = waveshapes.sawblep(this.phase[n], dt) - output[0][i] = output[0][i] + v * gainL; - output[1][i] = output[1][i] + v * gainR; + output[0][i] = output[0][i] + v * gainL + output[1][i] = output[1][i] + v * gainR - this.phase[n] = wrapPhase(this.phase[n] + dt); + this.phase[n] = wrapPhase(this.phase[n] + dt) } } - return true; + return true } } -registerProcessor('supersaw-oscillator', SuperSawOscillatorProcessor); +registerProcessor('supersaw-oscillator', SuperSawOscillatorProcessor) // Phase Vocoder sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file -const BUFFERED_BLOCK_SIZE = 2048; +const BUFFERED_BLOCK_SIZE = 2048 function genHannWindow(length) { - let win = new Float32Array(length); + let win = new Float32Array(length) for (var i = 0; i < length; i++) { - win[i] = 0.5 * (1 - Math.cos((2 * Math.PI * i) / length)); + win[i] = 0.5 * (1 - Math.cos((2 * Math.PI * i) / length)) } - return win; + return win } class PhaseVocoderProcessor extends OLAProcessor { @@ -579,180 +587,180 @@ class PhaseVocoderProcessor extends OLAProcessor { name: 'pitchFactor', defaultValue: 1.0, }, - ]; + ] } constructor(options) { options.processorOptions = { blockSize: BUFFERED_BLOCK_SIZE, - }; - super(options); + } + super(options) - this.fftSize = this.blockSize; - this.timeCursor = 0; + this.fftSize = this.blockSize + this.timeCursor = 0 - this.hannWindow = genHannWindow(this.blockSize); + this.hannWindow = genHannWindow(this.blockSize) // prepare FFT and pre-allocate buffers - this.fft = new FFT(this.fftSize); - this.freqComplexBuffer = this.fft.createComplexArray(); - this.freqComplexBufferShifted = this.fft.createComplexArray(); - this.timeComplexBuffer = this.fft.createComplexArray(); - this.magnitudes = new Float32Array(this.fftSize / 2 + 1); - this.peakIndexes = new Int32Array(this.magnitudes.length); - this.nbPeaks = 0; + this.fft = new FFT(this.fftSize) + this.freqComplexBuffer = this.fft.createComplexArray() + this.freqComplexBufferShifted = this.fft.createComplexArray() + this.timeComplexBuffer = this.fft.createComplexArray() + this.magnitudes = new Float32Array(this.fftSize / 2 + 1) + this.peakIndexes = new Int32Array(this.magnitudes.length) + this.nbPeaks = 0 } processOLA(inputs, outputs, parameters) { // no automation, take last value - let pitchFactor = parameters.pitchFactor[parameters.pitchFactor.length - 1]; + let pitchFactor = parameters.pitchFactor[parameters.pitchFactor.length - 1] if (pitchFactor < 0) { - pitchFactor = pitchFactor * 0.25; + pitchFactor = pitchFactor * 0.25 } - pitchFactor = Math.max(0, pitchFactor + 1); + pitchFactor = Math.max(0, pitchFactor + 1) for (var i = 0; i < this.nbInputs; i++) { for (var j = 0; j < inputs[i].length; j++) { // big assumption here: output is symetric to input - var input = inputs[i][j]; - var output = outputs[i][j]; + var input = inputs[i][j] + var output = outputs[i][j] - this.applyHannWindow(input); + this.applyHannWindow(input) - this.fft.realTransform(this.freqComplexBuffer, input); + this.fft.realTransform(this.freqComplexBuffer, input) - this.computeMagnitudes(); - this.findPeaks(); - this.shiftPeaks(pitchFactor); + this.computeMagnitudes() + this.findPeaks() + this.shiftPeaks(pitchFactor) - this.fft.completeSpectrum(this.freqComplexBufferShifted); - this.fft.inverseTransform(this.timeComplexBuffer, this.freqComplexBufferShifted); - this.fft.fromComplexArray(this.timeComplexBuffer, output); - this.applyHannWindow(output); + this.fft.completeSpectrum(this.freqComplexBufferShifted) + this.fft.inverseTransform(this.timeComplexBuffer, this.freqComplexBufferShifted) + this.fft.fromComplexArray(this.timeComplexBuffer, output) + this.applyHannWindow(output) } } - this.timeCursor += this.hopSize; + this.timeCursor += this.hopSize } /** Apply Hann window in-place */ applyHannWindow(input) { for (var i = 0; i < this.blockSize; i++) { - input[i] = input[i] * this.hannWindow[i] * 1.62; + input[i] = input[i] * this.hannWindow[i] * 1.62 } } /** Compute squared magnitudes for peak finding **/ computeMagnitudes() { var i = 0, - j = 0; + j = 0 while (i < this.magnitudes.length) { - let real = this.freqComplexBuffer[j]; - let imag = this.freqComplexBuffer[j + 1]; + let real = this.freqComplexBuffer[j] + let imag = this.freqComplexBuffer[j + 1] // no need to sqrt for peak finding - this.magnitudes[i] = real ** 2 + imag ** 2; - i += 1; - j += 2; + this.magnitudes[i] = real ** 2 + imag ** 2 + i += 1 + j += 2 } } /** Find peaks in spectrum magnitudes **/ findPeaks() { - this.nbPeaks = 0; - var i = 2; - let end = this.magnitudes.length - 2; + this.nbPeaks = 0 + var i = 2 + let end = this.magnitudes.length - 2 while (i < end) { - let mag = this.magnitudes[i]; + let mag = this.magnitudes[i] if (this.magnitudes[i - 1] >= mag || this.magnitudes[i - 2] >= mag) { - i++; - continue; + i++ + continue } if (this.magnitudes[i + 1] >= mag || this.magnitudes[i + 2] >= mag) { - i++; - continue; + i++ + continue } - this.peakIndexes[this.nbPeaks] = i; - this.nbPeaks++; - i += 2; + this.peakIndexes[this.nbPeaks] = i + this.nbPeaks++ + i += 2 } } /** Shift peaks and regions of influence by pitchFactor into new specturm */ shiftPeaks(pitchFactor) { // zero-fill new spectrum - this.freqComplexBufferShifted.fill(0); + this.freqComplexBufferShifted.fill(0) for (var i = 0; i < this.nbPeaks; i++) { - let peakIndex = this.peakIndexes[i]; - let peakIndexShifted = Math.round(peakIndex * pitchFactor); + let peakIndex = this.peakIndexes[i] + let peakIndexShifted = Math.round(peakIndex * pitchFactor) if (peakIndexShifted > this.magnitudes.length) { - break; + break } // find region of influence - var startIndex = 0; - var endIndex = this.fftSize; + var startIndex = 0 + var endIndex = this.fftSize if (i > 0) { - let peakIndexBefore = this.peakIndexes[i - 1]; - startIndex = peakIndex - Math.floor((peakIndex - peakIndexBefore) / 2); + let peakIndexBefore = this.peakIndexes[i - 1] + startIndex = peakIndex - Math.floor((peakIndex - peakIndexBefore) / 2) } if (i < this.nbPeaks - 1) { - let peakIndexAfter = this.peakIndexes[i + 1]; - endIndex = peakIndex + Math.ceil((peakIndexAfter - peakIndex) / 2); + let peakIndexAfter = this.peakIndexes[i + 1] + endIndex = peakIndex + Math.ceil((peakIndexAfter - peakIndex) / 2) } // shift whole region of influence around peak to shifted peak - let startOffset = startIndex - peakIndex; - let endOffset = endIndex - peakIndex; + let startOffset = startIndex - peakIndex + let endOffset = endIndex - peakIndex for (var j = startOffset; j < endOffset; j++) { - let binIndex = peakIndex + j; - let binIndexShifted = peakIndexShifted + j; + let binIndex = peakIndex + j + let binIndexShifted = peakIndexShifted + j if (binIndexShifted >= this.magnitudes.length) { - break; + break } // apply phase correction - let omegaDelta = (2 * Math.PI * (binIndexShifted - binIndex)) / this.fftSize; - let phaseShiftReal = Math.cos(omegaDelta * this.timeCursor); - let phaseShiftImag = Math.sin(omegaDelta * this.timeCursor); + let omegaDelta = (2 * Math.PI * (binIndexShifted - binIndex)) / this.fftSize + let phaseShiftReal = Math.cos(omegaDelta * this.timeCursor) + let phaseShiftImag = Math.sin(omegaDelta * this.timeCursor) - let indexReal = binIndex * 2; - let indexImag = indexReal + 1; - let valueReal = this.freqComplexBuffer[indexReal]; - let valueImag = this.freqComplexBuffer[indexImag]; + let indexReal = binIndex * 2 + let indexImag = indexReal + 1 + let valueReal = this.freqComplexBuffer[indexReal] + let valueImag = this.freqComplexBuffer[indexImag] - let valueShiftedReal = valueReal * phaseShiftReal - valueImag * phaseShiftImag; - let valueShiftedImag = valueReal * phaseShiftImag + valueImag * phaseShiftReal; + let valueShiftedReal = valueReal * phaseShiftReal - valueImag * phaseShiftImag + let valueShiftedImag = valueReal * phaseShiftImag + valueImag * phaseShiftReal - let indexShiftedReal = binIndexShifted * 2; - let indexShiftedImag = indexShiftedReal + 1; - this.freqComplexBufferShifted[indexShiftedReal] += valueShiftedReal; - this.freqComplexBufferShifted[indexShiftedImag] += valueShiftedImag; + let indexShiftedReal = binIndexShifted * 2 + let indexShiftedImag = indexShiftedReal + 1 + this.freqComplexBufferShifted[indexShiftedReal] += valueShiftedReal + this.freqComplexBufferShifted[indexShiftedImag] += valueShiftedImag } } } } -registerProcessor('phase-vocoder-processor', PhaseVocoderProcessor); +registerProcessor('phase-vocoder-processor', PhaseVocoderProcessor) // Adapted from https://www.musicdsp.org/en/latest/Effects/221-band-limited-pwm-generator.html class PulseOscillatorProcessor extends AudioWorkletProcessor { constructor() { - super(); - this.pi = _PI; - this.phi = -this.pi; // phase - this.Y0 = 0; // feedback memories - this.Y1 = 0; - this.PW = this.pi; // pulse width - this.B = 2.3; // feedback coefficient - this.dphif = 0; // filtered phase increment - this.envf = 0; // filtered envelope + super() + this.pi = _PI + this.phi = -this.pi // phase + this.Y0 = 0 // feedback memories + this.Y1 = 0 + this.PW = this.pi // pulse width + this.B = 2.3 // feedback coefficient + this.dphif = 0 // filtered phase increment + this.envf = 0 // filtered envelope } static get parameterDescriptors() { @@ -788,115 +796,118 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor { min: 0, max: Number.POSITIVE_INFINITY, }, - ]; + ] } process(inputs, outputs, params) { if (this.disconnected) { - return false; + return false } if (currentTime <= params.begin[0]) { - return true; + return true } if (currentTime >= params.end[0]) { - return false; + return false } - const output = outputs[0]; + const output = outputs[0] let env = 1, - dphi; + dphi for (let i = 0; i < (output[0].length ?? 0); i++) { - const pw = (1 - clamp(getParamValue(i, params.pulsewidth), -0.99, 0.99)) * this.pi; - const detune = getParamValue(i, params.detune); - const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100); + const pw = (1 - clamp(getParamValue(i, params.pulsewidth), -0.99, 0.99)) * this.pi + const detune = getParamValue(i, params.detune) + const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100) - dphi = freq * (this.pi / (sampleRate * 0.5)); // phase increment - this.dphif += 0.1 * (dphi - this.dphif); + dphi = freq * (this.pi / (sampleRate * 0.5)) // phase increment + this.dphif += 0.1 * (dphi - this.dphif) - env *= 0.9998; // exponential decay envelope - this.envf += 0.1 * (env - this.envf); + env *= 0.9998 // exponential decay envelope + this.envf += 0.1 * (env - this.envf) // Feedback coefficient control - this.B = 2.3 * (1 - 0.0001 * freq); // feedback limitation - if (this.B < 0) this.B = 0; + this.B = 2.3 * (1 - 0.0001 * freq) // feedback limitation + if (this.B < 0) this.B = 0 // Waveform generation (half-Tomisawa oscillators) - this.phi += this.dphif; // phase increment - if (this.phi >= this.pi) this.phi -= 2 * this.pi; // phase wrapping + this.phi += this.dphif // phase increment + if (this.phi >= this.pi) this.phi -= 2 * this.pi // phase wrapping // First half-Tomisawa generator - let out0 = Math.cos(this.phi + this.B * this.Y0); // self-phase modulation - this.Y0 = 0.5 * (out0 + this.Y0); // anti-hunting filter + let out0 = Math.cos(this.phi + this.B * this.Y0) // self-phase modulation + this.Y0 = 0.5 * (out0 + this.Y0) // anti-hunting filter // Second half-Tomisawa generator (with phase offset for pulse width) - let out1 = Math.cos(this.phi + this.B * this.Y1 + pw); - this.Y1 = 0.5 * (out1 + this.Y1); // anti-hunting filter + let out1 = Math.cos(this.phi + this.B * this.Y1 + pw) + this.Y1 = 0.5 * (out1 + this.Y1) // anti-hunting filter for (let o = 0; o < output.length; o++) { // Combination of both oscillators with envelope applied - output[o][i] = 0.15 * (out0 - out1) * this.envf; + output[o][i] = 0.15 * (out0 - out1) * this.envf } } - return true; // keep the audio processing going + return true // keep the audio processing going } } -registerProcessor('pulse-oscillator', PulseOscillatorProcessor); +registerProcessor('pulse-oscillator', PulseOscillatorProcessor) /** BYTE BEATS */ const chyx = { /*bit*/ bitC: function (x, y, z) { - return x & y ? z : 0; + return x & y ? z : 0 }, /*bit reverse*/ br: function (x, size = 8) { if (size > 32) { - throw new Error('br() Size cannot be greater than 32'); + throw new Error('br() Size cannot be greater than 32') } else { - let result = 0; + let result = 0 for (let idx = 0; idx < size - 0; idx++) { - result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1))); + result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1))) } - return result; + return result } }, /*sin that loops every 128 "steps", instead of every pi steps*/ sinf: function (x) { - return Math.sin(x / (128 / Math.PI)); + return Math.sin(x / (128 / Math.PI)) }, /*cos that loops every 128 "steps", instead of every pi steps*/ cosf: function (x) { - return Math.cos(x / (128 / Math.PI)); + return Math.cos(x / (128 / Math.PI)) }, /*tan that loops every 128 "steps", instead of every pi steps*/ tanf: function (x) { - return Math.tan(x / (128 / Math.PI)); + return Math.tan(x / (128 / Math.PI)) }, /*converts t into a string composed of it's bits, regex's that*/ regG: function (t, X) { - return X.test(t.toString(2)); + return X.test(t.toString(2)) }, -}; +} // Create shortened Math functions -let mathParams, byteBeatHelperFuncs; +let mathParams, byteBeatHelperFuncs function getByteBeatFunc(codetext) { if ((mathParams || byteBeatHelperFuncs) == null) { - mathParams = Object.getOwnPropertyNames(Math); - byteBeatHelperFuncs = mathParams.map((k) => Math[k]); - const chyxNames = Object.getOwnPropertyNames(chyx); - const chyxFuncs = chyxNames.map((k) => chyx[k]); - mathParams.push('int', 'window', ...chyxNames); - byteBeatHelperFuncs.push(Math.floor, globalThis, ...chyxFuncs); + mathParams = Object.getOwnPropertyNames(Math) + byteBeatHelperFuncs = mathParams.map((k) => Math[k]) + const chyxNames = Object.getOwnPropertyNames(chyx) + const chyxFuncs = chyxNames.map((k) => chyx[k]) + mathParams.push('int', 'window', ...chyxNames) + byteBeatHelperFuncs.push(Math.floor, globalThis, ...chyxFuncs) } - return new Function(...mathParams, 't', `return 0,\n${codetext || 0};`).bind(globalThis, ...byteBeatHelperFuncs); + return new Function(...mathParams, 't', `return 0,\n${codetext || 0};`).bind( + globalThis, + ...byteBeatHelperFuncs + ) } class ByteBeatProcessor extends AudioWorkletProcessor { constructor() { - super(); + super() this.port.onmessage = (event) => { - let { codeText } = event.data; - const { byteBeatStartTime } = event.data; + let { codeText } = event.data + const { byteBeatStartTime } = event.data if (byteBeatStartTime != null) { - this.t = 0; - this.initialOffset = Math.floor(byteBeatStartTime); + this.t = 0 + this.initialOffset = Math.floor(byteBeatStartTime) } //Optimization pulled from dollchan.net: https://github.com/Chasyxx/EnBeat_NEW, it seemed important @@ -905,14 +916,14 @@ class ByteBeatProcessor extends AudioWorkletProcessor { .trim() .replace( /^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/, - (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%')), - ); + (match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%')) + ) - this.func = getByteBeatFunc(codeText); - }; - this.initialOffset = null; - this.t = null; - this.func = null; + this.func = getByteBeatFunc(codeText) + } + this.initialOffset = null + this.t = null + this.func = null } static get parameterDescriptors() { @@ -940,42 +951,42 @@ class ByteBeatProcessor extends AudioWorkletProcessor { max: Number.POSITIVE_INFINITY, min: 0, }, - ]; + ] } process(inputs, outputs, params) { if (this.disconnected) { - return false; + return false } if (currentTime <= params.begin[0]) { - return true; + return true } if (currentTime >= params.end[0]) { - return false; + return false } if (this.t == null) { - this.t = params.begin[0] * sampleRate; + this.t = params.begin[0] * sampleRate } - const output = outputs[0]; + const output = outputs[0] for (let i = 0; i < output[0].length; i++) { - const detune = getParamValue(i, params.detune); - const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100); - let local_t = (this.t / (sampleRate / 256)) * freq + this.initialOffset; - const funcValue = this.func(local_t); - let signal = (funcValue & 255) / 127.5 - 1; - const out = signal * 0.2; + const detune = getParamValue(i, params.detune) + const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100) + let local_t = (this.t / (sampleRate / 256)) * freq + this.initialOffset + const funcValue = this.func(local_t) + let signal = (funcValue & 255) / 127.5 - 1 + const out = signal * 0.2 for (let c = 0; c < output.length; c++) { //prevent speaker blowout via clipping if threshold exceeds - output[c][i] = clamp(out, -0.4, 0.4); + output[c][i] = clamp(out, -0.4, 0.4) } - this.t = this.t + 1; + this.t = this.t + 1 } - return true; // keep the audio processing going + return true // keep the audio processing going } } -registerProcessor('byte-beat-processor', ByteBeatProcessor); +registerProcessor('byte-beat-processor', ByteBeatProcessor) export const WarpMode = Object.freeze({ NONE: 0, @@ -1000,51 +1011,51 @@ export const WarpMode = Object.freeze({ SIGMOID: 19, FRACTAL: 20, FLIP: 21, -}); +}) function hash32(u) { - u = u + 0x7ed55d16 + (u << 12); - u = u ^ 0xc761c23c ^ (u >>> 19); - u = u + 0x165667b1 + (u << 5); - u = (u + 0xd3a2646c) ^ (u << 9); - u = u + 0xfd7046c5 + (u << 3); - u = u ^ 0xb55a4f09 ^ (u >>> 16); - return u >>> 0; + u = u + 0x7ed55d16 + (u << 12) + u = u ^ 0xc761c23c ^ (u >>> 19) + u = u + 0x165667b1 + (u << 5) + u = (u + 0xd3a2646c) ^ (u << 9) + u = u + 0xfd7046c5 + (u << 3) + u = u ^ 0xb55a4f09 ^ (u >>> 16) + return u >>> 0 } -const hash01 = (i) => (hash32(i) >>> 8) / 0x01000000; +const hash01 = (i) => (hash32(i) >>> 8) / 0x01000000 function bitReverse(i, n) { - let r = 0; + let r = 0 for (let b = 0; b < n; b++) { - r = (r << 1) | (i & 1); - i >>>= 1; + r = (r << 1) | (i & 1) + i >>>= 1 } - return r; + return r } function noise(x) { const i = Math.floor(x), - f = x - i; + f = x - i const a = hash01(i), - b = hash01(i + 1); - return a + (b - a) * f; + b = hash01(i + 1) + return a + (b - a) * f } function brownian(x, oct = 4) { let amp = 0.5, sum = 0, norm = 0, - freq = 1; + freq = 1 for (let o = 0; o < oct; o++) { - sum += amp * noise(x * freq); - norm += amp; - amp *= 0.5; - freq *= 2; + sum += amp * noise(x * freq) + norm += amp + amp *= 0.5 + freq *= 2 } - return (sum / norm) * 2 - 1; + return (sum / norm) * 2 - 1 } -const tablesCache = {}; +const tablesCache = {} class WavetableOscillatorProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { return [ @@ -1059,263 +1070,268 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { { name: 'voices', defaultValue: 1, min: 1 }, { name: 'panspread', defaultValue: 0.7, min: 0, max: 1 }, { name: 'phaserand', defaultValue: 0, min: 0, max: 1 }, - ]; + ] } constructor(options) { - super(options); - this.frameLen = 0; - this.numFrames = 0; - this.phase = []; - this.invSR = 1 / sampleRate; + super(options) + this.frameLen = 0 + this.numFrames = 0 + this.phase = [] + this.invSR = 1 / sampleRate this.port.onmessage = (e) => { - const { type, payload } = e.data || {}; + const { type, payload } = e.data || {} if (type === 'table') { - const key = payload.key; - this.frameLen = payload.frameLen; + const key = payload.key + this.frameLen = payload.frameLen if (!tablesCache[key]) { - const tables = [payload.frames]; - let table = tables[0]; + const tables = [payload.frames] + let table = tables[0] for (let level = 1; level < 1; level++) { - const nextLen = table.length >> 1; + const nextLen = table.length >> 1 const nextTable = table.map((frame) => { - const avg = new Float32Array(nextLen); + const avg = new Float32Array(nextLen) for (let i = 0; i < nextLen; i++) { - avg[i] = (frame[2 * i] + frame[2 * i + 1]) / 2; + avg[i] = (frame[2 * i] + frame[2 * i + 1]) / 2 } - return avg; - }); - tables.push(nextTable); - table = nextTable; - if (nextLen <= 32) break; + return avg + }) + tables.push(nextTable) + table = nextTable + if (nextLen <= 32) break } - tablesCache[key] = tables; + tablesCache[key] = tables } - this.tables = tablesCache[key]; - this.numFrames = this.tables[0].length; + this.tables = tablesCache[key] + this.numFrames = this.tables[0].length } - }; + } } _mirror(x) { - return 1 - Math.abs(2 * x - 1); + return 1 - Math.abs(2 * x - 1) } _toBits(amt, min = 2, max = 12) { - const b = max + (min - max) * amt; - return { b, n: Math.round(Math.pow(2, b)) }; + const b = max + (min - max) * amt + return { b, n: Math.round(Math.pow(2, b)) } } _warpPhase(phase, amt, mode) { switch (mode) { case WarpMode.NONE: { - return phase; + return phase } case WarpMode.ASYM: { - const a = 0.01 + 0.99 * amt; - return phase < a ? (0.5 * phase) / a : 0.5 + (0.5 * (phase - a)) / (1 - a); + const a = 0.01 + 0.99 * amt + return phase < a ? (0.5 * phase) / a : 0.5 + (0.5 * (phase - a)) / (1 - a) } case WarpMode.MIRROR: { // Asym, then mirror - return this._mirror(this._warpPhase(phase, amt, WarpMode.ASYM)); + return this._mirror(this._warpPhase(phase, amt, WarpMode.ASYM)) } case WarpMode.BENDP: { - return Math.pow(phase, 1 + 3 * amt); + return Math.pow(phase, 1 + 3 * amt) } case WarpMode.BENDM: { - return Math.pow(phase, 1 / (1 + 3 * amt)); + return Math.pow(phase, 1 / (1 + 3 * amt)) } case WarpMode.BENDMP: { - return amt < 0.5 ? this._warpPhase(phase, 1 - 2 * amt, 3) : this._warpPhase(phase, 2 * amt - 1, 2); + return amt < 0.5 + ? this._warpPhase(phase, 1 - 2 * amt, 3) + : this._warpPhase(phase, 2 * amt - 1, 2) } case WarpMode.SYNC: { - const syncRatio = Math.pow(16, amt * amt); - return (phase * syncRatio) % 1; + const syncRatio = Math.pow(16, amt * amt) + return (phase * syncRatio) % 1 } case WarpMode.QUANT: { - const { n } = this._toBits(amt); - return ffloor(phase * n) / n; + const { n } = this._toBits(amt) + return ffloor(phase * n) / n } case WarpMode.FOLD: { - const K = 7; - const k = 1 + Math.max(1, Math.round(K * amt)); - return Math.abs(frac(k * phase) - 0.5) * 2; + const K = 7 + const k = 1 + Math.max(1, Math.round(K * amt)) + return Math.abs(frac(k * phase) - 0.5) * 2 } case WarpMode.PWM: { - const w = clamp(0.5 + 0.49 * (2 * amt - 1), 0, 1); - if (phase < w) return (phase / w) * 0.5; - return 0.5 + ((phase - w) / (1 - w)) * 0.5; + const w = clamp(0.5 + 0.49 * (2 * amt - 1), 0, 1) + if (phase < w) return (phase / w) * 0.5 + return 0.5 + ((phase - w) / (1 - w)) * 0.5 } case WarpMode.ORBIT: { - const depth = 0.5 * amt; - const n = 3; - return frac(phase + depth * Math.sin(2 * Math.PI * n * phase)); + const depth = 0.5 * amt + const n = 3 + return frac(phase + depth * Math.sin(2 * Math.PI * n * phase)) } case WarpMode.SPIN: { - const depth = 0.5 * amt; - const { n } = this._toBits(amt, 1, 6); - return frac(phase + depth * Math.sin(2 * Math.PI * n * phase)); + const depth = 0.5 * amt + const { n } = this._toBits(amt, 1, 6) + return frac(phase + depth * Math.sin(2 * Math.PI * n * phase)) } case WarpMode.CHAOS: { - const r = 3.7 + 0.3 * amt; - const logistic = r * phase * (1 - phase); - return clamp((1 - amt) * phase + amt * logistic, 0, 1); + const r = 3.7 + 0.3 * amt + const logistic = r * phase * (1 - phase) + return clamp((1 - amt) * phase + amt * logistic, 0, 1) } case WarpMode.PRIMES: { const isPrime = (n) => { - if (n < 2) return false; - if (n % 2 === 0) return n === 2; - for (let d = 3; d * d <= n; d += 2) if (n % d === 0) return false; - return true; - }; - let { n } = this._toBits(amt, 3); - while (!isPrime(n)) n++; - return ffloor(phase * n) / n; + if (n < 2) return false + if (n % 2 === 0) return n === 2 + for (let d = 3; d * d <= n; d += 2) if (n % d === 0) return false + return true + } + let { n } = this._toBits(amt, 3) + while (!isPrime(n)) n++ + return ffloor(phase * n) / n } case WarpMode.BINARY: { - let { b } = this._toBits(amt, 3); - b = Math.round(b); - const n = 1 << b; - const idx = ffloor(phase * n); - const ridx = bitReverse(idx, b); - return ridx / n; + let { b } = this._toBits(amt, 3) + b = Math.round(b) + const n = 1 << b + const idx = ffloor(phase * n) + const ridx = bitReverse(idx, b) + return ridx / n } case WarpMode.MODULAR: { - const { n } = this._toBits(amt); - const depth = 0.5 * amt; - const jump = frac(phase * n) / n; - return frac(phase + depth * jump); + const { n } = this._toBits(amt) + const depth = 0.5 * amt + const jump = frac(phase * n) / n + return frac(phase + depth * jump) } case WarpMode.BROWNIAN: { - const disp = 0.25 * amt * brownian(64 * phase, 4); - return frac(phase + disp); + const disp = 0.25 * amt * brownian(64 * phase, 4) + return frac(phase + disp) } case WarpMode.RECIPROCAL: { - const g = 2 + 4 * amt; - const num = phase * g; - const den = phase + (1 - phase) * g; - const y = den > 1e-12 ? num / den : 0; - return clamp(y, 0, 1); + const g = 2 + 4 * amt + const num = phase * g + const den = phase + (1 - phase) * g + const y = den > 1e-12 ? num / den : 0 + return clamp(y, 0, 1) } case WarpMode.WORMHOLE: { - const gap = clamp(0.8 * amt, 0, 1); - const a = 0.5 * (1 - gap); - const b = 0.5 * (1 + gap); - if (phase < a) return (phase / a) * 0.5; - if (phase > b) return 0.5 * (1 + (phase - b) / (1 - b)); - return 0.5; + const gap = clamp(0.8 * amt, 0, 1) + const a = 0.5 * (1 - gap) + const b = 0.5 * (1 + gap) + if (phase < a) return (phase / a) * 0.5 + if (phase > b) return 0.5 * (1 + (phase - b) / (1 - b)) + return 0.5 } case WarpMode.LOGISTIC: { - let x = phase; - const r = 3.6 + 0.4 * amt; - const iters = 1 + Math.round(2 * amt); - for (let i = 0; i < iters; i++) x = r * x * (1 - x); - return clamp(x, 0, 1); + let x = phase + const r = 3.6 + 0.4 * amt + const iters = 1 + Math.round(2 * amt) + for (let i = 0; i < iters; i++) x = r * x * (1 - x) + return clamp(x, 0, 1) } case WarpMode.SIGMOID: { - const k = 1 + 10 * amt; - const x = phase - 0.5; - const y = 1 / (1 + Math.exp(-k * x)); - const y0 = 1 / (1 + Math.exp(0.5 * k)); - const y1 = 1 / (1 + Math.exp(-0.5 * k)); - return (y - y0) / (y1 - y0); + const k = 1 + 10 * amt + const x = phase - 0.5 + const y = 1 / (1 + Math.exp(-k * x)) + const y0 = 1 / (1 + Math.exp(0.5 * k)) + const y1 = 1 / (1 + Math.exp(-0.5 * k)) + return (y - y0) / (y1 - y0) } case WarpMode.FRACTAL: { - const d = 0.5 * Math.sin(2 * Math.PI * phase) * amt; - return frac(phase + d); + const d = 0.5 * Math.sin(2 * Math.PI * phase) * amt + return frac(phase + d) } case WarpMode.FLIP: { - return phase; + return phase } default: - return phase; + return phase } } _sampleFrame(frame, phase) { - const len = frame.length; - const pos = phase * len; - let i = pos | 0; - if (i >= len) i = 0; // fast wrap - const frac = pos - i; - const a = frame[i]; - let i1 = i + 1; - if (i1 >= len) i1 = 0; - const b = frame[i1]; - return a + (b - a) * frac; + const len = frame.length + const pos = phase * len + let i = pos | 0 + if (i >= len) i = 0 // fast wrap + const frac = pos - i + const a = frame[i] + let i1 = i + 1 + if (i1 >= len) i1 = 0 + const b = frame[i1] + return a + (b - a) * frac } _chooseMip(dphi) { - const approxHarm = clamp(dphi, 1e-6, 64); - let level = 0; - while (level + 1 < (this.tables?.length || 1) && approxHarm < this.tables[level][0].length / 8) { - level++; + const approxHarm = clamp(dphi, 1e-6, 64) + let level = 0 + while ( + level + 1 < (this.tables?.length || 1) && + approxHarm < this.tables[level][0].length / 8 + ) { + level++ } - return level; + return level } process(_inputs, outputs, parameters) { if (currentTime >= parameters.end[0]) { - return false; + return false } if (currentTime <= parameters.begin[0]) { - return true; + return true } - const outL = outputs[0][0]; - const outR = outputs[0][1] || outputs[0][0]; + const outL = outputs[0][0] + const outR = outputs[0][1] || outputs[0][0] if (!this.tables) { - outL.fill(0); - if (outR !== outL) outR.set(outL); - return true; + outL.fill(0) + if (outR !== outL) outR.set(outL) + return true } for (let i = 0; i < outL.length; i++) { - const detune = pv(parameters.detune, i); - const freqspread = pv(parameters.freqspread, i); - const tablePos = clamp(pv(parameters.position, i), 0, 1); - const idx = tablePos * (this.numFrames - 1); - const fIdx = idx | 0; - const frac = idx - fIdx; - const warpAmount = clamp(pv(parameters.warp, i), 0, 1); - const warpMode = pv(parameters.warpMode, i); - const voices = pv(parameters.voices, i); - const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1); - const panspread = voices > 1 ? clamp(pv(parameters.panspread, i), 0, 1) : 0; - const gain1 = Math.sqrt(0.5 - 0.5 * panspread); - const gain2 = Math.sqrt(0.5 + 0.5 * panspread); - let f = pv(parameters.frequency, i); - f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune - const normalizer = 1 / Math.sqrt(voices); + const detune = pv(parameters.detune, i) + const freqspread = pv(parameters.freqspread, i) + const tablePos = clamp(pv(parameters.position, i), 0, 1) + const idx = tablePos * (this.numFrames - 1) + const fIdx = idx | 0 + const frac = idx - fIdx + const warpAmount = clamp(pv(parameters.warp, i), 0, 1) + const warpMode = pv(parameters.warpMode, i) + const voices = pv(parameters.voices, i) + const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1) + const panspread = voices > 1 ? clamp(pv(parameters.panspread, i), 0, 1) : 0 + const gain1 = Math.sqrt(0.5 - 0.5 * panspread) + const gain2 = Math.sqrt(0.5 + 0.5 * panspread) + let f = pv(parameters.frequency, i) + f = applySemitoneDetuneToFrequency(f, detune / 100) // overall detune + const normalizer = 1 / Math.sqrt(voices) for (let n = 0; n < voices; n++) { - const isOdd = (n & 1) == 1; - let gainL = gain1; - let gainR = gain2; + const isOdd = (n & 1) == 1 + let gainL = gain1 + let gainR = gain2 // invert right and left gain if (isOdd) { - gainL = gain2; - gainR = gain1; + gainL = gain2 + gainR = gain1 } - const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, freqspread, n)); // voice detune - const dPhase = fVoice * this.invSR; - const level = this._chooseMip(dPhase); - const table = this.tables[level]; + const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, freqspread, n)) // voice detune + const dPhase = fVoice * this.invSR + const level = this._chooseMip(dPhase) + const table = this.tables[level] // warp phase then sample - this.phase[n] = this.phase[n] ?? Math.random() * phaseRand; - const ph = this._warpPhase(this.phase[n], warpAmount, warpMode); - const s0 = this._sampleFrame(table[fIdx], ph); - const s1 = this._sampleFrame(table[Math.min(this.numFrames - 1, fIdx + 1)], ph); - let s = s0 + (s1 - s0) * frac; + this.phase[n] = this.phase[n] ?? Math.random() * phaseRand + const ph = this._warpPhase(this.phase[n], warpAmount, warpMode) + const s0 = this._sampleFrame(table[fIdx], ph) + const s1 = this._sampleFrame(table[Math.min(this.numFrames - 1, fIdx + 1)], ph) + let s = s0 + (s1 - s0) * frac if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) { - s = -s; + s = -s } - outL[i] += s * gainL * normalizer; - outR[i] += s * gainR * normalizer; - this.phase[n] = wrapPhase(this.phase[n] + dPhase); + outL[i] += s * gainL * normalizer + outR[i] += s * gainR * normalizer + this.phase[n] = wrapPhase(this.phase[n] + dPhase) } } - return true; + return true } } -registerProcessor('wavetable-oscillator-processor', WavetableOscillatorProcessor); +registerProcessor('wavetable-oscillator-processor', WavetableOscillatorProcessor) diff --git a/src/strudel/superdough/zzfx.mjs b/src/strudel/superdough/zzfx.mjs index 32db039..3435be0 100644 --- a/src/strudel/superdough/zzfx.mjs +++ b/src/strudel/superdough/zzfx.mjs @@ -1,8 +1,8 @@ //import { ZZFX } from 'zzfx'; -import { midiToFreq, noteToMidi } from './util.mjs'; -import { registerSound } from './superdough.mjs'; -import { getAudioContext } from './audioContext.mjs'; -import { buildSamples } from './zzfx_fork.mjs'; +import { midiToFreq, noteToMidi } from './util.mjs' +import { registerSound } from './superdough.mjs' +import { getAudioContext } from './audioContext.mjs' +import { buildSamples } from './zzfx_fork.mjs' export const getZZFX = (value, t) => { let { @@ -28,18 +28,18 @@ export const getZZFX = (value, t) => { tremolo = 0, duration = 0.2, zzfx, - } = value; - const sustainTime = Math.max(duration - attack - decay, 0); + } = value + const sustainTime = Math.max(duration - attack - decay, 0) if (typeof note === 'string') { - note = noteToMidi(note); // e.g. c3 => 48 + note = noteToMidi(note) // e.g. c3 => 48 } // get frequency if (!freq && typeof note === 'number') { - freq = midiToFreq(note); + freq = midiToFreq(note) } - s = s.replace('z_', ''); - const shape = ['sine', 'triangle', 'sawtooth', 'tan', 'noise'].indexOf(s) || 0; - curve = s === 'square' ? 0 : curve; + s = s.replace('z_', '') + const shape = ['sine', 'triangle', 'sawtooth', 'tan', 'noise'].indexOf(s) || 0 + curve = s === 'square' ? 0 : curve const params = zzfx || [ 0.25, // volume @@ -62,39 +62,43 @@ export const getZZFX = (value, t) => { sustain, // sustain volume! decay, tremolo, - ]; + ] // console.log(redableZZFX(params)); - const samples = /* ZZFX. */ buildSamples(...params); - const context = getAudioContext(); - const buffer = context.createBuffer(1, samples.length, context.sampleRate); - buffer.getChannelData(0).set(samples); - const source = getAudioContext().createBufferSource(); - source.buffer = buffer; - source.start(t); + const samples = /* ZZFX. */ buildSamples(...params) + const context = getAudioContext() + const buffer = context.createBuffer(1, samples.length, context.sampleRate) + buffer.getChannelData(0).set(samples) + const source = getAudioContext().createBufferSource() + source.buffer = buffer + source.start(t) return { node: source, - }; -}; + } +} export function registerZZFXSounds() { - ['zzfx', 'z_sine', 'z_sawtooth', 'z_triangle', 'z_square', 'z_tan', 'z_noise'].forEach((wave) => { - registerSound( - wave, - (t, value, onended) => { - const { node: o } = getZZFX({ s: wave, ...value }, t); - o.onended = () => { - o.disconnect(); - onended(); - }; - return { - node: o, - stop: () => {}, - }; - }, - { type: 'synth', prebake: true }, - ); - }); + ;['zzfx', 'z_sine', 'z_sawtooth', 'z_triangle', 'z_square', 'z_tan', 'z_noise'].forEach( + (wave) => { + registerSound( + wave, + (t, value, onended) => { + const { node: o } = getZZFX({ s: wave, ...value }, t) + o.onended = () => { + o.disconnect() + onended() + } + return { + node: o, + stop: () => { + return + }, + } + }, + { type: 'synth', prebake: true } + ) + } + ) } // just for debugging @@ -120,6 +124,6 @@ function redableZZFX(params) { 'sustainVolume', 'decay', 'tremolo', - ]; - return Object.fromEntries(paramOrder.map((param, i) => [param, params[i]])); + ] + return Object.fromEntries(paramOrder.map((param, i) => [param, params[i]])) } diff --git a/src/strudel/superdough/zzfx_fork.mjs b/src/strudel/superdough/zzfx_fork.mjs index f3ee6a0..877eca7 100644 --- a/src/strudel/superdough/zzfx_fork.mjs +++ b/src/strudel/superdough/zzfx_fork.mjs @@ -1,4 +1,4 @@ -import { getAudioContext } from './audioContext.mjs'; +import { getAudioContext } from './audioContext.mjs' // https://github.com/KilledByAPixel/ZzFX/blob/master/ZzFX.js#L85C5-L180C6 // changes: replaced this.volume with 1 + using sampleRate from getAudioContext() @@ -22,14 +22,15 @@ export function buildSamples( delay = 0, sustainVolume = 1, decay = 0, - tremolo = 0, + tremolo = 0 ) { // init parameters let PI2 = Math.PI * 2, sampleRate = getAudioContext().sampleRate, sign = (v) => (v > 0 ? 1 : -1), startSlide = (slide *= (500 * PI2) / sampleRate / sampleRate), - startFrequency = (frequency *= ((1 + randomness * 2 * Math.random() - randomness) * PI2) / sampleRate), + startFrequency = (frequency *= + ((1 + randomness * 2 * Math.random() - randomness) * PI2) / sampleRate), b = [], t = 0, tm = 0, @@ -39,19 +40,19 @@ export function buildSamples( c = 0, s = 0, f, - length; + length // scale by sample rate - attack = attack * sampleRate + 9; // minimum attack to prevent pop - decay *= sampleRate; - sustain *= sampleRate; - release *= sampleRate; - delay *= sampleRate; - deltaSlide *= (500 * PI2) / sampleRate ** 3; - modulation *= PI2 / sampleRate; - pitchJump *= PI2 / sampleRate; - pitchJumpTime *= sampleRate; - repeatTime = (repeatTime * sampleRate) | 0; + attack = attack * sampleRate + 9 // minimum attack to prevent pop + decay *= sampleRate + sustain *= sampleRate + release *= sampleRate + delay *= sampleRate + deltaSlide *= (500 * PI2) / sampleRate ** 3 + modulation *= PI2 / sampleRate + pitchJump *= PI2 / sampleRate + pitchJumpTime *= sampleRate + repeatTime = (repeatTime * sampleRate) | 0 // generate waveform for (length = (attack + decay + sustain + release + delay) | 0; i < length; b[i++] = s) { @@ -65,7 +66,7 @@ export function buildSamples( : Math.max(Math.min(Math.tan(t), 1), -1) // 3 tan : 1 - (((((2 * t) / PI2) % 2) + 2) % 2) // 2 saw : 1 - 4 * Math.abs(Math.round(t / PI2) - t / PI2) // 1 triangle - : Math.sin(t); // 0 sin + : Math.sin(t) // 0 sin s = (repeatTime @@ -84,7 +85,7 @@ export function buildSamples( : i < length - delay // release ? ((length - i - delay) / release) * // release falloff sustainVolume // release volume - : 0); // post release + : 0) // post release s = delay ? s / 2 + @@ -93,28 +94,28 @@ export function buildSamples( : ((i < length - delay ? 1 : (length - i) / delay) * // release delay b[(i - delay) | 0]) / 2) - : s; // sample delay + : s // sample delay } f = (frequency += slide += deltaSlide) * // frequency - Math.cos(modulation * tm++); // modulation - t += f - f * noise * (1 - (((Math.sin(i) + 1) * 1e9) % 2)); // noise + Math.cos(modulation * tm++) // modulation + t += f - f * noise * (1 - (((Math.sin(i) + 1) * 1e9) % 2)) // noise if (j && ++j > pitchJumpTime) { // pitch jump - frequency += pitchJump; // apply pitch jump - startFrequency += pitchJump; // also apply to start - j = 0; // stop pitch jump time + frequency += pitchJump // apply pitch jump + startFrequency += pitchJump // also apply to start + j = 0 // stop pitch jump time } if (repeatTime && !(++r % repeatTime)) { // repeat - frequency = startFrequency; // reset frequency - slide = startSlide; // reset slide - j ||= 1; // reset pitch jump time + frequency = startFrequency // reset frequency + slide = startSlide // reset slide + j ||= 1 // reset pitch jump time } } - return b; + return b } diff --git a/src/strudel/supradough/.gitignore b/src/strudel/supradough/.gitignore deleted file mode 100644 index d21cbdf..0000000 --- a/src/strudel/supradough/.gitignore +++ /dev/null @@ -1 +0,0 @@ -pattern.wav diff --git a/src/strudel/supradough/README.md b/src/strudel/supradough/README.md deleted file mode 100644 index a8cfa84..0000000 --- a/src/strudel/supradough/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# supradough - -platform agnostic synth and sampler intended for live coding. a reimplementation of superdough. \ No newline at end of file diff --git a/src/strudel/supradough/dough-export.mjs b/src/strudel/supradough/dough-export.mjs deleted file mode 100644 index bd4b530..0000000 --- a/src/strudel/supradough/dough-export.mjs +++ /dev/null @@ -1,123 +0,0 @@ -// this is a poc of how a pattern can be rendered as a wav file using node -// run via: node dough-export.mjs -import fs from 'node:fs'; -import WavEncoder from 'wav-encoder'; -import { evalScope } from '@strudel/core'; -import { miniAllStrings } from '@strudel/mini'; -import { Dough } from './dough.mjs'; - -await evalScope( - import('@strudel/core'), - import('@strudel/mini'), - import('@strudel/tonal'), - // import('@strudel/tonal'), -); - -miniAllStrings(); // allows using single quotes for mini notation / skip transpilation - -let sampleRate = 48000, - cps = 0.4; - -/* await doughsamples('github:eddyflux/crate'); -await doughsamples('github:eddyflux/wax'); */ - -let pat = note('c,eb,g,') - .s('sine') - .press() - .add(note(24)) - .fmi(3) - .fmh(5.01) - .dec(0.4) - .delay('.6:<.12 .22>:.8') - .jux(press) - .rarely(add(note('12'))) - .lpf(400) - .lpq(0.2) - .lpd(0.4) - .lpenv(3) - .fmdecay(0.4) - .fmenv(1) - .postgain(0.6) - .stack(s('*8').dec(0.07).rarely(ply('2')).delay(0.5).hpf(sine.range(200, 2000).slow(4)).hpq(0.2)) - .stack( - s('[- white@3]*2') - .dec(0.4) - .hpf('<2000!3 <4000 8000>>*4') - .hpq(0.6) - .ply('<1 2>*4') - .postgain(0.5) - .delay(0.5) - .jux(rev) - .lpf(5000), - ) - .stack( - note('*2') - .s('square') - .lpf(sine.range(100, 300).slow(4)) - .lpe(1) - .segment(8) - .lpd(0.3) - .lpq(0.2) - .dec(0.2) - .speed('<1 2>') - .ply('<1 2>') - .postgain(1), - ) - .stack( - chord('') - .voicing() - .s('') - .clip(1) - .rel(0.4) - .vib('4:.2') - .gain(0.7) - .hpf(1200) - .fm(0.5) - .att(1) - .lpa(0.5) - .lpf(200) - .lpenv(4) - .chorus(0.8), - ) - .slow(1 / cps); - -let cycles = 30; -let seconds = cycles + 1; // 1s release tail -const haps = pat.queryArc(0, cycles); - -const dough = new Dough(sampleRate); - -console.log('spawn voices...'); -haps.forEach((hap) => { - hap.value._begin = Number(hap.whole.begin); - hap.value._duration = hap.duration /* / cps */; - dough.scheduleSpawn(hap.value); -}); -console.log(`render ${seconds}s long buffer, each dot is 1 second:`); -const buffers = [new Float32Array(seconds * sampleRate), new Float32Array(seconds * sampleRate)]; -let t = performance.now(); -while (dough.t <= buffers[0].length) { - dough.update(); - buffers[0][dough.t] = dough.out[0]; - buffers[1][dough.t] = dough.out[1]; - if (dough.t % sampleRate === 0) { - process.stdout.write('.'); - } -} -const took = (performance.now() - t) / 1000; -const load = (took / seconds) * 100; -const speed = (seconds / took).toFixed(2); -console.log(''); -console.log(`done! -rendered ${seconds}s in ${took.toFixed(2)}s -speed: ${speed}x -load: ${load.toFixed(2)}%`); - -const patternAudio = { - sampleRate, - channelData: buffers, -}; - -WavEncoder.encode(patternAudio).then((buffer) => { - fs.writeFileSync('pattern.wav', new Float32Array(buffer)); -}); diff --git a/src/strudel/supradough/dough-worklet.mjs b/src/strudel/supradough/dough-worklet.mjs index 18c316b..30c6a98 100644 --- a/src/strudel/supradough/dough-worklet.mjs +++ b/src/strudel/supradough/dough-worklet.mjs @@ -1,39 +1,39 @@ -import { Dough } from './dough.mjs'; +import { Dough } from './dough.mjs' -const clamp = (num, min, max) => Math.min(Math.max(num, min), max); +const clamp = (num, min, max) => Math.min(Math.max(num, min), max) class DoughProcessor extends AudioWorkletProcessor { constructor() { - super(); - this.dough = new Dough(sampleRate, currentTime); + super() + this.dough = new Dough(sampleRate, currentTime) this.port.onmessage = (event) => { if (event.data.spawn) { - this.dough.scheduleSpawn(event.data.spawn); + this.dough.scheduleSpawn(event.data.spawn) } else if (event.data.sample) { - this.dough.loadSample(event.data.sample, event.data.channels, event.data.sampleRate); + this.dough.loadSample(event.data.sample, event.data.channels, event.data.sampleRate) } else if (event.data.samples) { event.data.samples.forEach(([name, channels, sampleRate]) => { - this.dough.loadSample(name, channels, sampleRate); - }); + this.dough.loadSample(name, channels, sampleRate) + }) } else { - console.log('unrecognized event type', event.data); + console.log('unrecognized event type', event.data) } - }; + } } process(inputs, outputs, params) { if (this.disconnected) { - return false; + return false } - const output = outputs[0]; + const output = outputs[0] for (let i = 0; i < output[0].length; i++) { - this.dough.update(); + this.dough.update() for (let c = 0; c < output.length; c++) { //prevent speaker blowout via clipping if threshold exceeds - output[c][i] = clamp(this.dough.out[c], -1, 1); + output[c][i] = clamp(this.dough.out[c], -1, 1) } } - return true; // keep the audio processing going + return true // keep the audio processing going } } -registerProcessor('dough-processor', DoughProcessor); +registerProcessor('dough-processor', DoughProcessor) diff --git a/src/strudel/supradough/dough.mjs b/src/strudel/supradough/dough.mjs index 1cb9241..ebbd9cb 100644 --- a/src/strudel/supradough/dough.mjs +++ b/src/strudel/supradough/dough.mjs @@ -1,14 +1,15 @@ // this is dough, the superdough without dependencies // @ts-check +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ignore next line because sampleRate is unknown -const SAMPLE_RATE = typeof sampleRate !== 'undefined' ? sampleRate : 48000; -const PI_DIV_SR = Math.PI / SAMPLE_RATE; -const ISR = 1 / SAMPLE_RATE; +const SAMPLE_RATE = typeof sampleRate !== 'undefined' ? sampleRate : 48000 +const PI_DIV_SR = Math.PI / SAMPLE_RATE +const ISR = 1 / SAMPLE_RATE -let gainCurveFunc = (val) => Math.pow(val, 2); +let gainCurveFunc = (val) => Math.pow(val, 2) function applyGainCurve(val) { - return gainCurveFunc(val); + return gainCurveFunc(val) } /** @@ -21,9 +22,9 @@ function applyGainCurve(val) { * @returns {number} Crossfaded output value. */ function crossfade(a, b, m) { - const aGain = Math.sin((1 - m) * 0.5 * Math.PI); - const bGain = Math.sin(m * 0.5 * Math.PI); - return a * aGain + b * bGain; + const aGain = Math.sin((1 - m) * 0.5 * Math.PI) + const bGain = Math.sin(m * 0.5 * Math.PI) + return a * aGain + b * bGain } // function setGainCurve(newGainCurveFunc) { @@ -31,337 +32,341 @@ function crossfade(a, b, m) { // } // https://garten.salat.dev/audio-DSP/oscillators.html export class SineOsc { - phase = 0; + phase = 0 update(freq) { - const value = Math.sin(this.phase * 2 * Math.PI); - this.phase = (this.phase + freq / SAMPLE_RATE) % 1; - return value; + const value = Math.sin(this.phase * 2 * Math.PI) + this.phase = (this.phase + freq / SAMPLE_RATE) % 1 + return value } } export class ZawOsc { - phase = 0; + phase = 0 update(freq) { - this.phase += ISR * freq; - return (this.phase % 1) * 2 - 1; + this.phase += ISR * freq + return (this.phase % 1) * 2 - 1 } } function polyBlep(t, dt) { // 0 <= t < 1 if (t < dt) { - t /= dt; + t /= dt // 2 * (t - t^2/2 - 0.5) - return t + t - t * t - 1; + return t + t - t * t - 1 } // -1 < t < 0 if (t > 1 - dt) { - t = (t - 1) / dt; + t = (t - 1) / dt // 2 * (t^2/2 + t + 0.5) - return t * t + t + t + 1; + return t * t + t + t + 1 } // 0 otherwise - return 0; + return 0 } export class SawOsc { constructor(props = {}) { - this.phase = props.phase ?? 0; + this.phase = props.phase ?? 0 } update(freq) { - const dt = freq / SAMPLE_RATE; - let p = polyBlep(this.phase, dt); - let s = 2 * this.phase - 1 - p; - this.phase += dt; + const dt = freq / SAMPLE_RATE + let p = polyBlep(this.phase, dt) + let s = 2 * this.phase - 1 - p + this.phase += dt if (this.phase > 1) { - this.phase -= 1; + this.phase -= 1 } - return s; + return s } } function getUnisonDetune(unison, detune, voiceIndex) { if (unison < 2) { - return 0; + return 0 } const lerp = (a, b, n) => { - return n * (b - a) + a; - }; - return lerp(-detune * 0.5, detune * 0.5, voiceIndex / (unison - 1)); + return n * (b - a) + a + } + return lerp(-detune * 0.5, detune * 0.5, voiceIndex / (unison - 1)) } function applySemitoneDetuneToFrequency(frequency, detune) { - return frequency * Math.pow(2, detune / 12); + return frequency * Math.pow(2, detune / 12) } export class SupersawOsc { constructor(props = {}) { //TODO: figure out a good way to pass in these params - this.voices = props.voices ?? 5; - this.freqspread = props.freqspread ?? 0.2; - this.panspread = props.panspread ?? 0.4; - this.phase = new Float32Array(this.voices).map(() => Math.random()); + this.voices = props.voices ?? 5 + this.freqspread = props.freqspread ?? 0.2 + this.panspread = props.panspread ?? 0.4 + this.phase = new Float32Array(this.voices).map(() => Math.random()) } update(freq) { - const gain1 = Math.sqrt(1 - this.panspread); - const gain2 = Math.sqrt(this.panspread); - let sl = 0; - let sr = 0; + const gain1 = Math.sqrt(1 - this.panspread) + const gain2 = Math.sqrt(this.panspread) + let sl = 0 + let sr = 0 for (let n = 0; n < this.voices; n++) { - const freqAdjusted = applySemitoneDetuneToFrequency(freq, getUnisonDetune(this.voices, this.freqspread, n)); - const dt = freqAdjusted / SAMPLE_RATE; - const isOdd = (n & 1) == 1; - let gainL = gain1; - let gainR = gain2; + const freqAdjusted = applySemitoneDetuneToFrequency( + freq, + getUnisonDetune(this.voices, this.freqspread, n) + ) + const dt = freqAdjusted / SAMPLE_RATE + const isOdd = (n & 1) == 1 + let gainL = gain1 + let gainR = gain2 // invert right and left gain if (isOdd) { - gainL = gain2; - gainR = gain1; + gainL = gain2 + gainR = gain1 } - let p = polyBlep(this.phase[n], dt); - let s = 2 * this.phase[n] - 1 - p; - sl = sl + s * gainL; - sr = sr + s * gainL; + let p = polyBlep(this.phase[n], dt) + let s = 2 * this.phase[n] - 1 - p + sl = sl + s * gainL + sr = sr + s * gainL - this.phase[n] += dt; + this.phase[n] += dt if (this.phase[n] > 1) { - this.phase[n] -= 1; + this.phase[n] -= 1 } } - return sl + sr; + return sl + sr //TODO: make stereo // return [sl, sr]; } } export class TriOsc { - phase = 0; + phase = 0 update(freq) { - this.phase += ISR * freq; - let phase = this.phase % 1; - let value = phase < 0.5 ? 2 * phase : 1 - 2 * (phase - 0.5); - return value * 2 - 1; + this.phase += ISR * freq + let phase = this.phase % 1 + let value = phase < 0.5 ? 2 * phase : 1 - 2 * (phase - 0.5) + return value * 2 - 1 } } export class TwoPoleFilter { - s0 = 0; - s1 = 0; + s0 = 0 + s1 = 0 update(s, cutoff, resonance = 0) { // Out of bound values can produce NaNs - resonance = Math.max(resonance, 0); + resonance = Math.max(resonance, 0) - cutoff = Math.min(cutoff, 20000); - const c = 2 * Math.sin(cutoff * PI_DIV_SR); + cutoff = Math.min(cutoff, 20000) + const c = 2 * Math.sin(cutoff * PI_DIV_SR) - const r = Math.pow(0.5, (resonance + 0.125) / 0.125); - const mrc = 1 - r * c; + const r = Math.pow(0.5, (resonance + 0.125) / 0.125) + const mrc = 1 - r * c - this.s0 = mrc * this.s0 - c * this.s1 + c * s; // bpf - this.s1 = mrc * this.s1 + c * this.s0; // lpf - return this.s1; // return lpf by default + this.s0 = mrc * this.s0 - c * this.s1 + c * s // bpf + this.s1 = mrc * this.s1 + c * this.s0 // lpf + return this.s1 // return lpf by default } } class PulseOsc { constructor(phase = 0) { - this.phase = phase; + this.phase = phase } saw(offset, dt) { - let phase = (this.phase + offset) % 1; - let p = polyBlep(phase, dt); - return 2 * phase - 1 - p; + let phase = (this.phase + offset) % 1 + let p = polyBlep(phase, dt) + return 2 * phase - 1 - p } update(freq, pw = 0.5) { - const dt = freq / SAMPLE_RATE; - let pulse = this.saw(0, dt) - this.saw(pw, dt); - this.phase = (this.phase + dt) % 1; - return pulse + pw * 2 - 1; + const dt = freq / SAMPLE_RATE + let pulse = this.saw(0, dt) - this.saw(pw, dt) + this.phase = (this.phase + dt) % 1 + return pulse + pw * 2 - 1 } } // non bandlimited (has aliasing) export class PulzeOsc { - phase = 0; + phase = 0 update(freq, duty = 0.5) { - this.phase += ISR * freq; - let cyclePos = this.phase % 1; - return cyclePos < duty ? 1 : -1; + this.phase += ISR * freq + let cyclePos = this.phase % 1 + return cyclePos < duty ? 1 : -1 } } export class Dust { - update = (density) => (Math.random() < density * ISR ? Math.random() : 0); + update = (density) => (Math.random() < density * ISR ? Math.random() : 0) } export class WhiteNoise { update() { - return Math.random() * 2 - 1; + return Math.random() * 2 - 1 } } export class BrownNoise { constructor() { - this.out = 0; + this.out = 0 } update() { - let white = Math.random() * 2 - 1; - this.out = (this.out + 0.02 * white) / 1.02; - return this.out; + let white = Math.random() * 2 - 1 + this.out = (this.out + 0.02 * white) / 1.02 + return this.out } } export class PinkNoise { constructor() { - this.b0 = 0; - this.b1 = 0; - this.b2 = 0; - this.b3 = 0; - this.b4 = 0; - this.b5 = 0; - this.b6 = 0; + this.b0 = 0 + this.b1 = 0 + this.b2 = 0 + this.b3 = 0 + this.b4 = 0 + this.b5 = 0 + this.b6 = 0 } update() { - const white = Math.random() * 2 - 1; + const white = Math.random() * 2 - 1 - this.b0 = 0.99886 * this.b0 + white * 0.0555179; - this.b1 = 0.99332 * this.b1 + white * 0.0750759; - this.b2 = 0.969 * this.b2 + white * 0.153852; - this.b3 = 0.8665 * this.b3 + white * 0.3104856; - this.b4 = 0.55 * this.b4 + white * 0.5329522; - this.b5 = -0.7616 * this.b5 - white * 0.016898; + this.b0 = 0.99886 * this.b0 + white * 0.0555179 + this.b1 = 0.99332 * this.b1 + white * 0.0750759 + this.b2 = 0.969 * this.b2 + white * 0.153852 + this.b3 = 0.8665 * this.b3 + white * 0.3104856 + this.b4 = 0.55 * this.b4 + white * 0.5329522 + this.b5 = -0.7616 * this.b5 - white * 0.016898 - const pink = this.b0 + this.b1 + this.b2 + this.b3 + this.b4 + this.b5 + this.b6 + white * 0.5362; - this.b6 = white * 0.115926; + const pink = + this.b0 + this.b1 + this.b2 + this.b3 + this.b4 + this.b5 + this.b6 + white * 0.5362 + this.b6 = white * 0.115926 - return pink * 0.11; + return pink * 0.11 } } export class Impulse { - phase = 1; + phase = 1 update(freq) { - this.phase += ISR * freq; - let v = this.phase >= 1 ? 1 : 0; - this.phase = this.phase % 1; - return v; + this.phase += ISR * freq + let v = this.phase >= 1 ? 1 : 0 + this.phase = this.phase % 1 + return v } } export class ClockDiv { - inSgn = true; - outSgn = true; - clockCnt = 0; + inSgn = true + outSgn = true + clockCnt = 0 update(clock, factor) { - let curSgn = clock > 0; + let curSgn = clock > 0 if (this.inSgn != curSgn) { - this.clockCnt++; + this.clockCnt++ if (this.clockCnt >= factor) { - this.clockCnt = 0; - this.outSgn = !this.outSgn; + this.clockCnt = 0 + this.outSgn = !this.outSgn } } - this.inSgn = curSgn; - return this.outSgn ? 1 : -1; + this.inSgn = curSgn + return this.outSgn ? 1 : -1 } } export class Hold { - value = 0; - trigSgn = false; + value = 0 + trigSgn = false update(input, trig) { - if (!this.trigSgn && trig > 0) this.value = input; - this.trigSgn = trig > 0; - return this.value; + if (!this.trigSgn && trig > 0) this.value = input + this.trigSgn = trig > 0 + return this.value } } function lerp(x, y0, y1, exponent = 1) { - if (x <= 0) return y0; - if (x >= 1) return y1; + if (x <= 0) return y0 + if (x >= 1) return y1 - let curvedX; + let curvedX if (exponent === 0) { - curvedX = x; // linear + curvedX = x // linear } else if (exponent > 0) { - curvedX = Math.pow(x, exponent); // ease-in + curvedX = Math.pow(x, exponent) // ease-in } else { - curvedX = 1 - Math.pow(1 - x, -exponent); // ease-out + curvedX = 1 - Math.pow(1 - x, -exponent) // ease-out } - return y0 + (y1 - y0) * curvedX; + return y0 + (y1 - y0) * curvedX } export class ADSR { constructor(props = {}) { - this.state = 'off'; - this.startTime = 0; - this.startVal = 0; - this.decayCurve = props.decayCurve ?? 1; + this.state = 'off' + this.startTime = 0 + this.startVal = 0 + this.decayCurve = props.decayCurve ?? 1 } update(curTime, gate, attack, decay, susVal, release) { switch (this.state) { case 'off': { if (gate > 0) { - this.state = 'attack'; - this.startTime = curTime; - this.startVal = 0; + this.state = 'attack' + this.startTime = curTime + this.startVal = 0 } - return 0; + return 0 } case 'attack': { - let time = curTime - this.startTime; + let time = curTime - this.startTime if (time > attack) { - this.state = 'decay'; - this.startTime = curTime; - return 1; + this.state = 'decay' + this.startTime = curTime + return 1 } - return lerp(time / attack, this.startVal, 1, 1); + return lerp(time / attack, this.startVal, 1, 1) } case 'decay': { - let time = curTime - this.startTime; - let curVal = lerp(time / decay, 1, susVal, -this.decayCurve); + let time = curTime - this.startTime + let curVal = lerp(time / decay, 1, susVal, -this.decayCurve) if (gate <= 0) { - this.state = 'release'; - this.startTime = curTime; - this.startVal = curVal; - return curVal; + this.state = 'release' + this.startTime = curTime + this.startVal = curVal + return curVal } if (time > decay) { - this.state = 'sustain'; - this.startTime = curTime; - return susVal; + this.state = 'sustain' + this.startTime = curTime + return susVal } - return curVal; + return curVal } case 'sustain': { if (gate <= 0) { - this.state = 'release'; - this.startTime = curTime; - this.startVal = susVal; + this.state = 'release' + this.startTime = curTime + this.startVal = susVal } - return susVal; + return susVal } case 'release': { - let time = curTime - this.startTime; + let time = curTime - this.startTime if (time > release) { - this.state = 'off'; - return 0; + this.state = 'off' + return 0 } - let curVal = lerp(time / release, this.startVal, 0, -this.decayCurve); + let curVal = lerp(time / release, this.startVal, 0, -this.decayCurve) if (gate > 0) { - this.state = 'attack'; - this.startTime = curTime; - this.startVal = curVal; + this.state = 'attack' + this.startTime = curTime + this.startVal = curVal } - return curVal; + return curVal } } - throw 'invalid envelope state'; + throw 'invalid envelope state' } } @@ -369,208 +374,214 @@ export class ADSR { impulse(1).ad(.1).mul(sine(200)) .add(x=>x.delay(.1).mul(.8)) .out()*/ -const MAX_DELAY_TIME = 10; +const MAX_DELAY_TIME = 10 export class PitchDelay { - lpf = new TwoPoleFilter(); + lpf = new TwoPoleFilter() constructor(_props = {}) { - this.buffer = new Float32Array(MAX_DELAY_TIME * SAMPLE_RATE); - this.writeIdx = 0; - this.readIdx = 0; - this.numSamples = 0; + this.buffer = new Float32Array(MAX_DELAY_TIME * SAMPLE_RATE) + this.writeIdx = 0 + this.readIdx = 0 + this.numSamples = 0 } write(s, delayTime) { // Calculate how far in the past to read - this.numSamples = Math.min(Math.floor(SAMPLE_RATE * delayTime), this.buffer.length - 1); - this.writeIdx = (this.writeIdx + 1) % this.numSamples; - this.buffer[this.writeIdx] = s; - this.readIdx = this.writeIdx - this.numSamples + 1; + this.numSamples = Math.min(Math.floor(SAMPLE_RATE * delayTime), this.buffer.length - 1) + this.writeIdx = (this.writeIdx + 1) % this.numSamples + this.buffer[this.writeIdx] = s + this.readIdx = this.writeIdx - this.numSamples + 1 // If past the start of the buffer, wrap around (Q: is this possible?) - if (this.readIdx < 0) this.readIdx += this.numSamples; + if (this.readIdx < 0) this.readIdx += this.numSamples } update(input, delayTime, speed = 1) { - this.write(input, delayTime); - let index = this.readIdx; + this.write(input, delayTime) + let index = this.readIdx if (speed < 0) { - index = this.numSamples - Math.floor(Math.abs(this.readIdx * speed) % this.numSamples); + index = this.numSamples - Math.floor(Math.abs(this.readIdx * speed) % this.numSamples) } else { - index = Math.floor(this.readIdx * speed) % this.numSamples; + index = Math.floor(this.readIdx * speed) % this.numSamples } - const s = this.lpf.update(this.buffer[index], 0.9, 0); + const s = this.lpf.update(this.buffer[index], 0.9, 0) - return s; + return s } } export class Delay { - writeIdx = 0; - readIdx = 0; - buffer = new Float32Array(MAX_DELAY_TIME * SAMPLE_RATE); //.fill(0) + writeIdx = 0 + readIdx = 0 + buffer = new Float32Array(MAX_DELAY_TIME * SAMPLE_RATE) //.fill(0) write(s, delayTime) { - this.writeIdx = (this.writeIdx + 1) % this.buffer.length; - this.buffer[this.writeIdx] = s; + this.writeIdx = (this.writeIdx + 1) % this.buffer.length + this.buffer[this.writeIdx] = s // Calculate how far in the past to read - let numSamples = Math.min(Math.floor(SAMPLE_RATE * delayTime), this.buffer.length - 1); - this.readIdx = this.writeIdx - numSamples; + let numSamples = Math.min(Math.floor(SAMPLE_RATE * delayTime), this.buffer.length - 1) + this.readIdx = this.writeIdx - numSamples // If past the start of the buffer, wrap around - if (this.readIdx < 0) this.readIdx += this.buffer.length; + if (this.readIdx < 0) this.readIdx += this.buffer.length } update(input, delayTime) { - this.write(input, delayTime); - return this.buffer[this.readIdx]; + this.write(input, delayTime) + return this.buffer[this.readIdx] } } //TODO: Figure out why clicking at the start off the buffer export class Chorus { - delay = new Delay(); - modulator = new TriOsc(); + delay = new Delay() + modulator = new TriOsc() update(input, mix, delayTime, modulationFreq, modulationDepth) { - const m = this.modulator.update(modulationFreq) * modulationDepth; - const c = this.delay.update(input, delayTime * (1 + m)); - return crossfade(input, c, mix); + const m = this.modulator.update(modulationFreq) * modulationDepth + const c = this.delay.update(input, delayTime * (1 + m)) + return crossfade(input, c, mix) } } export class Fold { update(input = 0, rate = 0) { - if (rate < 0) rate = 0; - rate = rate + 1; - input = input * rate; - return 4 * (Math.abs(0.25 * input + 0.25 - Math.round(0.25 * input + 0.25)) - 0.25); + if (rate < 0) rate = 0 + rate = rate + 1 + input = input * rate + return 4 * (Math.abs(0.25 * input + 0.25 - Math.round(0.25 * input + 0.25)) - 0.25) } } export class Lag { - lagUnit = 4410; - s = 0; + lagUnit = 4410 + s = 0 update(input, rate) { // Remap so the useful range is around [0, 1] - rate = rate * this.lagUnit; - if (rate < 1) rate = 1; - this.s += (1 / rate) * (input - this.s); - return this.s; + rate = rate * this.lagUnit + if (rate < 1) rate = 1 + this.s += (1 / rate) * (input - this.s) + return this.s } } export class Slew { - last = 0; + last = 0 update(input, up, dn) { - const upStep = up * ISR; - const downStep = dn * ISR; - let delta = input - this.last; + const upStep = up * ISR + const downStep = dn * ISR + let delta = input - this.last if (delta > upStep) { - delta = upStep; + delta = upStep } else if (delta < -downStep) { - delta = -downStep; + delta = -downStep } - this.last += delta; - return this.last; + this.last += delta + return this.last } } // overdrive style distortion (adapted from noisecraft) currently unused export function applyDistortion(x, amount) { - amount = Math.min(Math.max(amount, 0), 1); - amount -= 0.01; - var k = (2 * amount) / (1 - amount); - var y = ((1 + k) * x) / (1 + k * Math.abs(x)); - return y; + amount = Math.min(Math.max(amount, 0), 1) + amount -= 0.01 + var k = (2 * amount) / (1 - amount) + var y = ((1 + k) * x) / (1 + k * Math.abs(x)) + return y } export class Sequence { - clockSgn = true; - step = 0; - first = true; + clockSgn = true + step = 0 + first = true update(clock, ...ins) { if (!this.clockSgn && clock > 0) { - this.step = (this.step + 1) % ins.length; - this.clockSgn = clock > 0; - return 0; // set first sample to zero to retrigger gates on step change... + this.step = (this.step + 1) % ins.length + this.clockSgn = clock > 0 + return 0 // set first sample to zero to retrigger gates on step change... } - this.clockSgn = clock > 0; - return ins[this.step]; + this.clockSgn = clock > 0 + return ins[this.step] } } // sample rate bit crusher export class Coarse { - hold = 0; - t = 0; + hold = 0 + t = 0 update(input, coarse) { if (this.t++ % coarse === 0) { - this.t = 0; - this.hold = input; + this.t = 0 + this.hold = input } - return this.hold; + return this.hold } } // amplitude bit crusher export class Crush { update(input, crush) { - crush = Math.max(1, crush); - const x = Math.pow(2, crush - 1); - return Math.round(input * x) / x; + crush = Math.max(1, crush) + const x = Math.pow(2, crush - 1) + return Math.round(input * x) / x } } // this is the distort from superdough export class Distort { update(input, distort = 0, postgain = 1) { - postgain = Math.max(0.001, Math.min(1, postgain)); - const shape = Math.expm1(distort); - return (((1 + shape) * input) / (1 + shape * Math.abs(input))) * postgain; + postgain = Math.max(0.001, Math.min(1, postgain)) + const shape = Math.expm1(distort) + return (((1 + shape) * input) / (1 + shape * Math.abs(input))) * postgain } } // distortion could be expressed as a function, because it's stateless export class BufferPlayer { - static samples = new Map(); // string -> { channels, sampleRate } - buffer; // Float32Array - sampleRate; - pos = 0; - sampleFreq = note2freq(); + static samples = new Map() // string -> { channels, sampleRate } + buffer // Float32Array + sampleRate + pos = 0 + sampleFreq = note2freq() constructor(buffer, sampleRate, normalize) { - this.buffer = buffer; - this.sampleRate = sampleRate; - this.duration = this.buffer.length / this.sampleRate; - this.speed = SAMPLE_RATE / this.sampleRate; + this.buffer = buffer + this.sampleRate = sampleRate + this.duration = this.buffer.length / this.sampleRate + this.speed = SAMPLE_RATE / this.sampleRate if (normalize) { // this will make the buffer last 1s if freq = sampleFreq // it's useful to loop samples (e.g. fit function) - this.speed *= this.duration; + this.speed *= this.duration } } update(freq) { if (this.pos >= this.buffer.length) { - return 0; + return 0 } - const speed = (freq / this.sampleFreq) * this.speed; - let s = this.buffer[Math.floor(this.pos)]; - this.pos = this.pos + speed; - return s; + const speed = (freq / this.sampleFreq) * this.speed + let s = this.buffer[Math.floor(this.pos)] + this.pos = this.pos + speed + return s } } export function _rangex(sig, min, max) { - let logmin = Math.log(min); - let range = Math.log(max) - logmin; - const unipolar = (sig + 1) / 2; - return Math.exp(unipolar * range + logmin); + let logmin = Math.log(min) + let range = Math.log(max) - logmin + const unipolar = (sig + 1) / 2 + return Math.exp(unipolar * range + logmin) } // duplicate export const getADSR = (params, curve = 'linear', defaultValues) => { - const envmin = curve === 'exponential' ? 0.001 : 0.001; - const releaseMin = 0.01; - const envmax = 1; - const [a, d, s, r] = params; + const envmin = curve === 'exponential' ? 0.001 : 0.001 + const releaseMin = 0.01 + const envmax = 1 + const [a, d, s, r] = params if (a == null && d == null && s == null && r == null) { - return defaultValues ?? [envmin, envmin, envmax, releaseMin]; + return defaultValues ?? [envmin, envmin, envmax, releaseMin] } - const sustain = s != null ? s : (a != null && d == null) || (a == null && d == null) ? envmax : envmin; - return [Math.max(a ?? 0, envmin), Math.max(d ?? 0, envmin), Math.min(sustain, envmax), Math.max(r ?? 0, releaseMin)]; -}; + const sustain = + s != null ? s : (a != null && d == null) || (a == null && d == null) ? envmax : envmin + return [ + Math.max(a ?? 0, envmin), + Math.max(d ?? 0, envmin), + Math.min(sustain, envmax), + Math.max(r ?? 0, releaseMin), + ] +} let shapes = { sine: SineOsc, @@ -590,7 +601,7 @@ let shapes = { white: WhiteNoise, brown: BrownNoise, pink: PinkNoise, -}; +} const defaultDefaultValues = { chorus: 0, @@ -627,388 +638,448 @@ const defaultDefaultValues = { fmenv: 0, // differs from superdough speed: 1, pw: 0.5, -}; +} -let getDefaultValue = (key) => defaultDefaultValues[key]; +let getDefaultValue = (key) => defaultDefaultValues[key] -const chromas = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }; -const accs = { '#': 1, b: -1, s: 1, f: -1 }; +const chromas = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 } +const accs = { '#': 1, b: -1, s: 1, f: -1 } const note2midi = (note, defaultOctave = 3) => { let [pc, acc = '', oct = ''] = String(note) .match(/^([a-gA-G])([#bsf]*)([0-9]*)$/) - ?.slice(1) || []; + ?.slice(1) || [] if (!pc) { - throw new Error('not a note: "' + note + '"'); + throw new Error('not a note: "' + note + '"') } - const chroma = chromas[pc.toLowerCase()]; - const offset = acc?.split('').reduce((o, char) => o + accs[char], 0) || 0; - const octave = Number(oct || defaultOctave); - return (octave + 1) * 12 + chroma + offset; -}; -const midi2freq = (midi) => Math.pow(2, (midi - 69) / 12) * 440; + const chroma = chromas[pc.toLowerCase()] + const offset = acc?.split('').reduce((o, char) => o + accs[char], 0) || 0 + const octave = Number(oct || defaultOctave) + return (octave + 1) * 12 + chroma + offset +} +const midi2freq = (midi) => Math.pow(2, (midi - 69) / 12) * 440 const note2freq = (note) => { - note = note || getDefaultValue('note'); + note = note || getDefaultValue('note') if (typeof note === 'string') { - note = note2midi(note, 3); // e.g. c3 => 48 + note = note2midi(note, 3) // e.g. c3 => 48 } - return midi2freq(note); -}; + return midi2freq(note) +} export class DoughVoice { /** @type {number} */ - id = 0; + id = 0 /** @type {number[]} */ - out = [0, 0]; + out = [0, 0] /** @type {number | undefined} */ - attack; + attack /** @type {number | undefined} */ - decay; + decay /** @type {number | undefined} */ - sustain; + sustain /** @type {number} */ - release; + release /** @type {number} */ - _begin; + _begin /** @type {number} */ - _duration; + _duration /** @type {any} */ - _sound; + _sound /** @type {number} */ - _channels = 1; + _channels = 1 /** @type {BufferPlayer[] | undefined} */ - _buffers; + _buffers /** @type {string | undefined} */ - unit; + unit /** @type {ADSR | undefined} */ - _penv; + _penv /** @type {number | undefined} */ - penv; + penv /** @type {number | undefined} */ - pattack; + pattack /** @type {number | undefined} */ - pdecay; + pdecay /** @type {number | undefined} */ - psustain; + psustain /** @type {number | undefined} */ - prelease; + prelease /** @type {number | undefined} */ - vib; + vib - _vib; + _vib /** @type {number | undefined} */ - vibmod; + vibmod /** @type {SineOsc | undefined} */ - _fm; + _fm /** @type {number | undefined} */ - fmh; + fmh /** @type {number | undefined} */ - fmi; + fmi /** @type {ADSR | undefined} */ - _fmenv; + _fmenv /** @type {number | undefined} */ - fmattack; + fmattack /** @type {number | undefined} */ - fmdecay; + fmdecay /** @type {number | undefined} */ - fmsustain; + fmsustain /** @type {number | undefined} */ - fmrelease; + fmrelease /** @type {ADSR | undefined} */ - _lpenv; - lpenv; + _lpenv + lpenv /** @type {number | undefined} */ - lpattack; + lpattack /** @type {number | undefined} */ - lpdecay; + lpdecay /** @type {number | undefined} */ - lpsustain; + lpsustain /** @type {number | undefined} */ - lprelease; + lprelease /** @type {ADSR | undefined} */ - _hpenv; + _hpenv /** @type {number | undefined} */ - hpenv; + hpenv /** @type {number | undefined} */ - hpattack; + hpattack /** @type {number | undefined} */ - hpdecay; + hpdecay /** @type {number | undefined} */ - hpsustain; + hpsustain /** @type {number | undefined} */ - hprelease; + hprelease /** @type {ADSR | undefined} */ - _bpenv; + _bpenv /** @type {number | undefined} */ - bpenv; + bpenv /** @type {number | undefined} */ - bpattack; + bpattack /** @type {number | undefined} */ - bpdecay; + bpdecay /** @type {number | undefined} */ - bpsustain; + bpsustain /** @type {number | undefined} */ - bprelease; + bprelease /** @type {number | undefined} */ - cutoff; + cutoff /** @type {number | undefined} */ - hcutoff; + hcutoff /** @type {number | undefined} */ - bandf; + bandf /** @type {number | undefined} */ - coarse; + coarse /** @type {number | undefined} */ - crush; + crush /** @type {number | undefined} */ - distort; + distort /** @type {number} */ - freq; + freq /** @type {string | undefined} */ - note; + note /** @type {TwoPoleFilter[] | null | undefined} */ - _lpf; + _lpf /** @type {TwoPoleFilter[] | null | undefined} */ - _hpf; + _hpf /** @type {TwoPoleFilter[] | null | undefined} */ - _bpf; + _bpf /** @type {Chorus[] | null | undefined} */ - _chorus; + _chorus /** @type {Coarse[] | null | undefined} */ - _coarse; + _coarse /** @type {Crush[] | null | undefined} */ - _crush; + _crush /** @type {Distort[] | null | undefined} */ - _distort; + _distort /** * @param {DoughVoice} value */ constructor(value) { // mandatory controls - this.freq ??= note2freq(value.note); - this._begin = value._begin; - this._duration = value._duration; - this.release = value.release ?? 0; + this.freq ??= note2freq(value.note) + this._begin = value._begin + this._duration = value._duration + this.release = value.release ?? 0 // the rest.. we use $ for readability - let $ = this; - Object.assign($, value); - $.s = $.s ?? getDefaultValue('s'); - $.gain = applyGainCurve($.gain ?? getDefaultValue('gain')); - $.velocity = applyGainCurve($.velocity ?? getDefaultValue('velocity')); - $.postgain = applyGainCurve($.postgain ?? getDefaultValue('postgain')); - $.density = $.density ?? getDefaultValue('density'); - $.fanchor = $.fanchor ?? getDefaultValue('fanchor'); - $.drive = $.drive ?? 0.69; - $.phaserdepth = $.phaserdepth ?? getDefaultValue('phaserdepth'); - $.shapevol = applyGainCurve($.shapevol ?? getDefaultValue('shapevol')); - $.distortvol = applyGainCurve($.distortvol ?? getDefaultValue('distortvol')); - $.i = $.i ?? getDefaultValue('i'); - $.chorus = $.chorus ?? getDefaultValue('chorus'); - $.fft = $.fft ?? getDefaultValue('fft'); - $.pan = $.pan ?? getDefaultValue('pan'); - $.orbit = $.orbit ?? getDefaultValue('orbit'); - $.fmenv = $.fmenv ?? getDefaultValue('fmenv'); - $.resonance = $.resonance ?? getDefaultValue('resonance'); - $.hresonance = $.hresonance ?? getDefaultValue('hresonance'); - $.bandq = $.bandq ?? getDefaultValue('bandq'); - $.speed = $.speed ?? getDefaultValue('speed'); - $.pw = $.pw ?? getDefaultValue('pw'); + // eslint-disable-next-line @typescript-eslint/no-this-alias + let $ = this + Object.assign($, value) + $.s = $.s ?? getDefaultValue('s') + $.gain = applyGainCurve($.gain ?? getDefaultValue('gain')) + $.velocity = applyGainCurve($.velocity ?? getDefaultValue('velocity')) + $.postgain = applyGainCurve($.postgain ?? getDefaultValue('postgain')) + $.density = $.density ?? getDefaultValue('density') + $.fanchor = $.fanchor ?? getDefaultValue('fanchor') + $.drive = $.drive ?? 0.69 + $.phaserdepth = $.phaserdepth ?? getDefaultValue('phaserdepth') + $.shapevol = applyGainCurve($.shapevol ?? getDefaultValue('shapevol')) + $.distortvol = applyGainCurve($.distortvol ?? getDefaultValue('distortvol')) + $.i = $.i ?? getDefaultValue('i') + $.chorus = $.chorus ?? getDefaultValue('chorus') + $.fft = $.fft ?? getDefaultValue('fft') + $.pan = $.pan ?? getDefaultValue('pan') + $.orbit = $.orbit ?? getDefaultValue('orbit') + $.fmenv = $.fmenv ?? getDefaultValue('fmenv') + $.resonance = $.resonance ?? getDefaultValue('resonance') + $.hresonance = $.hresonance ?? getDefaultValue('hresonance') + $.bandq = $.bandq ?? getDefaultValue('bandq') + $.speed = $.speed ?? getDefaultValue('speed') + $.pw = $.pw ?? getDefaultValue('pw') + ;[$.attack, $.decay, $.sustain, $.release] = getADSR([$.attack, $.decay, $.sustain, $.release]) - [$.attack, $.decay, $.sustain, $.release] = getADSR([$.attack, $.decay, $.sustain, $.release]); - - $._holdEnd = $._begin + $._duration; // needed for gate - $._end = $._holdEnd + $.release + 0.01; // needed for despawn + $._holdEnd = $._begin + $._duration // needed for gate + $._end = $._holdEnd + $.release + 0.01 // needed for despawn if ($.fmi && ($.s === 'saw' || $.s === 'sawtooth')) { - $.s = 'zaw'; // polyblepped saw when fm is applied + $.s = 'zaw' // polyblepped saw when fm is applied } if (shapes[$.s]) { - const SourceClass = shapes[$.s]; - $._sound = new SourceClass(); - $._channels = 1; + const SourceClass = shapes[$.s] + $._sound = new SourceClass() + $._channels = 1 } else if (BufferPlayer.samples.has($.s)) { - const sample = BufferPlayer.samples.get($.s); - $._buffers = []; - $._channels = sample.channels.length; + const sample = BufferPlayer.samples.get($.s) + $._buffers = [] + $._channels = sample.channels.length for (let i = 0; i < $._channels; i++) { - $._buffers.push(new BufferPlayer(sample.channels[i], sample.sampleRate, $.unit === 'c')); // tbd unit === 'c' + $._buffers.push(new BufferPlayer(sample.channels[i], sample.sampleRate, $.unit === 'c')) // tbd unit === 'c' } } else { - console.warn('sound not loaded', $.s); + console.warn('sound not loaded', $.s) } if ($.penv) { - $._penv = new ADSR({ decayCurve: 4 }); - [$.pattack, $.pdecay, $.psustain, $.prelease] = getADSR([$.pattack, $.pdecay, $.psustain, $.prelease]); + $._penv = new ADSR({ decayCurve: 4 }) + ;[$.pattack, $.pdecay, $.psustain, $.prelease] = getADSR([ + $.pattack, + $.pdecay, + $.psustain, + $.prelease, + ]) } if ($.vib) { - $._vib = new SineOsc(); - $.vibmod = $.vibmod ?? getDefaultValue('vibmod'); + $._vib = new SineOsc() + $.vibmod = $.vibmod ?? getDefaultValue('vibmod') } if ($.fmi) { - $._fm = new SineOsc(); - $.fmh = $.fmh ?? getDefaultValue('fmh'); + $._fm = new SineOsc() + $.fmh = $.fmh ?? getDefaultValue('fmh') if ($.fmenv) { - $._fmenv = new ADSR({ decayCurve: 2 }); - [$.fmattack, $.fmdecay, $.fmsustain, $.fmrelease] = getADSR([$.fmattack, $.fmdecay, $.fmsustain, $.fmrelease]); + $._fmenv = new ADSR({ decayCurve: 2 }) + ;[$.fmattack, $.fmdecay, $.fmsustain, $.fmrelease] = getADSR([ + $.fmattack, + $.fmdecay, + $.fmsustain, + $.fmrelease, + ]) } } // gain envelope - $._adsr = new ADSR({ decayCurve: 2 }); + $._adsr = new ADSR({ decayCurve: 2 }) // delay - $.delay = applyGainCurve($.delay ?? getDefaultValue('delay')); - $.delayfeedback = $.delayfeedback ?? getDefaultValue('delayfeedback'); - $.delayspeed = $.delayspeed ?? getDefaultValue('delayspeed'); - $.delaytime = $.delaytime ?? getDefaultValue('delaytime'); + $.delay = applyGainCurve($.delay ?? getDefaultValue('delay')) + $.delayfeedback = $.delayfeedback ?? getDefaultValue('delayfeedback') + $.delayspeed = $.delayspeed ?? getDefaultValue('delayspeed') + $.delaytime = $.delaytime ?? getDefaultValue('delaytime') // filter setup if ($.lpenv) { - $._lpenv = new ADSR({ decayCurve: 4 }); - [$.lpattack, $.lpdecay, $.lpsustain, $.lprelease] = getADSR([$.lpattack, $.lpdecay, $.lpsustain, $.lprelease]); + $._lpenv = new ADSR({ decayCurve: 4 }) + ;[$.lpattack, $.lpdecay, $.lpsustain, $.lprelease] = getADSR([ + $.lpattack, + $.lpdecay, + $.lpsustain, + $.lprelease, + ]) } if ($.hpenv) { - $._hpenv = new ADSR({ decayCurve: 4 }); - [$.hpattack, $.hpdecay, $.hpsustain, $.hprelease] = getADSR([$.hpattack, $.hpdecay, $.hpsustain, $.hprelease]); + $._hpenv = new ADSR({ decayCurve: 4 }) + ;[$.hpattack, $.hpdecay, $.hpsustain, $.hprelease] = getADSR([ + $.hpattack, + $.hpdecay, + $.hpsustain, + $.hprelease, + ]) } if ($.bpenv) { - $._bpenv = new ADSR({ decayCurve: 4 }); - [$.bpattack, $.bpdecay, $.bpsustain, $.bprelease] = getADSR([$.bpattack, $.bpdecay, $.bpsustain, $.bprelease]); + $._bpenv = new ADSR({ decayCurve: 4 }) + ;[$.bpattack, $.bpdecay, $.bpsustain, $.bprelease] = getADSR([ + $.bpattack, + $.bpdecay, + $.bpsustain, + $.bprelease, + ]) } // channelwise effects setup - $._chorus = $.chorus ? [] : null; - $._lpf = $.cutoff ? [] : null; - $._hpf = $.hcutoff ? [] : null; - $._bpf = $.bandf ? [] : null; - $._coarse = $.coarse ? [] : null; - $._crush = $.crush ? [] : null; - $._distort = $.distort ? [] : null; + $._chorus = $.chorus ? [] : null + $._lpf = $.cutoff ? [] : null + $._hpf = $.hcutoff ? [] : null + $._bpf = $.bandf ? [] : null + $._coarse = $.coarse ? [] : null + $._crush = $.crush ? [] : null + $._distort = $.distort ? [] : null for (let i = 0; i < this._channels; i++) { - $._lpf?.push(new TwoPoleFilter()); - $._hpf?.push(new TwoPoleFilter()); - $._bpf?.push(new TwoPoleFilter()); - $._chorus?.push(new Chorus()); - $._coarse?.push(new Coarse()); - $._crush?.push(new Crush()); - $._distort?.push(new Distort()); + $._lpf?.push(new TwoPoleFilter()) + $._hpf?.push(new TwoPoleFilter()) + $._bpf?.push(new TwoPoleFilter()) + $._chorus?.push(new Chorus()) + $._coarse?.push(new Coarse()) + $._crush?.push(new Crush()) + $._distort?.push(new Distort()) } } update(t) { if (!this._sound && !this._buffers) { - return 0; + return 0 } - let gate = Number(t >= this._begin && t <= this._holdEnd); + let gate = Number(t >= this._begin && t <= this._holdEnd) - let freq = this.freq * this.speed; + let freq = this.freq * this.speed // frequency modulation if (this._fm && this.fmh !== undefined && this.fmi !== undefined) { - let fmi = this.fmi; + let fmi = this.fmi if (this._fmenv) { - const env = this._fmenv.update(t, gate, this.fmattack, this.fmdecay, this.fmsustain, this.fmrelease); - fmi = this.fmenv * env * fmi; + const env = this._fmenv.update( + t, + gate, + this.fmattack, + this.fmdecay, + this.fmsustain, + this.fmrelease + ) + fmi = this.fmenv * env * fmi } - const modfreq = freq * this.fmh; - const modgain = modfreq * fmi; - freq = freq + this._fm.update(modfreq) * modgain; + const modfreq = freq * this.fmh + const modgain = modfreq * fmi + freq = freq + this._fm.update(modfreq) * modgain } // vibrato if (this._vib && this.vibmod !== undefined) { - freq = freq * 2 ** ((this._vib.update(this.vib) * this.vibmod) / 12); + freq = freq * 2 ** ((this._vib.update(this.vib) * this.vibmod) / 12) } // pitch envelope if (this._penv && this.penv !== undefined) { - const env = this._penv.update(t, gate, this.pattack, this.pdecay, this.psustain, this.prelease); - freq = freq + env * this.penv; + const env = this._penv.update( + t, + gate, + this.pattack, + this.pdecay, + this.psustain, + this.prelease + ) + freq = freq + env * this.penv } // filters - let lpf = this.cutoff; + let lpf = this.cutoff if (lpf !== undefined && this._lpenv) { - const env = this._lpenv.update(t, gate, this.lpattack, this.lpdecay, this.lpsustain, this.lprelease); - lpf = this.lpenv * env * lpf + lpf; + const env = this._lpenv.update( + t, + gate, + this.lpattack, + this.lpdecay, + this.lpsustain, + this.lprelease + ) + lpf = this.lpenv * env * lpf + lpf } - let hpf = this.hcutoff; + let hpf = this.hcutoff if (hpf !== undefined && this._hpenv && this.hpenv !== undefined) { - const env = this._hpenv.update(t, gate, this.hpattack, this.hpdecay, this.hpsustain, this.hprelease); - hpf = 2 ** this.hpenv * env * hpf + hpf; + const env = this._hpenv.update( + t, + gate, + this.hpattack, + this.hpdecay, + this.hpsustain, + this.hprelease + ) + hpf = 2 ** this.hpenv * env * hpf + hpf } - let bpf = this.bandf; + let bpf = this.bandf if (bpf !== undefined && this._bpenv && this.bpenv !== undefined) { - const env = this._bpenv.update(t, gate, this.bpattack, this.bpdecay, this.bpsustain, this.bprelease); - bpf = 2 ** this.bpenv * env * bpf + bpf; + const env = this._bpenv.update( + t, + gate, + this.bpattack, + this.bpdecay, + this.bpsustain, + this.bprelease + ) + bpf = 2 ** this.bpenv * env * bpf + bpf } // gain envelope - const env = this._adsr.update(t, gate, this.attack, this.decay, this.sustain, this.release); + const env = this._adsr.update(t, gate, this.attack, this.decay, this.sustain, this.release) // channelwise dsp for (let i = 0; i < this._channels; i++) { // sound source if (this._sound && this.s === 'pulse') { - this.out[i] = this._sound.update(freq, this.pw); + this.out[i] = this._sound.update(freq, this.pw) } else if (this._sound) { - this.out[i] = this._sound.update(freq); + this.out[i] = this._sound.update(freq) } else if (this._buffers) { - this.out[i] = this._buffers[i].update(freq); + this.out[i] = this._buffers[i].update(freq) } - this.out[i] = this.out[i] * this.gain * this.velocity; + this.out[i] = this.out[i] * this.gain * this.velocity if (this._chorus) { - const c = this._chorus[i].update(this.out[i], this.chorus, 0.03 + 0.05 * i, 1, 0.11); - this.out[i] = c + this.out[i]; + const c = this._chorus[i].update(this.out[i], this.chorus, 0.03 + 0.05 * i, 1, 0.11) + this.out[i] = c + this.out[i] } if (this._lpf) { - this._lpf[i].update(this.out[i], lpf, this.resonance); - this.out[i] = this._lpf[i].s1; + this._lpf[i].update(this.out[i], lpf, this.resonance) + this.out[i] = this._lpf[i].s1 } if (this._hpf) { - this._hpf[i].update(this.out[i], hpf, this.hresonance); - this.out[i] = this.out[i] - this._hpf[i].s1; + this._hpf[i].update(this.out[i], hpf, this.hresonance) + this.out[i] = this.out[i] - this._hpf[i].s1 } if (this._bpf) { - this._bpf[i].update(this.out[i], bpf, this.bandq); - this.out[i] = this._bpf[i].s0; + this._bpf[i].update(this.out[i], bpf, this.bandq) + this.out[i] = this._bpf[i].s0 } if (this._coarse) { - this.out[i] = this._coarse[i].update(this.out[i], this.coarse); + this.out[i] = this._coarse[i].update(this.out[i], this.coarse) } if (this._crush) { - this.out[i] = this._crush[i].update(this.out[i], this.crush); + this.out[i] = this._crush[i].update(this.out[i], this.crush) } if (this._distort) { - this.out[i] = this._distort[i].update(this.out[i], this.distort, this.distortvol); + this.out[i] = this._distort[i].update(this.out[i], this.distort, this.distortvol) } - this.out[i] = this.out[i] * env; - this.out[i] = this.out[i] * this.postgain; + this.out[i] = this.out[i] * env + this.out[i] = this.out[i] * this.postgain if (!this._buffers) { - this.out[i] = this.out[i] * 0.2; // turn down waveform + this.out[i] = this.out[i] * 0.2 // turn down waveform } } if (this._channels === 1) { - this.out[1] = this.out[0]; + this.out[1] = this.out[0] } if (this.pan !== 0.5) { - const panpos = (this.pan * Math.PI) / 2; - this.out[0] = this.out[0] * Math.cos(panpos); - this.out[1] = this.out[1] * Math.sin(panpos); + const panpos = (this.pan * Math.PI) / 2 + this.out[0] = this.out[0] * Math.cos(panpos) + this.out[1] = this.out[1] * Math.sin(panpos) } } } @@ -1016,49 +1087,49 @@ export class DoughVoice { // this class is the interface to the "outer world" // it handles spawning and despawning of DoughVoice's export class Dough { - voices = []; // DoughVoice[] - vid = 0; - q = []; - out = [0, 0]; - delaysend = [0, 0]; - delaytime = getDefaultValue('delaytime'); - delayfeedback = getDefaultValue('delayfeedback'); - delayspeed = getDefaultValue('delayspeed'); - t = 0; + voices = [] // DoughVoice[] + vid = 0 + q = [] + out = [0, 0] + delaysend = [0, 0] + delaytime = getDefaultValue('delaytime') + delayfeedback = getDefaultValue('delayfeedback') + delayspeed = getDefaultValue('delayspeed') + t = 0 // sampleRate: number, currentTime: number (seconds) constructor(sampleRate = 48000, currentTime = 0) { - this.sampleRate = sampleRate; - this.t = Math.floor(currentTime * sampleRate); // samples + this.sampleRate = sampleRate + this.t = Math.floor(currentTime * sampleRate) // samples // console.log('init dough', this.sampleRate, this.t); - this._delayL = new Delay(); - this._delayR = new Delay(); + this._delayL = new Delay() + this._delayR = new Delay() } loadSample(name, channels, sampleRate) { - BufferPlayer.samples.set(name, { channels, sampleRate }); + BufferPlayer.samples.set(name, { channels, sampleRate }) } scheduleSpawn(value) { if (value._begin === undefined) { - throw new Error('[dough]: scheduleSpawn expected _begin to be set'); + throw new Error('[dough]: scheduleSpawn expected _begin to be set') } if (value._duration === undefined) { - throw new Error('[dough]: scheduleSpawn expected _duration to be set'); + throw new Error('[dough]: scheduleSpawn expected _duration to be set') } - value.sampleRate = this.sampleRate; + value.sampleRate = this.sampleRate // convert seconds to samples - const time = Math.floor(value._begin * this.sampleRate); // set from supradough.mjs - this.schedule({ time, type: 'spawn', arg: value }); + const time = Math.floor(value._begin * this.sampleRate) // set from supradough.mjs + this.schedule({ time, type: 'spawn', arg: value }) } spawn(value) { - value.id = this.vid++; - const voice = new DoughVoice(value); - this.voices.push(voice); + value.id = this.vid++ + const voice = new DoughVoice(value) + this.voices.push(voice) // console.log('spawn', voice.id, 'voices:', this.voices.length); // schedule removal - const endTime = Math.ceil(voice._end * this.sampleRate); - this.schedule({ time: endTime /* + 48000 */, type: 'despawn', arg: voice.id }); + const endTime = Math.ceil(voice._end * this.sampleRate) + this.schedule({ time: endTime /* + 48000 */, type: 'despawn', arg: voice.id }) } despawn(vid) { - this.voices = this.voices.filter((v) => v.id !== vid); + this.voices = this.voices.filter((v) => v.id !== vid) // console.log('despawn', vid, 'voices:', this.voices.length); } // schedules a function call with a single argument @@ -1067,17 +1138,17 @@ export class Dough { schedule(msg) { if (!this.q.length) { // if empty, just push - this.q.push(msg); - return; + this.q.push(msg) + return } // not empty // find index where msg.time fits in - let i = 0; + let i = 0 while (i < this.q.length && this.q[i].time < msg.time) { - i++; + i++ } // this ensures q stays sorted by time, so we only need to check q[0] - this.q.splice(i, 0, msg); + this.q.splice(i, 0, msg) } // maybe update should be called once per block instead for perf reasons? update() { @@ -1085,32 +1156,32 @@ export class Dough { while (this.q.length > 0 && this.q[0].time <= this.t) { // console.log('schedule', this.q[0]); // trigger due messages. q is sorted, so we only need to check q[0] - this[this.q[0].type](this.q[0].arg); // type is expected to be a Dough method - this.q.shift(); + this[this.q[0].type](this.q[0].arg) // type is expected to be a Dough method + this.q.shift() } // add active voices - this.out[0] = 0; - this.out[1] = 0; + this.out[0] = 0 + this.out[1] = 0 for (let v = 0; v < this.voices.length; v++) { - this.voices[v].update(this.t / this.sampleRate); - this.out[0] += this.voices[v].out[0]; - this.out[1] += this.voices[v].out[1]; + this.voices[v].update(this.t / this.sampleRate) + this.out[0] += this.voices[v].out[0] + this.out[1] += this.voices[v].out[1] if (this.voices[v].delay) { - this.delaysend[0] += this.voices[v].out[0] * this.voices[v].delay; - this.delaysend[1] += this.voices[v].out[1] * this.voices[v].delay; - this.delaytime = this.voices[v].delaytime; // we trust that these are initialized in the voice - this.delayspeed = this.voices[v].delayspeed; // we trust that these are initialized in the voice - this.delayfeedback = this.voices[v].delayfeedback; + this.delaysend[0] += this.voices[v].out[0] * this.voices[v].delay + this.delaysend[1] += this.voices[v].out[1] * this.voices[v].delay + this.delaytime = this.voices[v].delaytime // we trust that these are initialized in the voice + this.delayspeed = this.voices[v].delayspeed // we trust that these are initialized in the voice + this.delayfeedback = this.voices[v].delayfeedback } } // todo: how to change delaytime / delayfeedback from a voice? - const delayL = this._delayL.update(this.delaysend[0], this.delaytime); - const delayR = this._delayR.update(this.delaysend[1], this.delaytime); + const delayL = this._delayL.update(this.delaysend[0], this.delaytime) + const delayR = this._delayR.update(this.delaysend[1], this.delaytime) - this.delaysend[0] = delayL * this.delayfeedback; - this.delaysend[1] = delayR * this.delayfeedback; - this.out[0] += delayL; - this.out[1] += delayR; - this.t++; + this.delaysend[0] = delayL * this.delayfeedback + this.delaysend[1] = delayR * this.delayfeedback + this.out[0] += delayL + this.out[1] += delayR + this.t++ } } diff --git a/src/strudel/supradough/index.mjs b/src/strudel/supradough/index.mjs index cb132fb..b1d8d45 100644 --- a/src/strudel/supradough/index.mjs +++ b/src/strudel/supradough/index.mjs @@ -1,5 +1,5 @@ // import _workletUrl from './dough-worklet.mjs?url'; // only for dev (breaks for production build) -import _workletUrl from './dough-worklet.mjs?audioworklet'; // only for prod (breaks in development?!) +import _workletUrl from './dough-worklet.mjs?audioworklet' // only for prod (breaks in development?!) -export * from './dough.mjs'; -export const workletUrl = _workletUrl; +export * from './dough.mjs' +export const workletUrl = _workletUrl diff --git a/src/strudel/supradough/package.json b/src/strudel/supradough/package.json deleted file mode 100644 index 7e465c0..0000000 --- a/src/strudel/supradough/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "supradough", - "version": "1.2.3", - "description": "platform agnostic synth and sampler intended for live coding. a reimplementation of superdough.", - "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": [ - "tidalcycles", - "strudel", - "pattern", - "livecoding", - "algorave" - ], - "author": "Felix Roos ", - "license": "AGPL-3.0-or-later", - "bugs": { - "url": "https://github.com/tidalcycles/strudel/issues" - }, - "homepage": "https://github.com/tidalcycles/strudel#readme", - "devDependencies": { - "vite": "^6.0.11", - "vite-plugin-bundle-audioworklet": "workspace:*", - "wav-encoder": "^1.3.0" - }, - "dependencies": {} -} diff --git a/src/strudel/tonal/index.mjs b/src/strudel/tonal/index.mjs index ebbcf08..3c06240 100644 --- a/src/strudel/tonal/index.mjs +++ b/src/strudel/tonal/index.mjs @@ -1,9 +1,9 @@ -import './tonal.mjs'; -import './voicings.mjs'; -import './ireal.mjs'; +import './tonal.mjs' +import './voicings.mjs' +import './ireal.mjs' -export * from './tonal.mjs'; -export * from './voicings.mjs'; -export * from './ireal.mjs'; +export * from './tonal.mjs' +export * from './voicings.mjs' +export * from './ireal.mjs' -export const packageName = '@strudel/tonal'; +export const packageName = '../tonal/index.mjs' diff --git a/src/strudel/tonal/ireal.mjs b/src/strudel/tonal/ireal.mjs index e6faef9..b554a49 100644 --- a/src/strudel/tonal/ireal.mjs +++ b/src/strudel/tonal/ireal.mjs @@ -23,9 +23,21 @@ export const simple = { '7m 10M 12P 15P 16M', ], 11: ['1P 5P 7m 9M 11P', '5P 7m 8P 9M 11P', '7m 8P 9M 11P 12P', '7m 8P 11P 12P 16M'], - 13: ['1P 6M 7m 9M 10M', '1P 7m 9M 10M 13M', '3M 7m 8P 9M 13M', '7m 8P 9M 10M 13M', '7m 9M 10M 13M 15P'], + 13: [ + '1P 6M 7m 9M 10M', + '1P 7m 9M 10M 13M', + '3M 7m 8P 9M 13M', + '7m 8P 9M 10M 13M', + '7m 9M 10M 13M 15P', + ], 69: ['1P 5P 6M 9M 10M', '1P 5P 9M 10M 13M', '3M 5P 8P 9M 13M', '5P 8P 9M 10M 13M'], - add9: ['1P 5P 8P 9M 10M', '1P 5P 9M 10M 12P', '3M 8P 9M 10M 12P', '3M 8P 9M 12P 15P', '5P 8P 9M 12P 17M'], + add9: [ + '1P 5P 8P 9M 10M', + '1P 5P 9M 10M 12P', + '3M 8P 9M 10M 12P', + '3M 8P 9M 12P 15P', + '5P 8P 9M 12P 17M', + ], '+': [ '1P 3M 6m 8P 10M', '1P 6m 8P 10M 13m', @@ -46,9 +58,21 @@ export const simple = { '5d 8P 10m 14m 17m', ], sus: ['1P 4P 5P 8P', '1P 4P 5P 8P 11P', '5P 8P 11P 12P', '5P 8P 11P 12P 15P'], - '^': ['1P 5P 8P 10M', '1P 5P 8P 10M 12P', '3M 5P 8P 10M 12P', '3M 8P 10M 12P 15P', '5P 8P 10M 12P 15P'], + '^': [ + '1P 5P 8P 10M', + '1P 5P 8P 10M 12P', + '3M 5P 8P 10M 12P', + '3M 8P 10M 12P 15P', + '5P 8P 10M 12P 15P', + ], '-': ['1P 3m 5P 8P 10m', '1P 5P 8P 10m 12P', '3m 5P 8P 10m 12P', '5P 8P 10m 12P 15P'], - '^7': ['1P 5P 7M 10M 12P', '1P 10M 12P 14M', '3M 8P 10M 12P 14M', '5P 8P 10M 12P 14M', '5P 8P 10M 14M 17M'], + '^7': [ + '1P 5P 7M 10M 12P', + '1P 10M 12P 14M', + '3M 8P 10M 12P 14M', + '5P 8P 10M 12P 14M', + '5P 8P 10M 14M 17M', + ], '-7': [ '1P 3m 5P 7m 10m', '1P 5P 7m 10m 12P', @@ -89,8 +113,20 @@ export const simple = { '5P 8P 10M 14M 16M', '7M 8P 10M 12P 16M', ], - '^13': ['1P 6M 7M 9M 10M', '1P 7M 9M 10M 13M', '3M 7M 8P 9M 13M', '3M 7M 8P 13M 16M', '7M 8P 10M 13M 16M'], - '^7#11': ['1P 5P 7M 10M 12d', '3M 7M 8P 10M 12d', '1P 7M 10M 12d 14M', '3M 7M 8P 12d 14M', '5P 8P 10M 12d 14M'], + '^13': [ + '1P 6M 7M 9M 10M', + '1P 7M 9M 10M 13M', + '3M 7M 8P 9M 13M', + '3M 7M 8P 13M 16M', + '7M 8P 10M 13M 16M', + ], + '^7#11': [ + '1P 5P 7M 10M 12d', + '3M 7M 8P 10M 12d', + '1P 7M 10M 12d 14M', + '3M 7M 8P 12d 14M', + '5P 8P 10M 12d 14M', + ], '^9#11': ['1P 3M 5d 7M 9M', '1P 7M 9M 10M 12d', '3M 7M 8P 9M 12d', '3M 8P 9M 12d 14M'], '^7#5': ['1P 6m 7M 10M 13m', '3M 7M 8P 10M 13m', '6m 7M 8P 10M 13m'], '-6': [ @@ -111,7 +147,13 @@ export const simple = { '5P 8P 9M 10m 13M', '5P 8P 10m 13M 16M', ], - '-^7': ['1P 3m 5P 7M 10m', '1P 5P 7M 10m 12P', '3m 7M 8P 10m 12P', '5P 7M 8P 10m 14M', '5P 8P 10m 14M 17m'], + '-^7': [ + '1P 3m 5P 7M 10m', + '1P 5P 7M 10m 12P', + '3m 7M 8P 10m 12P', + '5P 7M 8P 10m 14M', + '5P 8P 10m 14M 17m', + ], '-^9': ['1P 3m 5P 7M 9M', '1P 7M 9M 10m 12P', '3m 7M 8P 9M 12P', '5P 8P 9M 10m 14M'], '-9': [ '1P 3m 5P 7m 9M', @@ -143,7 +185,13 @@ export const simple = { '5d 8P 10m 14m 17m', ], h9: ['1P 7m 9M 10m 12d', '3m 7m 8P 9M 12d', '5d 8P 9M 10m 14m', '7m 10m 12d 15P 16M'], - '-b6': ['1P 5P 6m 8P 10m', '1P 5P 8P 10m 13m', '3m 5P 8P 10m 13m', '5P 8P 10m 13m', '5P 8P 10m 13m 15P'], + '-b6': [ + '1P 5P 6m 8P 10m', + '1P 5P 8P 10m 13m', + '3m 5P 8P 10m 13m', + '5P 8P 10m 13m', + '5P 8P 10m 13m 15P', + ], '-#5': ['1P 6m 8P 10m 13m', '3m 6m 8P 10m 13m', '6m 8P 10m 13m 15P'], '7b9': ['1P 3M 7m 9m 10M', '3M 7m 8P 9m 10M', '3M 7m 8P 9m 14m', '7m 9m 10M 14m 15P'], '7#9': ['1P 3M 7m 10m', '3M 7m 8P 10m 14m', '7m 10m 10M 14m 15P'], @@ -152,7 +200,13 @@ export const simple = { '7#5': ['1P 3M 7m 10M 13m', '3M 7m 8P 10M 13m', '3M 7m 8P 13m 14m', '7m 10M 13m 14m 15P'], '9#11': ['1P 7m 9M 10M 12d', '3M 7m 8P 9M 12d', '7m 10M 12d 15P 16M'], '9b5': ['1P 7m 9M 10M 12d', '3M 7m 8P 9M 12d', '7m 10M 12d 15P 16M'], - '9#5': ['1P 7m 9M 10M 13m', '3M 7m 9M 10M 13m', '3M 7m 9M 13m 14m', '7m 10M 13m 14m 16M', '7m 10M 13m 16M 17M'], + '9#5': [ + '1P 7m 9M 10M 13m', + '3M 7m 9M 10M 13m', + '3M 7m 9M 13m 14m', + '7m 10M 13m 14m 16M', + '7m 10M 13m 16M 17M', + ], '7b13': ['1P 3M 7m 10M 13m', '3M 7m 8P 10M 13m', '3M 7m 8P 13m 14m', '7m 10M 13m 14m 15P'], '7#9#5': ['1P 3M 7m 10m 13m', '3M 7m 10m 13m 15P', '7m 10M 13m 15P 17m'], '7#9b5': ['1P 3M 7m 10m 12d', '3M 7m 10m 12d 15P', '7m 10M 12d 15P 17m'], @@ -172,19 +226,31 @@ export const simple = { '7m 10M 13m 15P 17m', ], '13#11': ['1P 6M 7m 10M 12d', '3M 7m 9M 12d 13M', '7m 10M 12d 13M 16M'], - '13b9': ['1P 3M 6M 7m 9m', '1P 6M 7m 9m 10M', '3M 7m 9m 10M 13M', '3M 7m 10M 13M 16m', '7m 10M 13M 16m 17M'], + '13b9': [ + '1P 3M 6M 7m 9m', + '1P 6M 7m 9m 10M', + '3M 7m 9m 10M 13M', + '3M 7m 10M 13M 16m', + '7m 10M 13M 16m 17M', + ], '13#9': ['1P 3M 6M 7m 10m', '3M 7m 8P 10m 13M', '7m 10M 13M 14m 17m'], '7b9sus': ['1P 5P 7m 9m 11P', '5P 7m 8P 9m 11P', '7m 8P 11P 14m 16m'], '7susadd3': ['1P 4P 5P 7m 10M', '5P 8P 10M 11P 14m', '7m 11P 12P 15P 17M'], '9sus': ['1P 5P 7m 9M 11P', '5P 7m 8P 9M 11P', '7m 8P 9M 11P 12P', '7m 8P 11P 12P 16M'], '13sus': ['1P 4P 6M 7m 9M', '1P 7m 9M 11P 13M', '5P 7m 9M 11P 13M', '7m 9M 11P 13M 15P'], '7b13sus': ['1P 5P 7m 11P 13m', '5P 7m 8P 11P 13m', '7m 11P 13m 14m 15P'], -}; +} export const complex = { 2: ['1P 5P 6M 8P 9M', '1P 5P 8P 9M 12P', '5P 8P 9M 12P 13M', '5P 8P 9M 12P 15P'], 5: ['1P 5P 8P 12P', '1P 5P 8P 9M 12P', '5P 8P 12P 15P', '5P 8P 12P 15P 16M'], - 6: ['1P 5P 6M 9M 10M', '1P 5P 9M 10M 13M', '3M 5P 9M 10M 13M', '5P 8P 9M 10M 13M', '3M 6M 9M 12P 15P'], + 6: [ + '1P 5P 6M 9M 10M', + '1P 5P 9M 10M 13M', + '3M 5P 9M 10M 13M', + '5P 8P 9M 10M 13M', + '3M 6M 9M 12P 15P', + ], 7: [ '1P 5P 7m 8P 10M', '1P 7m 8P 10M 12P', @@ -221,7 +287,13 @@ export const complex = { '7m 10M 13M 16M 17M', '7m 10M 13M 16M 19P', ], - 69: ['1P 5P 6M 9M 10M', '1P 5P 9M 10M 13M', '3M 5P 9M 10M 13M', '5P 8P 9M 10M 13M', '3M 6M 9M 12P 15P'], + 69: [ + '1P 5P 6M 9M 10M', + '1P 5P 9M 10M 13M', + '3M 5P 9M 10M 13M', + '5P 8P 9M 10M 13M', + '3M 6M 9M 12P 15P', + ], add9: [ '1P 5P 8P 9M 10M', '1P 5P 9M 10M 12P', @@ -383,7 +455,13 @@ export const complex = { '7M 10M 12d 14M 16M', '7M 10M 12d 13M 16M', ], - '^7#5': ['1P 6m 7M 10M 13m', '3M 7M 9M 10M 13m', '3M 7M 10M 13m 14M', '7M 10M 13m 14M 16M', '7M 10M 13m 14M 17M'], + '^7#5': [ + '1P 6m 7M 10M 13m', + '3M 7M 9M 10M 13m', + '3M 7M 10M 13m 14M', + '7M 10M 13m 14M 16M', + '7M 10M 13m 14M 17M', + ], '-6': [ '1P 3m 5P 6M 9M', '3m 5P 6M 8P 9M', @@ -471,19 +549,49 @@ export const complex = { '7#9': ['1P 3M 7m 10m', '3M 7m 10m 10M 12P', '3M 7m 10m 12P 14m', '7m 10M 12P 14m 17m'], '7#11': ['1P 3M 7m 9M 12d', '3M 7m 9M 12d 13M', '7m 10M 12d 13M 16M'], '7b5': ['1P 3M 7m 9M 12d', '3M 7m 9M 12d 13M', '7m 10M 12d 13M 16M'], - '7#5': ['1P 3M 7m 10M 13m', '3M 7m 8P 10M 13m', '3M 7m 8P 13m 14m', '7m 10M 13m 14m 15P', '7m 10M 13m 14m 17M'], + '7#5': [ + '1P 3M 7m 10M 13m', + '3M 7m 8P 10M 13m', + '3M 7m 8P 13m 14m', + '7m 10M 13m 14m 15P', + '7m 10M 13m 14m 17M', + ], '9#11': ['1P 7m 9M 10M 12d', '3M 7m 8P 9M 12d', '7m 10M 12d 15P 16M'], '9b5': ['1P 7m 9M 10M 12d', '3M 7m 8P 9M 12d', '7m 10M 12d 15P 16M'], - '9#5': ['1P 7m 9M 10M 13m', '3M 7m 9M 10M 13m', '3M 7m 9M 13m 14m', '7m 10M 13m 14m 16M', '7m 10M 13m 16M 17M'], - '7b13': ['1P 3M 7m 10M 13m', '3M 7m 8P 10M 13m', '3M 7m 8P 13m 14m', '7m 10M 13m 14m 15P', '7m 10M 13m 14m 17M'], + '9#5': [ + '1P 7m 9M 10M 13m', + '3M 7m 9M 10M 13m', + '3M 7m 9M 13m 14m', + '7m 10M 13m 14m 16M', + '7m 10M 13m 16M 17M', + ], + '7b13': [ + '1P 3M 7m 10M 13m', + '3M 7m 8P 10M 13m', + '3M 7m 8P 13m 14m', + '7m 10M 13m 14m 15P', + '7m 10M 13m 14m 17M', + ], '7#9#5': ['3M 7m 10m 10M 13m', '3M 7m 10m 13m 14m', '7m 10M 13m 14m 17m'], '7#9b5': ['3M 7m 10m 10M 12d', '3M 7m 10m 12d 14m', '7m 10M 12d 14m 17m'], '7#9#11': ['3M 7m 10m 10M 12d', '3M 7m 10m 12d 14m', '7m 10M 12d 14m 17m'], '7b9#11': ['3M 7m 9m 10M 12d', '3M 7m 9m 12d 14m', '7m 8P 10M 12d 16m', '7m 10M 12d 14m 16m'], '7b9b5': ['3M 7m 9m 10M 12d', '3M 7m 9m 12d 14m', '7m 8P 10M 12d 16m', '7m 10M 12d 14m 16m'], - '7b9#5': ['1P 7m 9m 10M 13m', '3M 7m 9m 10M 13m', '3M 7m 10M 13m 16m', '7m 10M 13m 14m 16m', '7m 10M 13m 16m 17M'], + '7b9#5': [ + '1P 7m 9m 10M 13m', + '3M 7m 9m 10M 13m', + '3M 7m 10M 13m 16m', + '7m 10M 13m 14m 16m', + '7m 10M 13m 16m 17M', + ], '7b9#9': ['1P 3M 7m 9m 10m', '3M 7m 10m 13m 16m', '7m 10M 13m 16m 17m'], - '7b9b13': ['1P 7m 9m 10M 13m', '3M 7m 9m 10M 13m', '3M 7m 10M 13m 16m', '7m 10M 13m 14m 16m', '7m 10M 13m 16m 17M'], + '7b9b13': [ + '1P 7m 9m 10M 13m', + '3M 7m 9m 10M 13m', + '3M 7m 10M 13m 16m', + '7m 10M 13m 14m 16m', + '7m 10M 13m 16m 17M', + ], '7alt': [ '3M 7m 8P 10m 13m', '3M 7m 9m 12d 13m', @@ -520,4 +628,4 @@ export const complex = { '7m 11P 13M 16M 18P', ], '7b13sus': ['1P 5P 7m 11P 13m', '5P 7m 8P 11P 13m', '7m 11P 13m 14m 15P'], -}; +} diff --git a/src/strudel/tonal/tonal.mjs b/src/strudel/tonal/tonal.mjs index ae75ab6..9f20364 100644 --- a/src/strudel/tonal/tonal.mjs +++ b/src/strudel/tonal/tonal.mjs @@ -4,69 +4,69 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { Note, Interval, Scale } from '@tonaljs/tonal'; -import { register, _mod, silence, logger, pure, isNote } from '@strudel/core'; -import { stepInNamedScale, nearestNumberIndex } from './tonleiter.mjs'; -import { noteToMidi } from '../core/util.mjs'; +import { Note, Interval, Scale } from '@tonaljs/tonal' +import { register, _mod, silence, logger, pure, isNote } from '../core/index.mjs' +import { stepInNamedScale, nearestNumberIndex } from './tonleiter.mjs' +import { noteToMidi } from '../core/util.mjs' -const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P'; +const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P' function getScale(scaleName) { - scaleName = scaleName.replaceAll(':', ' '); - const scale = Scale.get(scaleName); - const { tonic, empty } = scale; + scaleName = scaleName.replaceAll(':', ' ') + const scale = Scale.get(scaleName) + const { tonic, empty } = scale if ((empty && isNote(scaleName)) || (empty && !tonic)) { throw new Error( - `Scale name ${scaleName} is incomplete. Make sure to use ":" instead of spaces, example: .scale("C:major")`, - ); + `Scale name ${scaleName} is incomplete. Make sure to use ":" instead of spaces, example: .scale("C:major")` + ) } else if (empty) { - throw new Error(`Invalid scale name "${scaleName}"`); + throw new Error(`Invalid scale name "${scaleName}"`) } - return scale; + return scale } function scaleStep(step, scale) { - step = Math.ceil(step); - let { intervals, tonic } = getScale(scale); - tonic = tonic || 'C'; - const { pc, oct = 3 } = Note.get(tonic); - const octaveOffset = Math.floor(step / intervals.length); - const scaleStep = _mod(step, intervals.length); - const interval = Interval.add(intervals[scaleStep], octavesInterval(octaveOffset)); - return Note.transpose(pc + oct, interval); + step = Math.ceil(step) + let { intervals, tonic } = getScale(scale) + tonic = tonic || 'C' + const { pc, oct = 3 } = Note.get(tonic) + const octaveOffset = Math.floor(step / intervals.length) + const scaleStep = _mod(step, intervals.length) + const interval = Interval.add(intervals[scaleStep], octavesInterval(octaveOffset)) + return Note.transpose(pc + oct, interval) } // transpose note inside scale by offset steps // function scaleOffset(scale: string, offset: number, note: string) { function scaleOffset(scale, offset, note) { - let { notes } = getScale(scale); - notes = notes.map((note) => Note.get(note).pc); // use only pc! - offset = Number(offset); + let { notes } = getScale(scale) + notes = notes.map((note) => Note.get(note).pc) // use only pc! + offset = Number(offset) if (isNaN(offset)) { - throw new Error(`scale offset "${offset}" not a number`); + throw new Error(`scale offset "${offset}" not a number`) } - const { pc: fromPc, oct = 3 } = Note.get(note); - const noteIndex = notes.indexOf(fromPc); + const { pc: fromPc, oct = 3 } = Note.get(note) + const noteIndex = notes.indexOf(fromPc) if (noteIndex === -1) { - throw new Error(`note "${note}" is not in scale "${scale}"`); + throw new Error(`note "${note}" is not in scale "${scale}"`) } let i = noteIndex, o = oct, - n = fromPc; - const direction = Math.sign(offset); + n = fromPc + const direction = Math.sign(offset) // TODO: find way to do this smarter while (Math.abs(i - noteIndex) < Math.abs(offset)) { - i += direction; - const index = _mod(i, notes.length); + i += direction + const index = _mod(i, notes.length) if (direction < 0 && n[0] === 'C') { - o += direction; + o += direction } - n = notes[index]; + n = notes[index] if (direction > 0 && n[0] === 'C') { - o += direction; + o += direction } } - return n + o; + return n + o } // Pattern.prototype._transpose = function (intervalOrSemitones: string | number) { @@ -103,38 +103,41 @@ function scaleOffset(scale, offset, note) { * "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note() */ -export const { transpose, trans } = register(['transpose', 'trans'], function transposeFn(intervalOrSemitones, pat) { - return pat.withHap((hap) => { - const note = hap.value.note ?? hap.value; - if (typeof note === 'number') { - // note is a number, so just add the number semitones of the interval - let semitones; - if (typeof intervalOrSemitones === 'number') { - semitones = intervalOrSemitones; - } else if (typeof intervalOrSemitones === 'string') { - semitones = Interval.semitones(intervalOrSemitones) || 0; +export const { transpose, trans } = register( + ['transpose', 'trans'], + function transposeFn(intervalOrSemitones, pat) { + return pat.withHap((hap) => { + const note = hap.value.note ?? hap.value + if (typeof note === 'number') { + // note is a number, so just add the number semitones of the interval + let semitones + if (typeof intervalOrSemitones === 'number') { + semitones = intervalOrSemitones + } else if (typeof intervalOrSemitones === 'string') { + semitones = Interval.semitones(intervalOrSemitones) || 0 + } + const targetNote = note + semitones + if (typeof hap.value === 'object') { + return hap.withValue(() => ({ ...hap.value, note: targetNote })) + } + return hap.withValue(() => targetNote) } - const targetNote = note + semitones; + if (typeof note !== 'string' || !isNote(note)) { + logger(`[tonal] transpose: not a note "${note}"`, 'warning') + return hap + } + // note is a string, so we might be able to preserve harmonics if interval is a string as well + const interval = !isNaN(Number(intervalOrSemitones)) + ? Interval.fromSemitones(intervalOrSemitones) + : String(intervalOrSemitones) + const targetNote = Note.transpose(note, interval) if (typeof hap.value === 'object') { - return hap.withValue(() => ({ ...hap.value, note: targetNote })); + return hap.withValue(() => ({ ...hap.value, note: targetNote })) } - return hap.withValue(() => targetNote); - } - if (typeof note !== 'string' || !isNote(note)) { - logger(`[tonal] transpose: not a note "${note}"`, 'warning'); - return hap; - } - // note is a string, so we might be able to preserve harmonics if interval is a string as well - const interval = !isNaN(Number(intervalOrSemitones)) - ? Interval.fromSemitones(intervalOrSemitones) - : String(intervalOrSemitones); - const targetNote = Note.transpose(note, interval); - if (typeof hap.value === 'object') { - return hap.withValue(() => ({ ...hap.value, note: targetNote })); - } - return hap.withValue(() => targetNote); - }); -}); + return hap.withValue(() => targetNote) + }) + } +) // example: transpose(3).late(0.2) will be equivalent to compose(transpose(3), late(0.2)) // e.g. `stack(c3).superimpose(transpose(slowcat(7, 5)))` or @@ -161,65 +164,67 @@ export const { scaleTranspose, scaleTrans, strans } = register( function (offset /* : number | string */, pat) { return pat.withHap((hap) => { if (!hap.context.scale) { - throw new Error('can only use scaleTranspose after .scale'); + throw new Error('can only use scaleTranspose after .scale') } if (typeof hap.value === 'object') return hap.withValue(() => ({ ...hap.value, note: scaleOffset(hap.context.scale, Number(offset), hap.value.note), - })); + })) if (typeof hap.value !== 'string') { - throw new Error('can only use scaleTranspose with notes'); + throw new Error('can only use scaleTranspose with notes') } - return hap.withValue(() => scaleOffset(hap.context.scale, Number(offset), hap.value)); - }); - }, -); + return hap.withValue(() => scaleOffset(hap.context.scale, Number(offset), hap.value)) + }) + } +) // Converts a step value, which is a number optionally decorated with sharps and flats, // to a number and an `offset` number of semitones function _convertStepToNumberAndOffset(step) { - let asNumber = Number(step); - let offset = 0; + let asNumber = Number(step) + let offset = 0 if (isNaN(asNumber)) { - step = String(step); + step = String(step) // Check to see if the step matches the expected format: // - A number (possibly negative) // - Some number of sharps or flats (but not both) - const match = /^(-?\d+)(#+|b+)?$/.exec(step); + const match = /^(-?\d+)(#+|b+)?$/.exec(step) if (!match) { - throw new Error(`invalid scale step "${step}", expected number or integer with optional # b suffixes`); + throw new Error( + `invalid scale step "${step}", expected number or integer with optional # b suffixes` + ) } - asNumber = Number(match[1]); + asNumber = Number(match[1]) // These decorations will determine the semitone offset based on the number of // sharps or flats - const decorations = match[2] || ''; - offset = decorations[0] === '#' ? decorations.length : -decorations.length; + const decorations = match[2] || '' + offset = decorations[0] === '#' ? decorations.length : -decorations.length } - return [asNumber, offset]; + return [asNumber, offset] } -let scaleToMidisAndNotes = {}; +let scaleToMidisAndNotes = {} // Finds the nearest scale note to `note` function _getNearestScaleNote(scaleName, note, preferHigher = true) { - let noteMidi = typeof note === 'string' ? noteToMidi(note) : note; + let noteMidi = typeof note === 'string' ? noteToMidi(note) : note if (scaleToMidisAndNotes[scaleName] === undefined) { - const { intervals, tonic } = getScale(scaleName); - const { pc } = Note.get(tonic); - const expandedIntervals = intervals.concat('8P'); // add the octave for wrapping - const sNotes = expandedIntervals.map((interval) => Note.transpose(pc + '0', interval)); - const sMidi = sNotes.map(noteToMidi); + const { intervals, tonic } = getScale(scaleName) + const { pc } = Note.get(tonic) + const expandedIntervals = intervals.concat('8P') // add the octave for wrapping + const sNotes = expandedIntervals.map((interval) => Note.transpose(pc + '0', interval)) + const sMidi = sNotes.map(noteToMidi) // Cache - scaleToMidisAndNotes[scaleName] = [sMidi, sNotes]; + scaleToMidisAndNotes[scaleName] = [sMidi, sNotes] } - const [scaleMidis, scaleNotes] = scaleToMidisAndNotes[scaleName]; - const rootMidi = scaleMidis[0]; - const octaveDiff = Math.floor((noteMidi - rootMidi) / 12); - const alignedMidis = scaleMidis.map((m) => m + 12 * octaveDiff); - const noteIdx = nearestNumberIndex(noteMidi, alignedMidis, preferHigher); - const noteMatch = scaleNotes[noteIdx]; - return Note.transpose(noteMatch, Interval.fromSemitones(12 * octaveDiff)); + const [scaleMidis, scaleNotes] = scaleToMidisAndNotes[scaleName] + const rootMidi = scaleMidis[0] + const octaveDiff = Math.floor((noteMidi - rootMidi) / 12) + const alignedMidis = scaleMidis.map((m) => m + 12 * octaveDiff) + const noteIdx = nearestNumberIndex(noteMidi, alignedMidis, preferHigher) + const noteMatch = scaleNotes[noteIdx] + return Note.transpose(noteMatch, Interval.fromSemitones(12 * octaveDiff)) } /** @@ -260,47 +265,47 @@ export const scale = register( function (scale, pat) { // Supports ':' list syntax in mininotation if (Array.isArray(scale)) { - scale = scale.flat().join(' '); + scale = scale.flat().join(' ') } return ( pat .fmap((value) => { - const isObject = typeof value === 'object'; + const isObject = typeof value === 'object' // The case where the note has been defined via `n` or `pure` if (!isObject || (isObject && ('n' in value || 'value' in value))) { - const step = isObject ? (value.n ?? value.value) : value; - delete value.n; // remove n so it won't cause trouble + const step = isObject ? (value.n ?? value.value) : value + delete value.n // remove n so it won't cause trouble if (isNote(step)) { // legacy.. - return pure(step); + return pure(step) } try { - const [number, offset] = _convertStepToNumberAndOffset(step); - let note; + const [number, offset] = _convertStepToNumberAndOffset(step) + let note if (isObject && value.anchor) { - note = stepInNamedScale(number, scale, value.anchor); + note = stepInNamedScale(number, scale, value.anchor) } else { - note = scaleStep(number, scale); + note = scaleStep(number, scale) } - if (offset != 0) note = Note.transpose(note, Interval.fromSemitones(offset)); - value = pure(isObject ? { ...value, note } : note); + if (offset != 0) note = Note.transpose(note, Interval.fromSemitones(offset)) + value = pure(isObject ? { ...value, note } : note) } catch (err) { - logger(`[tonal] ${err.message}`, 'error'); - return silence; + logger(`[tonal] ${err.message}`, 'error') + return silence } - return value; + return value } // The case where the note has been defined via `note` else { - const note = _getNearestScaleNote(scale, value.note); - return pure(isObject ? { ...value, note } : note); + const note = _getNearestScaleNote(scale, value.note) + return pure(isObject ? { ...value, note } : note) } }) .outerJoin() // legacy: .withHap((hap) => hap.setContext({ ...hap.context, scale })) - ); + ) }, true, - true, // preserve step count -); + true // preserve step count +) diff --git a/src/strudel/tonal/tonleiter.mjs b/src/strudel/tonal/tonleiter.mjs index 2331296..bfe606a 100644 --- a/src/strudel/tonal/tonleiter.mjs +++ b/src/strudel/tonal/tonleiter.mjs @@ -1,131 +1,131 @@ -import { isNote, isNoteWithOctave, _mod, noteToMidi, tokenizeNote } from '@strudel/core'; -import { Interval, Scale } from '@tonaljs/tonal'; +import { isNote, isNoteWithOctave, _mod, noteToMidi, tokenizeNote } from '../core/index.mjs' +import { Interval, Scale } from '@tonaljs/tonal' // https://codesandbox.io/s/stateless-voicings-g2tmz0?file=/src/lib.js:0-2515 -const flats = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B']; -const pcs = ['c', 'db', 'd', 'eb', 'e', 'f', 'gb', 'g', 'ab', 'a', 'bb', 'b']; -const sharps = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; -const accs = { b: -1, '#': 1 }; +const flats = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'] +const pcs = ['c', 'db', 'd', 'eb', 'e', 'f', 'gb', 'g', 'ab', 'a', 'bb', 'b'] +const sharps = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] +const accs = { b: -1, '#': 1 } export const pc2chroma = (pc) => { - const [letter, ...rest] = pc.split(''); - return pcs.indexOf(letter.toLowerCase()) + rest.reduce((sum, sign) => sum + accs[sign], 0); -}; + const [letter, ...rest] = pc.split('') + return pcs.indexOf(letter.toLowerCase()) + rest.reduce((sum, sign) => sum + accs[sign], 0) +} -export const rotateChroma = (chroma, steps) => (chroma + (steps % 12) + 12) % 12; +export const rotateChroma = (chroma, steps) => (chroma + (steps % 12) + 12) % 12 export const chroma2pc = (chroma, sharp = false) => { - return (sharp ? sharps : flats)[chroma]; -}; + return (sharp ? sharps : flats)[chroma] +} export function tokenizeChord(chord) { - const match = (chord || '').match(/^([A-G][b#]*)([^/]*)[/]?([A-G][b#]*)?$/); + const match = (chord || '').match(/^([A-G][b#]*)([^/]*)[/]?([A-G][b#]*)?$/) if (!match) { // console.warn('could not tokenize chord', chord); - return []; + return [] } - return match.slice(1); + return match.slice(1) } -export const note2pc = (note) => note.match(/^[A-G][#b]?/i)[0]; -export const note2oct = (note) => tokenizeNote(note)[2]; -export const note2midi = noteToMidi; +export const note2pc = (note) => note.match(/^[A-G][#b]?/i)[0] +export const note2oct = (note) => tokenizeNote(note)[2] +export const note2midi = noteToMidi export const note2chroma = (note) => { - return pc2chroma(note2pc(note)); -}; + return pc2chroma(note2pc(note)) +} // TODO: test -export const midi2chroma = (midi) => midi % 12; +export const midi2chroma = (midi) => midi % 12 // TODO: test and use in voicing function export const pitch2chroma = (x, defaultOctave) => { if (isNoteWithOctave(x)) { - return note2chroma(x); + return note2chroma(x) } if (isNote(x)) { //pc - return pc2chroma(x, defaultOctave); + return pc2chroma(x, defaultOctave) } if (typeof x === 'number') { // expect midi - return midi2chroma(x); + return midi2chroma(x) } -}; +} export const step2semitones = (x) => { - let num = Number(x); + let num = Number(x) if (!isNaN(num)) { - return num; + return num } - return Interval.semitones(x); -}; + return Interval.semitones(x) +} export const x2midi = (x, defaultOctave) => { if (typeof x === 'number') { - return x; + return x } if (typeof x === 'string') { - return noteToMidi(x, defaultOctave); + return noteToMidi(x, defaultOctave) } -}; +} // duplicate: util.mjs (does not support sharp flag) export const midi2note = (midi, sharp = false) => { - const oct = Math.floor(midi / 12) - 1; - const pc = (sharp ? sharps : flats)[midi % 12]; - return pc + oct; -}; + const oct = Math.floor(midi / 12) - 1 + const pc = (sharp ? sharps : flats)[midi % 12] + return pc + oct +} export function scaleStep(notes, offset, octaves = 1) { - notes = notes.map((note) => (typeof note === 'string' ? noteToMidi(note) : note)); - const octOffset = Math.floor(offset / notes.length) * octaves * 12; - offset = _mod(offset, notes.length); - return notes[offset] + octOffset; + notes = notes.map((note) => (typeof note === 'string' ? noteToMidi(note) : note)) + const octOffset = Math.floor(offset / notes.length) * octaves * 12 + offset = _mod(offset, notes.length) + return notes[offset] + octOffset } export function nearestNumberIndex(target, numbers, preferHigher) { let bestIndex = 0, - bestDiff = Infinity; + bestDiff = Infinity numbers.forEach((s, i) => { - const diff = Math.abs(s - target); + const diff = Math.abs(s - target) // preferHigher only works if numbers are sorted in ascending order! if ((!preferHigher && diff < bestDiff) || (preferHigher && diff <= bestDiff)) { - bestIndex = i; - bestDiff = diff; + bestIndex = i + bestDiff = diff } - }); - return bestIndex; + }) + return bestIndex } -let scaleSteps = {}; // [scaleName]: semitones[] +let scaleSteps = {} // [scaleName]: semitones[] export function stepInNamedScale(step, scale, anchor, preferHigher) { - const [root, scaleName] = Scale.tokenize(scale); - const rootMidi = x2midi(root); - const rootChroma = midi2chroma(rootMidi); + const [root, scaleName] = Scale.tokenize(scale) + const rootMidi = x2midi(root) + const rootChroma = midi2chroma(rootMidi) if (!scaleSteps[scaleName]) { - const { intervals } = Scale.get(`C ${scaleName}`); + const { intervals } = Scale.get(`C ${scaleName}`) // cache result - scaleSteps[scaleName] = intervals.map(step2semitones); + scaleSteps[scaleName] = intervals.map(step2semitones) } - const steps = scaleSteps[scaleName]; + const steps = scaleSteps[scaleName] if (!steps) { - return null; + return null } - let transpose = rootMidi; + let transpose = rootMidi if (anchor) { - anchor = x2midi(anchor, 3); - const anchorChroma = midi2chroma(anchor); - const anchorDiff = _mod(anchorChroma - rootChroma, 12); - const zeroIndex = nearestNumberIndex(anchorDiff, steps, preferHigher); - step = step + zeroIndex; - transpose = anchor - anchorDiff; + anchor = x2midi(anchor, 3) + const anchorChroma = midi2chroma(anchor) + const anchorDiff = _mod(anchorChroma - rootChroma, 12) + const zeroIndex = nearestNumberIndex(anchorDiff, steps, preferHigher) + step = step + zeroIndex + transpose = anchor - anchorDiff } - const octOffset = Math.floor(step / steps.length) * 12; - step = _mod(step, steps.length); - const targetMidi = steps[step] + transpose; - return targetMidi + octOffset; + const octOffset = Math.floor(step / steps.length) * 12 + step = _mod(step, steps.length) + const targetMidi = steps[step] + transpose + return targetMidi + octOffset } // different ways to resolve the note to compare the anchor to (see renderVoicing) @@ -134,105 +134,115 @@ let modeTarget = { duck: (v) => v.slice(-1)[0], above: (v) => v[0], root: (v) => v[0], -}; +} -export function renderVoicing({ chord, dictionary, offset = 0, n, mode = 'below', anchor = 'c5', octaves = 1 }) { - const [root, symbol] = tokenizeChord(chord); - const rootChroma = pc2chroma(root); - anchor = x2midi(anchor?.note || anchor, 4); - const anchorChroma = midi2chroma(anchor); +export function renderVoicing({ + chord, + dictionary, + offset = 0, + n, + mode = 'below', + anchor = 'c5', + octaves = 1, +}) { + const [root, symbol] = tokenizeChord(chord) + const rootChroma = pc2chroma(root) + anchor = x2midi(anchor?.note || anchor, 4) + const anchorChroma = midi2chroma(anchor) const voicings = dictionary[symbol].map((voicing) => - (typeof voicing === 'string' ? voicing.split(' ') : voicing).map(step2semitones), - ); + (typeof voicing === 'string' ? voicing.split(' ') : voicing).map(step2semitones) + ) - let minDistance, bestIndex; + let minDistance, bestIndex // calculate distances up from voicing top notes let chromaDiffs = voicings.map((v, i) => { - const targetStep = modeTarget[mode](v); - const diff = _mod(anchorChroma - targetStep - rootChroma, 12); + const targetStep = modeTarget[mode](v) + const diff = _mod(anchorChroma - targetStep - rootChroma, 12) if (minDistance === undefined || diff < minDistance) { - minDistance = diff; - bestIndex = i; + minDistance = diff + bestIndex = i } - return diff; - }); + return diff + }) if (mode === 'root') { - bestIndex = 0; + bestIndex = 0 } - const octDiff = Math.ceil(offset / voicings.length) * 12; - const indexWithOffset = _mod(bestIndex + offset, voicings.length); - const voicing = voicings[indexWithOffset]; - const targetStep = modeTarget[mode](voicing); - const anchorMidi = anchor - chromaDiffs[indexWithOffset] + octDiff; + const octDiff = Math.ceil(offset / voicings.length) * 12 + const indexWithOffset = _mod(bestIndex + offset, voicings.length) + const voicing = voicings[indexWithOffset] + const targetStep = modeTarget[mode](voicing) + const anchorMidi = anchor - chromaDiffs[indexWithOffset] + octDiff - const voicingMidi = voicing.map((v) => anchorMidi - targetStep + v); - let notes = voicingMidi.map((n) => midi2note(n)); + const voicingMidi = voicing.map((v) => anchorMidi - targetStep + v) + let notes = voicingMidi.map((n) => midi2note(n)) if (mode === 'duck') { - notes = notes.filter((_, i) => voicingMidi[i] !== anchor); + notes = notes.filter((_, i) => voicingMidi[i] !== anchor) } if (n !== undefined) { - return [scaleStep(notes, n, octaves)]; + return [scaleStep(notes, n, octaves)] } - return notes; + return notes } // https://codeberg.org/uzu/strudel/blob/14184993d0ee7d69c47df57ac864a1a0f99a893f/packages/tonal/tonleiter.mjs -const steps = [1, 0, 2, 0, 3, 4, 0, 5, 0, 6, 0, 7]; -const notes = ['C', '', 'D', '', 'E', 'F', '', 'G', '', 'A', '', 'B']; -const noteLetters = ['C', 'D', 'E', 'F', 'G', 'A', 'B']; +const steps = [1, 0, 2, 0, 3, 4, 0, 5, 0, 6, 0, 7] +const notes = ['C', '', 'D', '', 'E', 'F', '', 'G', '', 'A', '', 'B'] +const noteLetters = ['C', 'D', 'E', 'F', 'G', 'A', 'B'] export const accidentalOffset = (accidentals) => { - return accidentals.split('#').length - accidentals.split('b').length; -}; + return accidentals.split('#').length - accidentals.split('b').length +} const accidentalString = (offset) => { if (offset < 0) { - return 'b'.repeat(-offset); + return 'b'.repeat(-offset) } if (offset > 0) { - return '#'.repeat(offset); + return '#'.repeat(offset) } - return ''; -}; + return '' +} export const Step = { tokenize(step) { - const matches = step.match(/^([#b]*)([1-9][0-9]*)$/); + const matches = step.match(/^([#b]*)([1-9][0-9]*)$/) if (!matches) { - throw new Error(`Step.tokenize: not a valid step: ${step}`); + throw new Error(`Step.tokenize: not a valid step: ${step}`) } - const [accidentals, stepNumber] = matches.slice(1); - return [accidentals, parseInt(stepNumber)]; + const [accidentals, stepNumber] = matches.slice(1) + return [accidentals, parseInt(stepNumber)] }, accidentals(step) { - return accidentalOffset(Step.tokenize(step)[0]); + return accidentalOffset(Step.tokenize(step)[0]) }, -}; +} export const Note = { // TODO: support octave numbers tokenize(note) { - return [note[0], note.slice(1)]; + return [note[0], note.slice(1)] }, accidentals(note) { - return accidentalOffset(this.tokenize(note)[1]); + return accidentalOffset(this.tokenize(note)[1]) }, -}; +} // TODO: support octave numbers // Example: Note("Bb3").transpose("c3") export function transpose(note, step) { // example: E, 3 - const stepNumber = Step.tokenize(step)[1]; // 3 - const noteLetter = Note.tokenize(note)[0]; // E - const noteIndex = noteLetters.indexOf(noteLetter); // 2 "E is C+2" - const targetNote = noteLetters[(noteIndex + stepNumber - 1) % 8]; // G "G is a third above E" - const rootIndex = notes.indexOf(noteLetter); // 4 "E is 4 semitones above C" - const targetIndex = notes.indexOf(targetNote); // 7 "G is 7 semitones above C" - const indexOffset = targetIndex - rootIndex; // 3 (E to G is normally a 3 semitones) - const stepIndex = steps.indexOf(stepNumber); // 4 ("3" is normally 4 semitones) - const offsetAccidentals = accidentalString(Step.accidentals(step) + Note.accidentals(note) + stepIndex - indexOffset); // "we need to add a # to to the G to make it a major third from E" - return [targetNote, offsetAccidentals].join(''); + const stepNumber = Step.tokenize(step)[1] // 3 + const noteLetter = Note.tokenize(note)[0] // E + const noteIndex = noteLetters.indexOf(noteLetter) // 2 "E is C+2" + const targetNote = noteLetters[(noteIndex + stepNumber - 1) % 8] // G "G is a third above E" + const rootIndex = notes.indexOf(noteLetter) // 4 "E is 4 semitones above C" + const targetIndex = notes.indexOf(targetNote) // 7 "G is 7 semitones above C" + const indexOffset = targetIndex - rootIndex // 3 (E to G is normally a 3 semitones) + const stepIndex = steps.indexOf(stepNumber) // 4 ("3" is normally 4 semitones) + const offsetAccidentals = accidentalString( + Step.accidentals(step) + Note.accidentals(note) + stepIndex - indexOffset + ) // "we need to add a # to to the G to make it a major third from E" + return [targetNote, offsetAccidentals].join('') } diff --git a/src/strudel/tonal/voicings.mjs b/src/strudel/tonal/voicings.mjs index d81911e..c9be7cd 100644 --- a/src/strudel/tonal/voicings.mjs +++ b/src/strudel/tonal/voicings.mjs @@ -4,11 +4,11 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { stack, register, silence, logger } from '@strudel/core'; -import { renderVoicing } from './tonleiter.mjs'; -import _voicings from 'chord-voicings'; -import { complex, simple } from './ireal.mjs'; -const { dictionaryVoicing, minTopNoteDiff } = _voicings.default || _voicings; // parcel module resolution fuckup +import { stack, register, silence, logger } from '../core/index.mjs' +import { renderVoicing } from './tonleiter.mjs' +import _voicings from 'chord-voicings' +import { complex, simple } from './ireal.mjs' +const { dictionaryVoicing, minTopNoteDiff } = _voicings.default || _voicings // parcel module resolution fuckup const lefthand = { m7: ['3m 5P 7m 9M', '7m 9M 10m 12P'], @@ -23,7 +23,7 @@ const lefthand = { '7#9': ['3M 7m 9A'], mM7: ['3m 5P 7M 9M', '7M 9M 10m 12P'], m6: ['3m 5P 6M 9M', '6M 9M 10m 12P'], -}; +} const guidetones = { m7: ['3m 7m', '7m 10m'], @@ -41,7 +41,7 @@ const guidetones = { '7#9': ['3M 7m', '7m 10M'], mM7: ['3m 7M', '7M 10m'], m6: ['3m 6M', '6M 10m'], -}; +} const triads = { '': ['1P 3M 5P', '3M 5P 8P', '5P 8P 10M'], @@ -49,7 +49,7 @@ const triads = { m: ['1P 3m 5P', '3m 5P 8P', '5P 8P 10m'], o: ['1P 3m 5d', '3m 5d 8P', '5d 8P 10m'], aug: ['1P 3m 5A', '3m 5A 8P', '5A 8P 10m'], -}; +} const defaultDictionary = { // triads @@ -71,20 +71,21 @@ const defaultDictionary = { '7#9': ['3M 7m 9A'], mM7: ['3m 5P 7M 9M', '7M 9M 10m 12P'], m6: ['3m 5P 6M 9M', '6M 9M 10m 12P'], -}; +} export const voicingRegistry = { lefthand: { dictionary: lefthand, range: ['F3', 'A4'], mode: 'below', anchor: 'a4' }, triads: { dictionary: triads, mode: 'below', anchor: 'a4' }, guidetones: { dictionary: guidetones, mode: 'above', anchor: 'a4' }, legacy: { dictionary: defaultDictionary, mode: 'below', anchor: 'a4' }, -}; +} -let defaultDict = 'ireal'; -export const setDefaultVoicings = (dict) => (defaultDict = dict); +let defaultDict = 'ireal' +export const setDefaultVoicings = (dict) => (defaultDict = dict) // e.g. typeof setDefaultVoicings !== 'undefined' && setDefaultVoicings('legacy'); -export const setVoicingRange = (name, range) => addVoicings(name, voicingRegistry[name].dictionary, range); +export const setVoicingRange = (name, range) => + addVoicings(name, voicingRegistry[name].dictionary, range) /** * Adds a new custom voicing dictionary. @@ -108,24 +109,24 @@ export const setVoicingRange = (name, range) => addVoicings(name, voicingRegistr * "".voicings('cookie').note() */ export const addVoicings = (name, dictionary, range = ['F3', 'A4']) => { - Object.assign(voicingRegistry, { [name]: { dictionary, range } }); -}; + Object.assign(voicingRegistry, { [name]: { dictionary, range } }) +} // new call signature export const registerVoicings = (name, dictionary, options = {}) => { - Object.assign(voicingRegistry, { [name]: { dictionary, ...options } }); -}; + Object.assign(voicingRegistry, { [name]: { dictionary, ...options } }) +} const getVoicing = (chord, dictionaryName, lastVoicing) => { - const { dictionary, range } = voicingRegistry[dictionaryName]; + const { dictionary, range } = voicingRegistry[dictionaryName] return dictionaryVoicing({ chord, dictionary, range, picker: minTopNoteDiff, lastVoicing, - }); -}; + }) +} /** * DEPRECATED: still works, but it is recommended you use .voicing instead (without s). @@ -140,18 +141,18 @@ const getVoicing = (chord, dictionaryName, lastVoicing) => { * stack("".voicings('lefthand'), "").note() */ -let lastVoicing; // this now has to be global until another solution is found :-/ +let lastVoicing // this now has to be global until another solution is found :-/ // it used to be local to the voicings function at evaluation time // but since register will patternify by default, means that // the function is called over and over again, resetting the lastVoicing variables export const voicings = register('voicings', function (dictionary, pat) { return pat .fmap((value) => { - lastVoicing = getVoicing(value, dictionary, lastVoicing); - return stack(...lastVoicing); + lastVoicing = getVoicing(value, dictionary, lastVoicing) + return stack(...lastVoicing) }) - .outerJoin(); -}); + .outerJoin() +}) /** * Maps the chords of the incoming pattern to root notes in the given octave. @@ -165,12 +166,12 @@ export const voicings = register('voicings', function (dictionary, pat) { */ export const rootNotes = register('rootNotes', function (octave, pat) { return pat.fmap((value) => { - const chord = value.chord || value; - const root = chord.match(/^([a-gA-G][b#]?).*$/)[1]; - const note = root + octave; - return value.chord ? { note } : note; - }); -}); + const chord = value.chord || value + const root = chord.match(/^([a-gA-G][b#]?).*$/)[1] + const note = root + octave + return value.chord ? { note } : note + }) +}) /** * Turns chord symbols into voicings. You can use the following control params: @@ -197,55 +198,57 @@ export const voicing = register('voicing', function (pat) { return pat .fmap((value) => { // destructure voicing controls out - value = typeof value === 'string' ? { chord: value } : value; - let { dictionary = defaultDict, chord, anchor, offset, mode, n, octaves, ...rest } = value; + value = typeof value === 'string' ? { chord: value } : value + let { dictionary = defaultDict, chord, anchor, offset, mode, n, octaves, ...rest } = value dictionary = - typeof dictionary === 'string' ? voicingRegistry[dictionary] : { dictionary, mode: 'below', anchor: 'c5' }; + typeof dictionary === 'string' + ? voicingRegistry[dictionary] + : { dictionary, mode: 'below', anchor: 'c5' } try { - let notes = renderVoicing({ ...dictionary, chord, anchor, offset, mode, n, octaves }); + let notes = renderVoicing({ ...dictionary, chord, anchor, offset, mode, n, octaves }) return stack(...notes) .note() - .set(rest); // rest does not include voicing controls anymore! + .set(rest) // rest does not include voicing controls anymore! } catch (err) { - logger(`[voicing]: unknown chord "${chord}"`); - return silence; + logger(`[voicing]: unknown chord "${chord}"`) + return silence } }) - .outerJoin(); -}); + .outerJoin() +}) export function voicingAlias(symbol, alias, setOrSets) { - setOrSets = !Array.isArray(setOrSets) ? [setOrSets] : setOrSets; + setOrSets = !Array.isArray(setOrSets) ? [setOrSets] : setOrSets setOrSets.forEach((set) => { - set[alias] = set[symbol]; - }); + set[alias] = set[symbol] + }) } // no symbol = major chord -voicingAlias('^', '', [simple, complex]); +voicingAlias('^', '', [simple, complex]) Object.keys(simple).forEach((symbol) => { // add aliases for "-" === "m" if (symbol.includes('-')) { - let alias = symbol.replace('-', 'm'); - voicingAlias(symbol, alias, [complex, simple]); + let alias = symbol.replace('-', 'm') + voicingAlias(symbol, alias, [complex, simple]) } // add aliases for "^" === "M" if (symbol.includes('^')) { - let alias = symbol.replace('^', 'M'); - voicingAlias(symbol, alias, [complex, simple]); + let alias = symbol.replace('^', 'M') + voicingAlias(symbol, alias, [complex, simple]) } // add aliases for "+" === "aug" if (symbol.includes('+')) { - let alias = symbol.replace('+', 'aug'); - voicingAlias(symbol, alias, [complex, simple]); + let alias = symbol.replace('+', 'aug') + voicingAlias(symbol, alias, [complex, simple]) } -}); +}) -registerVoicings('ireal', simple); -registerVoicings('ireal-ext', complex); +registerVoicings('ireal', simple) +registerVoicings('ireal-ext', complex) export function resetVoicings() { - lastVoicing = undefined; - setDefaultVoicings('ireal'); + lastVoicing = undefined + setDefaultVoicings('ireal') } diff --git a/src/strudel/transpiler/index.mjs b/src/strudel/transpiler/index.mjs index 484c3e7..989fdb7 100644 --- a/src/strudel/transpiler/index.mjs +++ b/src/strudel/transpiler/index.mjs @@ -1,5 +1,5 @@ -import { evaluate as _evaluate } from '@strudel/core'; -import { transpiler } from './transpiler.mjs'; -export * from './transpiler.mjs'; +import { evaluate as _evaluate } from '../core/index.mjs' +import { transpiler } from './transpiler.mjs' +export * from './transpiler.mjs' -export const evaluate = (code) => _evaluate(code, transpiler); +export const evaluate = (code) => _evaluate(code, transpiler) diff --git a/src/strudel/transpiler/transpiler.mjs b/src/strudel/transpiler/transpiler.mjs index fea6bad..b15862f 100644 --- a/src/strudel/transpiler/transpiler.mjs +++ b/src/strudel/transpiler/transpiler.mjs @@ -1,82 +1,87 @@ -import { getLeafLocations } from '@strudel/mini'; -import { parse } from 'acorn'; -import escodegen from 'escodegen'; -import { walk } from 'estree-walker'; +import { getLeafLocations } from '../mini/index.mjs' +import { parse } from 'acorn' +import escodegen from 'escodegen' +import { walk } from 'estree-walker' -let widgetMethods = []; +let widgetMethods = [] export function registerWidgetType(type) { - widgetMethods.push(type); + widgetMethods.push(type) } -let languages = new Map(); +let languages = new Map() // config = { getLocations: (code: string, offset?: number) => number[][] } // see mondough.mjs for example use // the language will kick in when the code contains a template literal of type // example: mondo`...` will use language of type "mondo" // TODO: refactor tidal.mjs to use this export function registerLanguage(type, config) { - languages.set(type, config); + languages.set(type, config) } export function transpiler(input, options = {}) { - const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options; + const { + wrapAsync = false, + addReturn = true, + emitMiniLocations = true, + emitWidgets = true, + } = options let ast = parse(input, { ecmaVersion: 2022, allowAwaitOutsideFunction: true, locations: true, - }); + }) - let miniLocations = []; + let miniLocations = [] const collectMiniLocations = (value, node) => { - const minilang = languages.get('minilang'); + const minilang = languages.get('minilang') if (minilang) { - const code = `[${value}]`; - const locs = minilang.getLocations(code, node.start); - miniLocations = miniLocations.concat(locs); + const code = `[${value}]` + const locs = minilang.getLocations(code, node.start) + miniLocations = miniLocations.concat(locs) } else { - const leafLocs = getLeafLocations(`"${value}"`, node.start, input); - miniLocations = miniLocations.concat(leafLocs); + const leafLocs = getLeafLocations(`"${value}"`, node.start, input) + miniLocations = miniLocations.concat(leafLocs) } - }; - let widgets = []; + } + let widgets = [] walk(ast, { enter(node, parent /* , prop, index */) { if (isLanguageLiteral(node)) { - const { name } = node.tag; - const language = languages.get(name); - const code = node.quasi.quasis[0].value.raw; - const offset = node.quasi.start + 1; + const { name } = node.tag + const language = languages.get(name) + const code = node.quasi.quasis[0].value.raw + const offset = node.quasi.start + 1 if (emitMiniLocations) { - const locs = language.getLocations(code, offset); - miniLocations = miniLocations.concat(locs); + const locs = language.getLocations(code, offset) + miniLocations = miniLocations.concat(locs) } - this.skip(); - return this.replace(languageWithLocation(name, code, offset)); + this.skip() + return this.replace(languageWithLocation(name, code, offset)) } if (isTemplateLiteral(node, 'tidal')) { - const raw = node.quasi.quasis[0].value.raw; - const offset = node.quasi.start + 1; + const raw = node.quasi.quasis[0].value.raw + const offset = node.quasi.start + 1 if (emitMiniLocations) { - const stringLocs = collectHaskellMiniLocations(raw, offset); - miniLocations = miniLocations.concat(stringLocs); + const stringLocs = collectHaskellMiniLocations(raw, offset) + miniLocations = miniLocations.concat(stringLocs) } - this.skip(); - return this.replace(tidalWithLocation(raw, offset)); + this.skip() + return this.replace(tidalWithLocation(raw, offset)) } if (isBackTickString(node, parent)) { - const { quasis } = node; - const { raw } = quasis[0].value; - this.skip(); - emitMiniLocations && collectMiniLocations(raw, node); - return this.replace(miniWithLocation(raw, node)); + const { quasis } = node + const { raw } = quasis[0].value + this.skip() + emitMiniLocations && collectMiniLocations(raw, node) + return this.replace(miniWithLocation(raw, node)) } if (isStringWithDoubleQuotes(node)) { - const { value } = node; - this.skip(); - emitMiniLocations && collectMiniLocations(value, node); - return this.replace(miniWithLocation(value, node)); + const { value } = node + this.skip() + emitMiniLocations && collectMiniLocations(value, node) + return this.replace(miniWithLocation(value, node)) } if (isSliderFunction(node)) { emitWidgets && @@ -88,82 +93,84 @@ export function transpiler(input, options = {}) { max: node.arguments[2]?.value ?? 1, step: node.arguments[3]?.value, type: 'slider', - }); - return this.replace(sliderWithLocation(node)); + }) + return this.replace(sliderWithLocation(node)) } if (isWidgetMethod(node)) { - const type = node.callee.property.name; - const index = widgets.filter((w) => w.type === type).length; + const type = node.callee.property.name + const index = widgets.filter((w) => w.type === type).length const widgetConfig = { to: node.end, index, type, id: options.id, - }; - emitWidgets && widgets.push(widgetConfig); - return this.replace(widgetWithLocation(node, widgetConfig)); + } + emitWidgets && widgets.push(widgetConfig) + return this.replace(widgetWithLocation(node, widgetConfig)) } if (isBareSamplesCall(node, parent)) { - return this.replace(withAwait(node)); + return this.replace(withAwait(node)) } if (isLabelStatement(node)) { - return this.replace(labelToP(node)); + return this.replace(labelToP(node)) } }, - leave(node, parent, prop, index) {}, - }); + leave(node, parent, prop, index) { + return + }, + }) - let { body } = ast; + let { body } = ast if (!body.length) { - console.warn('empty body -> fallback to silence'); + console.warn('empty body -> fallback to silence') body.push({ type: 'ExpressionStatement', expression: { type: 'Identifier', name: 'silence', }, - }); + }) } else if (!body?.[body.length - 1]?.expression) { - throw new Error('unexpected ast format without body expression'); + throw new Error('unexpected ast format without body expression') } // add return to last statement if (addReturn) { - const { expression } = body[body.length - 1]; + const { expression } = body[body.length - 1] body[body.length - 1] = { type: 'ReturnStatement', argument: expression, - }; + } } - let output = escodegen.generate(ast); + let output = escodegen.generate(ast) if (wrapAsync) { - output = `(async ()=>{${output}})()`; + output = `(async ()=>{${output}})()` } if (!emitMiniLocations) { - return { output }; + return { output } } - return { output, miniLocations, widgets }; + return { output, miniLocations, widgets } } function isStringWithDoubleQuotes(node, locations, code) { if (node.type !== 'Literal') { - return false; + return false } - return node.raw[0] === '"'; + return node.raw[0] === '"' } function isBackTickString(node, parent) { - return node.type === 'TemplateLiteral' && parent.type !== 'TaggedTemplateExpression'; + return node.type === 'TemplateLiteral' && parent.type !== 'TaggedTemplateExpression' } function miniWithLocation(value, node) { - const { start: fromOffset } = node; + const { start: fromOffset } = node - const minilang = languages.get('minilang'); - let name = 'm'; + const minilang = languages.get('minilang') + let name = 'm' if (minilang && minilang.name) { - name = minilang.name; // name is expected to be exported from the package of the minilang + name = minilang.name // name is expected to be exported from the package of the minilang } return { @@ -177,30 +184,30 @@ function miniWithLocation(value, node) { { type: 'Literal', value: fromOffset }, ], optional: false, - }; + } } // these functions are connected to @strudel/codemirror -> slider.mjs // maybe someday there will be pluggable transpiler functions, then move this there function isSliderFunction(node) { - return node.type === 'CallExpression' && node.callee.name === 'slider'; + return node.type === 'CallExpression' && node.callee.name === 'slider' } function isWidgetMethod(node) { - return node.type === 'CallExpression' && widgetMethods.includes(node.callee.property?.name); + return node.type === 'CallExpression' && widgetMethods.includes(node.callee.property?.name) } function sliderWithLocation(node) { - const id = 'slider_' + node.arguments[0].start; // use loc of first arg for id + const id = 'slider_' + node.arguments[0].start // use loc of first arg for id // add loc as identifier to first argument // the sliderWithID function is assumed to be sliderWithID(id, value, min?, max?) node.arguments.unshift({ type: 'Literal', value: id, raw: id, - }); - node.callee.name = 'sliderWithID'; - return node; + }) + node.callee.name = 'sliderWithID' + return node } export function getWidgetID(widgetConfig) { @@ -209,34 +216,38 @@ export function getWidgetID(widgetConfig) { // that means, if we use the index index of line position as id, less garbage is generated // return `widget_${widgetConfig.to}`; // more gargabe //return `widget_${widgetConfig.index}_${widgetConfig.to}`; // also more garbage - return `${widgetConfig.id || ''}_widget_${widgetConfig.type}_${widgetConfig.index}`; // less garbage + return `${widgetConfig.id || ''}_widget_${widgetConfig.type}_${widgetConfig.index}` // less garbage } function widgetWithLocation(node, widgetConfig) { - const id = getWidgetID(widgetConfig); + const id = getWidgetID(widgetConfig) // add loc as identifier to first argument // the sliderWithID function is assumed to be sliderWithID(id, value, min?, max?) node.arguments.unshift({ type: 'Literal', value: id, raw: id, - }); - return node; + }) + return node } function isBareSamplesCall(node, parent) { - return node.type === 'CallExpression' && node.callee.name === 'samples' && parent.type !== 'AwaitExpression'; + return ( + node.type === 'CallExpression' && + node.callee.name === 'samples' && + parent.type !== 'AwaitExpression' + ) } function withAwait(node) { return { type: 'AwaitExpression', argument: node, - }; + } } function isLabelStatement(node) { - return node.type === 'LabeledStatement'; + return node.type === 'LabeledStatement' } // converts label expressions to p calls: "x: y" to "y.p('x')" @@ -262,11 +273,11 @@ function labelToP(node) { }, ], }, - }; + } } function isLanguageLiteral(node) { - return node.type === 'TaggedTemplateExpression' && languages.has(node.tag.name); + return node.type === 'TaggedTemplateExpression' && languages.has(node.tag.name) } // tidal highlighting @@ -274,7 +285,7 @@ function isLanguageLiteral(node) { // but maybe it's the only way function isTemplateLiteral(node, value) { - return node.type === 'TaggedTemplateExpression' && node.tag.name === value; + return node.type === 'TaggedTemplateExpression' && node.tag.name === value } function collectHaskellMiniLocations(haskellCode, offset) { @@ -282,20 +293,20 @@ function collectHaskellMiniLocations(haskellCode, offset) { .split('') .reduce((acc, char, i) => { if (char !== '"') { - return acc; + return acc } if (!acc.length || acc[acc.length - 1].length > 1) { - acc.push([i + 1]); + acc.push([i + 1]) } else { - acc[acc.length - 1].push(i); + acc[acc.length - 1].push(i) } - return acc; + return acc }, []) .map(([start, end]) => { - const miniString = haskellCode.slice(start, end); - return getLeafLocations(`"${miniString}"`, offset + start - 1); + const miniString = haskellCode.slice(start, end) + return getLeafLocations(`"${miniString}"`, offset + start - 1) }) - .flat(); + .flat() } function tidalWithLocation(value, offset) { @@ -310,7 +321,7 @@ function tidalWithLocation(value, offset) { { type: 'Literal', value: offset }, ], optional: false, - }; + } } function languageWithLocation(name, value, offset) { @@ -325,5 +336,5 @@ function languageWithLocation(name, value, offset) { { type: 'Literal', value: offset }, ], optional: false, - }; + } } diff --git a/src/strudel/webaudio/index.mjs b/src/strudel/webaudio/index.mjs index 4933b7a..e977a76 100644 --- a/src/strudel/webaudio/index.mjs +++ b/src/strudel/webaudio/index.mjs @@ -4,8 +4,8 @@ Copyright (C) 2022 Strudel contributors - see . */ -export * from './webaudio.mjs'; -export * from './scope.mjs'; -export * from './spectrum.mjs'; -export * from './supradough.mjs'; -export * from 'superdough'; +export * from './webaudio.mjs' +export * from './scope.mjs' +export * from './spectrum.mjs' +export * from './supradough.mjs' +export * from '../superdough/index.mjs' diff --git a/src/strudel/webaudio/scope.mjs b/src/strudel/webaudio/scope.mjs index e423b20..fe3267f 100644 --- a/src/strudel/webaudio/scope.mjs +++ b/src/strudel/webaudio/scope.mjs @@ -1,6 +1,6 @@ -import { Pattern, clamp } from '@strudel/core'; -import { getDrawContext, getTheme } from '@strudel/draw'; -import { analysers, getAnalyzerData } from 'superdough'; +import { Pattern, clamp } from '../core/index.mjs' +import { getDrawContext, getTheme } from '../draw/index.mjs' +import { analysers, getAnalyzerData } from '../superdough/index.mjs' export function drawTimeScope( analyser, @@ -13,85 +13,94 @@ export function drawTimeScope( trigger = 0, ctx = getDrawContext(), id = 1, - } = {}, + } = {} ) { - ctx.lineWidth = thickness; - ctx.strokeStyle = color; - let canvas = ctx.canvas; + ctx.lineWidth = thickness + ctx.strokeStyle = color + let canvas = ctx.canvas if (!analyser) { // if analyser is undefined, draw straight line // it may be undefined when no sound has been played yet - ctx.beginPath(); - let y = pos * canvas.height; - ctx.moveTo(0, y); - ctx.lineTo(canvas.width, y); - ctx.stroke(); - return; + ctx.beginPath() + let y = pos * canvas.height + ctx.moveTo(0, y) + ctx.lineTo(canvas.width, y) + ctx.stroke() + return } - const dataArray = getAnalyzerData('time', id); + const dataArray = getAnalyzerData('time', id) - ctx.beginPath(); + ctx.beginPath() - const bufferSize = analyser.frequencyBinCount; + const bufferSize = analyser.frequencyBinCount let triggerIndex = align ? Array.from(dataArray).findIndex((v, i, arr) => i && arr[i - 1] > -trigger && v <= -trigger) - : 0; - triggerIndex = Math.max(triggerIndex, 0); // fallback to 0 when no trigger is found + : 0 + triggerIndex = Math.max(triggerIndex, 0) // fallback to 0 when no trigger is found - const sliceWidth = (canvas.width * 1.0) / bufferSize; - let x = 0; + const sliceWidth = (canvas.width * 1.0) / bufferSize + let x = 0 for (let i = triggerIndex; i < bufferSize; i++) { - const v = dataArray[i] + 1; - const y = (pos - scale * (v - 1)) * canvas.height; + const v = dataArray[i] + 1 + const y = (pos - scale * (v - 1)) * canvas.height if (i === 0) { - ctx.moveTo(x, y); + ctx.moveTo(x, y) } else { - ctx.lineTo(x, y); + ctx.lineTo(x, y) } - x += sliceWidth; + x += sliceWidth } - ctx.stroke(); + ctx.stroke() } export function drawFrequencyScope( analyser, - { color = 'white', scale = 0.25, pos = 0.75, lean = 0.5, min = -150, max = 0, ctx = getDrawContext(), id = 1 } = {}, + { + color = 'white', + scale = 0.25, + pos = 0.75, + lean = 0.5, + min = -150, + max = 0, + ctx = getDrawContext(), + id = 1, + } = {} ) { if (!analyser) { - ctx.beginPath(); - let y = pos * canvas.height; - ctx.moveTo(0, y); - ctx.lineTo(canvas.width, y); - ctx.stroke(); - return; + ctx.beginPath() + let y = pos * canvas.height + ctx.moveTo(0, y) + ctx.lineTo(canvas.width, y) + ctx.stroke() + return } - const dataArray = getAnalyzerData('frequency', id); - const canvas = ctx.canvas; + const dataArray = getAnalyzerData('frequency', id) + const canvas = ctx.canvas - ctx.fillStyle = color; - const bufferSize = analyser.frequencyBinCount; - const sliceWidth = (canvas.width * 1.0) / bufferSize; + ctx.fillStyle = color + const bufferSize = analyser.frequencyBinCount + const sliceWidth = (canvas.width * 1.0) / bufferSize - let x = 0; + let x = 0 for (let i = 0; i < bufferSize; i++) { - const normalized = clamp((dataArray[i] - min) / (max - min), 0, 1); - const v = normalized * scale; - const h = v * canvas.height; - const y = (pos - v * lean) * canvas.height; + const normalized = clamp((dataArray[i] - min) / (max - min), 0, 1) + const v = normalized * scale + const h = v * canvas.height + const y = (pos - v * lean) * canvas.height - ctx.fillRect(x, y, Math.max(sliceWidth, 1), h); - x += sliceWidth; + ctx.fillRect(x, y, Math.max(sliceWidth, 1), h) + x += sliceWidth } } function clearScreen(smear = 0, smearRGB = `0,0,0`, ctx = getDrawContext()) { if (!smear) { - ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height) } else { - ctx.fillStyle = `rgba(${smearRGB},${1 - smear})`; - ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.fillStyle = `rgba(${smearRGB},${1 - smear})` + ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height) } } @@ -108,15 +117,15 @@ function clearScreen(smear = 0, smearRGB = `0,0,0`, ctx = getDrawContext()) { * s("sawtooth").fscope() */ Pattern.prototype.fscope = function (config = {}) { - let id = config.id ?? 1; + let id = config.id ?? 1 return this.analyze(id).draw( () => { - clearScreen(config.smear, '0,0,0', config.ctx); - analysers[id] && drawFrequencyScope(analysers[id], config); + clearScreen(config.smear, '0,0,0', config.ctx) + analysers[id] && drawFrequencyScope(analysers[id], config) }, - { id }, - ); -}; + { id } + ) +} /** * Renders an oscilloscope for the time domain of the audio signal. @@ -132,18 +141,18 @@ Pattern.prototype.fscope = function (config = {}) { * @example * s("sawtooth")._scope() */ -let latestColor = {}; +let latestColor = {} Pattern.prototype.tscope = function (config = {}) { - let id = config.id ?? 1; + let id = config.id ?? 1 return this.analyze(id).draw( (haps) => { - config.color = haps[0]?.value?.color || getTheme().foreground; - latestColor[id] = config.color; - clearScreen(config.smear, '0,0,0', config.ctx); - drawTimeScope(analysers[id], config); + config.color = haps[0]?.value?.color || getTheme().foreground + latestColor[id] = config.color + clearScreen(config.smear, '0,0,0', config.ctx) + drawTimeScope(analysers[id], config) }, - { id }, - ); -}; + { id } + ) +} -Pattern.prototype.scope = Pattern.prototype.tscope; +Pattern.prototype.scope = Pattern.prototype.tscope diff --git a/src/strudel/webaudio/spectrum.mjs b/src/strudel/webaudio/spectrum.mjs index 2ddd214..c62345e 100644 --- a/src/strudel/webaudio/spectrum.mjs +++ b/src/strudel/webaudio/spectrum.mjs @@ -1,6 +1,6 @@ -import { Pattern, clamp } from '@strudel/core'; -import { getDrawContext, getTheme } from '@strudel/draw'; -import { analysers, getAnalyzerData } from 'superdough'; +import { Pattern, clamp } from '../core/index.mjs' +import { getDrawContext, getTheme } from '../draw/index.mjs' +import { analysers, getAnalyzerData } from '../superdough/index.mjs' /** * Renders a spectrum analyzer for the incoming audio signal. @@ -19,51 +19,51 @@ import { analysers, getAnalyzerData } from 'superdough'; * .dec(.3).room(.5) * ._spectrum() */ -let latestColor = {}; +let latestColor = {} Pattern.prototype.spectrum = function (config = {}) { - let id = config.id ?? 1; + let id = config.id ?? 1 return this.analyze(id).draw( (haps) => { - config.color = haps[0]?.value?.color || latestColor[id] || getTheme().foreground; - latestColor[id] = config.color; - drawSpectrum(analysers[id], config); + config.color = haps[0]?.value?.color || latestColor[id] || getTheme().foreground + latestColor[id] = config.color + drawSpectrum(analysers[id], config) }, - { id }, - ); -}; + { id } + ) +} -Pattern.prototype.scope = Pattern.prototype.tscope; +Pattern.prototype.scope = Pattern.prototype.tscope -const lastFrames = new Map(); +const lastFrames = new Map() function drawSpectrum( analyser, - { thickness = 3, speed = 1, min = -80, max = 0, ctx = getDrawContext(), id = 1, color } = {}, + { thickness = 3, speed = 1, min = -80, max = 0, ctx = getDrawContext(), id = 1, color } = {} ) { - ctx.lineWidth = thickness; - ctx.strokeStyle = color; + ctx.lineWidth = thickness + ctx.strokeStyle = color if (!analyser) { // if analyser is undefined, draw straight line // it may be undefined when no sound has been played yet - return; + return } - const scrollSize = speed; - const dataArray = getAnalyzerData('frequency', id); - const canvas = ctx.canvas; - ctx.fillStyle = color; - const bufferSize = analyser.frequencyBinCount; - let imageData = lastFrames.get(id) || ctx.getImageData(0, 0, canvas.width, canvas.height); - lastFrames.set(id, imageData); - ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); - ctx.putImageData(imageData, -scrollSize, 0); - let q = canvas.width - speed; + const scrollSize = speed + const dataArray = getAnalyzerData('frequency', id) + const canvas = ctx.canvas + ctx.fillStyle = color + const bufferSize = analyser.frequencyBinCount + let imageData = lastFrames.get(id) || ctx.getImageData(0, 0, canvas.width, canvas.height) + lastFrames.set(id, imageData) + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height) + ctx.putImageData(imageData, -scrollSize, 0) + let q = canvas.width - speed for (let i = 0; i < bufferSize; i++) { - const normalized = clamp((dataArray[i] - min) / (max - min), 0, 1); - ctx.globalAlpha = normalized; - const next = (Math.log(i + 1) / Math.log(bufferSize)) * canvas.height; - const size = 2; //next - pos; - ctx.fillRect(q, canvas.height - next, scrollSize, size); + const normalized = clamp((dataArray[i] - min) / (max - min), 0, 1) + ctx.globalAlpha = normalized + const next = (Math.log(i + 1) / Math.log(bufferSize)) * canvas.height + const size = 2 //next - pos; + ctx.fillRect(q, canvas.height - next, scrollSize, size) } - lastFrames.set(id, ctx.getImageData(0, 0, canvas.width, canvas.height)); + lastFrames.set(id, ctx.getImageData(0, 0, canvas.width, canvas.height)) } diff --git a/src/strudel/webaudio/supradough.mjs b/src/strudel/webaudio/supradough.mjs index f97251a..1ea4571 100644 --- a/src/strudel/webaudio/supradough.mjs +++ b/src/strudel/webaudio/supradough.mjs @@ -1,104 +1,104 @@ -import { Pattern } from '@strudel/core'; -import { connectToDestination, getAudioContext, getWorklet } from 'superdough'; +import { Pattern } from '../core/index.mjs' +import { connectToDestination, getAudioContext, getWorklet } from '../superdough/index.mjs' -let doughWorklet; +let doughWorklet function initDoughWorklet() { - const ac = getAudioContext(); + const ac = getAudioContext() doughWorklet = getWorklet( ac, 'dough-processor', {}, { outputChannelCount: [2], - }, - ); - connectToDestination(doughWorklet); // channels? + } + ) + connectToDestination(doughWorklet) // channels? } -const soundMap = new Map(); -const loadedSounds = new Map(); +const soundMap = new Map() +const loadedSounds = new Map() Pattern.prototype.supradough = function () { return this.onTrigger((hap, __, cps, begin) => { - hap.value._begin = begin; - hap.value._duration = hap.duration / cps; - !doughWorklet && initDoughWorklet(); - const s = (hap.value.bank ? hap.value.bank + '_' : '') + hap.value.s; - const n = hap.value.n ?? 0; - const soundKey = `${s}:${n}`; + hap.value._begin = begin + hap.value._duration = hap.duration / cps + !doughWorklet && initDoughWorklet() + const s = (hap.value.bank ? hap.value.bank + '_' : '') + hap.value.s + const n = hap.value.n ?? 0 + const soundKey = `${s}:${n}` if (soundMap.has(s)) { - hap.value.s = soundKey; // dough.mjs is unaware of bank and n (only maps keys to buffers) + hap.value.s = soundKey // dough.mjs is unaware of bank and n (only maps keys to buffers) } if (soundMap.has(s) && !loadedSounds.has(soundKey)) { - const urls = soundMap.get(s); - const url = urls[n % urls.length]; - console.log(`load ${soundKey} from ${url}`); - const loadSample = fetchSample(url); - loadedSounds.set(soundKey, loadSample); + const urls = soundMap.get(s) + const url = urls[n % urls.length] + console.log(`load ${soundKey} from ${url}`) + const loadSample = fetchSample(url) + loadedSounds.set(soundKey, loadSample) loadSample.then(({ channels, sampleRate }) => doughWorklet.port.postMessage({ sample: soundKey, channels, sampleRate, - }), - ); + }) + ) } - doughWorklet.port.postMessage({ spawn: hap.value }); - }, 1); -}; + doughWorklet.port.postMessage({ spawn: hap.value }) + }, 1) +} function githubPath(base, subpath = '') { if (!base.startsWith('github:')) { - throw new Error('expected "github:" at the start of pseudoUrl'); + throw new Error('expected "github:" at the start of pseudoUrl') } - let [_, path] = base.split('github:'); - path = path.endsWith('/') ? path.slice(0, -1) : path; + 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'; + path += '/main' } - return `https://raw.githubusercontent.com/${path}/${subpath}`; + return `https://raw.githubusercontent.com/${path}/${subpath}` } export async function fetchSampleMap(url) { if (url.startsWith('github:')) { - url = githubPath(url, 'strudel.json'); + url = githubPath(url, 'strudel.json') } if (url.startsWith('local:')) { - url = `http://localhost:5432`; + url = `http://localhost:5432` } if (url.startsWith('shabda:')) { - let [_, path] = url.split('shabda:'); - url = `https://shabda.ndre.gr/${path}.json?strudel=1`; + let [_, path] = url.split('shabda:') + url = `https://shabda.ndre.gr/${path}.json?strudel=1` } if (url.startsWith('shabda/speech')) { - let [_, path] = url.split('shabda/speech'); - path = path.startsWith('/') ? path.substring(1) : path; - let [params, words] = path.split(':'); - let gender = 'f'; - let language = 'en-GB'; + let [_, path] = url.split('shabda/speech') + path = path.startsWith('/') ? path.substring(1) : path + let [params, words] = path.split(':') + let gender = 'f' + let language = 'en-GB' if (params) { - [language, gender] = params.split('/'); + ;[language, gender] = params.split('/') } - url = `https://shabda.ndre.gr/speech/${words}.json?gender=${gender}&language=${language}&strudel=1'`; + url = `https://shabda.ndre.gr/speech/${words}.json?gender=${gender}&language=${language}&strudel=1'` } if (typeof fetch !== 'function') { // not a browser - return; + return } - const base = url.split('/').slice(0, -1).join('/'); + const base = url.split('/').slice(0, -1).join('/') if (typeof fetch === 'undefined') { // skip fetch when in node / testing - return; + return } const json = await fetch(url) .then((res) => res.json()) .catch((error) => { - console.error(error); - throw new Error(`error loading "${url}"`); - }); - return [json, json._base || base]; + console.error(error) + throw new Error(`error loading "${url}"`) + }) + return [json, json._base || base] } // for some reason, only piano and flute work.. is it because mp3?? @@ -106,25 +106,25 @@ export async function fetchSampleMap(url) { async function fetchSample(url) { const buffer = await fetch(url) .then((res) => res.arrayBuffer()) - .then((buf) => getAudioContext().decodeAudioData(buf)); - let channels = []; + .then((buf) => getAudioContext().decodeAudioData(buf)) + let channels = [] for (let i = 0; i < buffer.numberOfChannels; i++) { - channels.push(buffer.getChannelData(i)); + channels.push(buffer.getChannelData(i)) } - return { channels, sampleRate: buffer.sampleRate }; + return { channels, sampleRate: buffer.sampleRate } } export async function doughsamples(sampleMap, baseUrl) { if (typeof sampleMap === 'string') { - const [json, base] = await fetchSampleMap(sampleMap); + const [json, base] = await fetchSampleMap(sampleMap) // console.log('json', json, 'base', base); - return doughsamples(json, base); + return doughsamples(json, base) } Object.entries(sampleMap).map(async ([key, urls]) => { if (key !== '_base') { - urls = urls.map((url) => baseUrl + url); + urls = urls.map((url) => baseUrl + url) // console.log('set', key, urls); - soundMap.set(key, urls); + soundMap.set(key, urls) } - }); + }) } diff --git a/src/strudel/webaudio/webaudio.mjs b/src/strudel/webaudio/webaudio.mjs index 383e87f..50d377c 100644 --- a/src/strudel/webaudio/webaudio.mjs +++ b/src/strudel/webaudio/webaudio.mjs @@ -4,37 +4,43 @@ Copyright (C) 2022 Strudel contributors - see . */ -import * as strudel from '@strudel/core'; -import { superdough, getAudioContext, setLogger, doughTrigger, registerWorklet } from 'superdough'; -import './supradough.mjs'; -import { workletUrl } from 'supradough'; +import * as strudel from '../core/index.mjs' +import { + superdough, + getAudioContext, + setLogger, + doughTrigger, + registerWorklet, +} from '../superdough/index.mjs' +import './supradough.mjs' +import { workletUrl } from '../supradough/index.mjs' -registerWorklet(workletUrl); +registerWorklet(workletUrl) -const { Pattern, logger, repl } = strudel; +const { Pattern, logger, repl } = strudel -setLogger(logger); +setLogger(logger) const hap2value = (hap) => { - hap.ensureObjectValue(); - return hap.value; -}; + hap.ensureObjectValue() + return hap.value +} // uses more precise, absolute t if available, see https://github.com/tidalcycles/strudel/pull/1004 // TODO: refactor output callbacks to eliminate deadline export const webaudioOutput = (hap, _deadline, hapDuration, cps, t) => { - return superdough(hap2value(hap), t, hapDuration, cps, hap.whole?.begin.valueOf()); -}; + return superdough(hap2value(hap), t, hapDuration, cps, hap.whole?.begin.valueOf()) +} export function webaudioRepl(options = {}) { options = { getTime: () => getAudioContext().currentTime, defaultOutput: webaudioOutput, ...options, - }; - return repl(options); + } + return repl(options) } Pattern.prototype.dough = function () { - return this.onTrigger(doughTrigger, 1); -}; + return this.onTrigger(doughTrigger, 1) +} diff --git a/src/strudel/xen/index.mjs b/src/strudel/xen/index.mjs index ca813f3..a26226a 100644 --- a/src/strudel/xen/index.mjs +++ b/src/strudel/xen/index.mjs @@ -1,4 +1,4 @@ -import './xen.mjs'; -import './tune.mjs'; +import './xen.mjs' +import './tune.mjs' -export * from './xen.mjs'; +export * from './xen.mjs' diff --git a/src/strudel/xen/tune.mjs b/src/strudel/xen/tune.mjs index 01303bf..ac03b1d 100644 --- a/src/strudel/xen/tune.mjs +++ b/src/strudel/xen/tune.mjs @@ -4,17 +4,21 @@ Copyright (C) 2022 Strudel contributors - see . */ -import Tune from './tunejs.js'; -import { register } from '@strudel/core'; +import Tune from './tunejs.js' +import { register } from '../core/index.mjs' export const tune = register('tune', (scale, pat) => { - const tune = new Tune(); + const tune = new Tune() if (!tune.isValidScale(scale)) { - throw new Error('not a valid tune.js scale name: "' + scale + '". See http://abbernie.github.io/tune/scales.html'); + throw new Error( + 'not a valid tune.js scale name: "' + + scale + + '". See http://abbernie.github.io/tune/scales.html' + ) } - tune.loadScale(scale); - tune.tonicize(1); + tune.loadScale(scale) + tune.tonicize(1) return pat.withHap((hap) => { - return hap.withValue(() => tune.note(hap.value)); - }); -}); + return hap.withValue(() => tune.note(hap.value)) + }) +}) diff --git a/src/strudel/xen/xen.mjs b/src/strudel/xen/xen.mjs index cc96f41..b3fd49a 100644 --- a/src/strudel/xen/xen.mjs +++ b/src/strudel/xen/xen.mjs @@ -4,61 +4,74 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { register, _mod, parseNumeral } from '@strudel/core'; +import { register, _mod, parseNumeral } from '../core/index.mjs' export function edo(name) { if (!/^[1-9]+[0-9]*edo$/.test(name)) { - throw new Error('not an edo scale: "' + name + '"'); + throw new Error('not an edo scale: "' + name + '"') } - const [_, divisions] = name.match(/^([1-9]+[0-9]*)edo$/); - return Array.from({ length: divisions }, (_, i) => Math.pow(2, i / divisions)); + const [_, divisions] = name.match(/^([1-9]+[0-9]*)edo$/) + return Array.from({ length: divisions }, (_, i) => Math.pow(2, i / divisions)) } const presets = { - '12ji': [1 / 1, 16 / 15, 9 / 8, 6 / 5, 5 / 4, 4 / 3, 45 / 32, 3 / 2, 8 / 5, 5 / 3, 16 / 9, 15 / 8], -}; - -function withBase(freq, scale) { - return scale.map((r) => r * freq); + '12ji': [ + 1 / 1, + 16 / 15, + 9 / 8, + 6 / 5, + 5 / 4, + 4 / 3, + 45 / 32, + 3 / 2, + 8 / 5, + 5 / 3, + 16 / 9, + 15 / 8, + ], } -const defaultBase = 220; +function withBase(freq, scale) { + return scale.map((r) => r * freq) +} + +const defaultBase = 220 function getXenScale(scale, indices) { if (typeof scale === 'string') { if (/^[1-9]+[0-9]*edo$/.test(scale)) { - scale = edo(scale); + scale = edo(scale) } else if (presets[scale]) { - scale = presets[scale]; + scale = presets[scale] } else { - throw new Error('unknown scale name: "' + scale + '"'); + throw new Error('unknown scale name: "' + scale + '"') } } - scale = withBase(defaultBase, scale); + scale = withBase(defaultBase, scale) if (!indices) { - return scale; + return scale } - return scale.filter((_, i) => indices.includes(i)); + return scale.filter((_, i) => indices.includes(i)) } function xenOffset(xenScale, offset, index = 0) { - const i = _mod(index + offset, xenScale.length); - const oct = Math.floor(offset / xenScale.length); - return xenScale[i] * Math.pow(2, oct); + const i = _mod(index + offset, xenScale.length) + const oct = Math.floor(offset / xenScale.length) + return xenScale[i] * Math.pow(2, oct) } // scaleNameOrRatios: string || number[], steps?: number export const xen = register('xen', function (scaleNameOrRatios, pat) { return pat.withHap((hap) => { - const scale = getXenScale(scaleNameOrRatios); - const frequency = xenOffset(scale, parseNumeral(hap.value)); - return hap.withValue(() => frequency); - }); -}); + const scale = getXenScale(scaleNameOrRatios) + const frequency = xenOffset(scale, parseNumeral(hap.value)) + return hap.withValue(() => frequency) + }) +}) export const tuning = register('tuning', function (ratios, pat) { return pat.withHap((hap) => { - const frequency = xenOffset(ratios, parseNumeral(hap.value)); - return hap.withValue(() => frequency); - }); -}); + const frequency = xenOffset(ratios, parseNumeral(hap.value)) + return hap.withValue(() => frequency) + }) +}) diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 0000000..86e502d --- /dev/null +++ b/src/styles.css @@ -0,0 +1,5 @@ +.strudel-mark { + /* outline: solid 2px var(--text-accent); */ + background-color: var(--text-selection); + border-radius: var(--radius-s); +} diff --git a/src/vue-shims.d.ts b/src/vue-shims.d.ts new file mode 100644 index 0000000..bf98be6 --- /dev/null +++ b/src/vue-shims.d.ts @@ -0,0 +1,7 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue' + // eslint-disable-next-line @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any> + export default component +} + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..03da888 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "inlineSourceMap": true, + "inlineSources": true, + "module": "ESNext", + "target": "ESNext", + "noImplicitAny": true, + "moduleResolution": "node", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "importHelpers": true, + "lib": ["DOM", "ESNext"], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.vue"] +} diff --git a/version-bump.mjs b/version-bump.mjs new file mode 100644 index 0000000..9058311 --- /dev/null +++ b/version-bump.mjs @@ -0,0 +1,14 @@ +import { readFileSync, writeFileSync } from 'fs' + +const targetVersion = process.env.npm_package_version + +// read minAppVersion from manifest.json and bump version to target version +let manifest = JSON.parse(readFileSync('manifest.json', 'utf8')) +const { minAppVersion } = manifest +manifest.version = targetVersion +writeFileSync('manifest.json', JSON.stringify(manifest, null, '\t')) + +// update versions.json with target version and minAppVersion from manifest.json +let versions = JSON.parse(readFileSync('versions.json', 'utf8')) +versions[targetVersion] = minAppVersion +writeFileSync('versions.json', JSON.stringify(versions, null, '\t')) diff --git a/versions.json b/versions.json new file mode 100644 index 0000000..6842d2f --- /dev/null +++ b/versions.json @@ -0,0 +1,3 @@ +{ + "0.0.1": "1.9.14" +} diff --git a/vite.config.mts b/vite.config.mts new file mode 100644 index 0000000..20679cc --- /dev/null +++ b/vite.config.mts @@ -0,0 +1,113 @@ +import { UserConfig, defineConfig } from 'vite' +import path from 'path' +import builtins from 'builtin-modules' +import vue from '@vitejs/plugin-vue' +import replace from '@rollup/plugin-replace' + +import { createLogger, build } from 'vite' + +const end = '?audioworklet' + +function bundleAudioWorkletPlugin() /* : PluginOption */ { + let viteConfig /* : UserConfig */ + + return { + name: 'vite-plugin-bundle-audioworklet', + /* apply: 'build', */ + enforce: 'post', + + config(config) { + viteConfig = config + }, + + async transform(_code, id) { + if (!id.endsWith(end)) { + return + } + const entry = id.replace(end, '') + const quietLogger = createLogger() + quietLogger.info = () => undefined + + const output = await build({ + configFile: false, + clearScreen: false, + customLogger: quietLogger, + build: { + lib: { + entry, + name: '_', + formats: ['iife'], + }, + write: false, + }, + }) + if (!(output instanceof Array)) { + throw new Error('Expected output to be Array') + } + const iife = output[0].output[0].code + const encoded = Buffer.from(iife, 'utf8').toString('base64') + return `export default "data:text/javascript;base64,${encoded}";` + }, + } +} + +export default defineConfig(async ({ mode }) => { + const { resolve } = path + const prod = mode === 'production' + + return { + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, + plugins: [vue(), bundleAudioWorkletPlugin()], + minify: prod, + build: { + lib: { + entry: resolve(__dirname, 'src/main.ts'), + name: 'main', + fileName: () => 'main.js', + formats: ['cjs'], + }, + sourcemap: prod ? false : 'inline', + cssCodeSplit: false, + emptyOutDir: true, + outDir: 'build', + rollupOptions: { + output: { + assetFileNames: (assetInfo) => { + if (assetInfo.name && assetInfo.name.endsWith('.css')) { + return 'main.css' + } + return '[name].[ext]' + }, + }, + input: { + main: resolve(__dirname, 'src/main.ts'), + }, + plugins: [ + replace({ + 'process.env.NODE_ENV': JSON.stringify(prod ? 'production' : 'development'), + }), + ], + external: [ + 'obsidian', + 'electron', + '@codemirror/autocomplete', + '@codemirror/collab', + '@codemirror/commands', + '@codemirror/language', + '@codemirror/lint', + '@codemirror/search', + '@codemirror/state', + '@codemirror/view', + '@lezer/common', + '@lezer/highlight', + '@lezer/lr', + ...builtins, + ], + }, + }, + } as UserConfig +}) diff --git a/watch.sh b/watch.sh new file mode 100644 index 0000000..bffbf4b --- /dev/null +++ b/watch.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +npx nodemon --exec "./build_and_copy_plugin.sh" --watch src --ext js,ts,vue,css