Compare commits

..

4 Commits

Author SHA1 Message Date
Felix Roos b9933c610a really fix lint 2024-06-06 12:11:57 +02:00
Felix Roos 9d81a4d071 fix lint 2024-06-06 12:09:55 +02:00
Felix Roos 86cc9b87c3 looper prototype 2024-06-06 10:05:29 +02:00
Felix Roos 0df6f3e56e simplify idb import 2024-06-06 00:44:41 +02:00
240 changed files with 20769 additions and 31195 deletions
+25
View File
@@ -0,0 +1,25 @@
krill-parser.js
krill.pegjs
.eslintrc.json
server.js
tidal-sniffer.js
*.jsx
tunejs.js
out/**
postcss.config.js
postcss.config.cjs
tailwind.config.js
tailwind.config.cjs
vite.config.js
/**/dist/**/*
!**/*.mjs
**/*.tsx
**/*.ts
**/*.json
**/dev-dist
**/dist
/src-tauri/target/**/*
reverbGen.mjs
hydra.mjs
jsdoc-synonyms.js
packages/hs2js/src/hs2js.mjs
+1 -2
View File
@@ -14,6 +14,5 @@
"rules": {
"no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }],
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}]
},
"files": ["**/*.mjs", "**/*.js"]
}
}
+5 -5
View File
@@ -22,10 +22,10 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
with:
version: 9.12.2
- uses: actions/setup-node@v4
version: 8.11.0
- uses: actions/setup-node@v3
with:
node-version: 20
cache: "pnpm"
@@ -39,11 +39,11 @@ jobs:
uses: actions/configure-pages@v2
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v1
with:
# Upload entire repository
path: "./website/dist"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v1
+3 -3
View File
@@ -11,10 +11,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
with:
version: 9.12.2
- uses: actions/setup-node@v4
version: 8.11.0
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
-2
View File
@@ -11,5 +11,3 @@ pnpm-lock.yaml
pnpm-workspace.yaml
**/dev-dist
website/.astro
!tidal-drum-machines.json
!tidal-drum-machines-alias.json
-12
View File
@@ -138,18 +138,6 @@ pnpm --filter "./packages/**" publish --access public
To manually publish a single package, increase the version in the `package.json`, then run `pnpm publish`.
Important: Always publish with `pnpm`, as `npm` does not support overriding main files in `publishConfig`, which is done in all the packages.
## useful commands
```sh
#regenerate the test snapshots (ex: when updating or creating new pattern functions)
pnpm snapshot
#start the OSC server
pnpm run osc
#build the standalone version
pnpm tauri build
```
## Have Fun
Remember to have fun, and that this project is driven by the passion of volunteers!
+5 -15
View File
@@ -1,6 +1,6 @@
# strudel
[![Strudel test status](https://github.com/tidalcycles/strudel/actions/workflows/test.yml/badge.svg)](https://github.com/tidalcycles/strudel/actions) [![DOI](https://zenodo.org/badge/450927247.svg)](https://doi.org/10.5281/zenodo.6659278)
[![Strudel test status](https://github.com/tidalcycles/strudel/actions/workflows/test.yml/badge.svg)](https://github.com/tidalcycles/strudel/actions)
An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using web technologies. This software is a bit more stable now, but please continue to tread carefully.
@@ -14,16 +14,10 @@ An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using w
After cloning the project, you can run the REPL locally:
1. Install [Node.js](https://nodejs.org/)
2. Install [pnpm](https://pnpm.io/installation)
3. Install dependencies by running the following command:
```bash
pnpm i
```
4. Run the development server:
```bash
pnpm dev
```
```bash
pnpm i
pnpm dev
```
## Using Strudel In Your Project
@@ -31,10 +25,6 @@ This project is organized into many [packages](./packages), which are also avail
Read more about how to use these in your own project [here](https://strudel.cc/technical-manual/project-start).
You will need to abide by the terms of the [GNU Affero Public Licence v3](LICENSE.md). As such, Strudel code can only be shared within free/open source projects under the same license -- see the license for details.
Licensing info for the default sound banks can be found over on the [dough-samples](https://github.com/felixroos/dough-samples/blob/main/README.md) repository.
## Contributing
There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md).
+6 -6
View File
@@ -1,22 +1,22 @@
import { queryCode, testCycles } from '../test/runtime.mjs';
import * as tunes from '../website/src/repl/tunes.mjs';
import { describe, bench } from 'vitest';
import { calculateSteps } from '../packages/core/index.mjs';
import { calculateTactus } from '../packages/core/index.mjs';
const tuneKeys = Object.keys(tunes);
describe('renders tunes', () => {
tuneKeys.forEach((key) => {
describe(key, () => {
calculateSteps(true);
bench(`+steps`, async () => {
calculateTactus(true);
bench(`+tactus`, async () => {
await queryCode(tunes[key], testCycles[key] || 1);
});
calculateSteps(false);
bench(`-steps`, async () => {
calculateTactus(false);
bench(`-tactus`, async () => {
await queryCode(tunes[key], testCycles[key] || 1);
});
calculateSteps(true);
calculateTactus(true);
});
});
});
-86
View File
@@ -1,86 +0,0 @@
import _import from 'eslint-plugin-import';
import { fixupPluginRules } from '@eslint/compat';
import globals from 'globals';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import js from '@eslint/js';
import { FlatCompat } from '@eslint/eslintrc';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});
export default [
{
ignores: [
'**/krill-parser.js',
'**/krill.pegjs',
'**/.eslintrc.json',
'**/server.js',
'**/tidal-sniffer.js',
'**/*.jsx',
'**/tunejs.js',
'out/**/*',
'**/postcss.config.js',
'**/postcss.config.cjs',
'**/tailwind.config.js',
'**/tailwind.config.cjs',
'**/vite.config.js',
'**/dist/**/*',
'!**/*.mjs',
'**/*.tsx',
'**/*.ts',
'**/*.json',
'**/dev-dist',
'**/dist',
'src-tauri/target/**/*',
'**/reverbGen.mjs',
'**/hydra.mjs',
'**/jsdoc-synonyms.js',
'packages/hs2js/src/hs2js.mjs',
'**/samples',
],
},
...compat.extends('eslint:recommended').map((config) => ({
...config,
files: ['**/*.mjs', '**/*.js'],
})),
{
files: ['**/*.mjs', '**/*.js'],
plugins: {
import: fixupPluginRules(_import),
},
languageOptions: {
globals: {
...globals.node,
...globals.browser,
},
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {
'no-unused-vars': [
'warn',
{
destructuredArrayIgnorePattern: '.',
ignoreRestSiblings: false,
},
],
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
},
],
},
},
];
+1 -1
View File
@@ -9,7 +9,7 @@
"preview": "vite preview"
},
"devDependencies": {
"vite": "^6.0.11"
"vite": "^5.0.10"
},
"dependencies": {
"@strudel/codemirror": "workspace:*",
+1 -1
View File
@@ -10,7 +10,7 @@
"preview": "vite preview"
},
"devDependencies": {
"vite": "^6.0.11"
"vite": "^5.0.10"
},
"dependencies": {
"@strudel/web": "workspace:*"
+1 -1
View File
@@ -10,7 +10,7 @@
"preview": "vite preview"
},
"devDependencies": {
"vite": "^6.0.11"
"vite": "^5.0.10"
},
"dependencies": {
"@strudel/core": "workspace:*",
+1 -1
View File
@@ -12,6 +12,6 @@
"superdough": "workspace:*"
},
"devDependencies": {
"vite": "^6.0.11"
"vite": "^5.0.10"
}
}
+1 -1
View File
@@ -31,6 +31,6 @@
"hs2js": "workspace:*"
},
"devDependencies": {
"vite": "^6.0.11"
"vite": "^5.0.8"
}
}
+13 -16
View File
@@ -21,7 +21,7 @@
"osc": "cd packages/osc && npm run server",
"jsdoc": "jsdoc packages/ -c jsdoc/jsdoc.config.json",
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc/jsdoc.config.json",
"lint": "eslint . --quiet",
"lint": "eslint . --ext mjs,js --quiet",
"codeformat": "prettier --write .",
"format-check": "prettier --check .",
"report-undocumented": "npm run jsdoc-json && node jsdoc/undocumented.mjs > undocumented.json",
@@ -55,22 +55,19 @@
"@strudel/xen": "workspace:*"
},
"devDependencies": {
"@eslint/compat": "^1.2.5",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.19.0",
"@tauri-apps/cli": "^2.2.7",
"@vitest/coverage-v8": "3.0.4",
"@vitest/ui": "^3.0.4",
"acorn": "^8.14.0",
"dependency-tree": "^11.0.1",
"eslint": "^9.19.0",
"eslint-plugin-import": "^2.31.0",
"@tauri-apps/cli": "^1.5.9",
"@vitest/ui": "^1.1.0",
"acorn": "^8.11.3",
"dependency-tree": "^10.0.9",
"eslint": "^8.56.0",
"eslint-plugin-import": "^2.29.1",
"events": "^3.3.0",
"globals": "^15.14.0",
"jsdoc": "^4.0.4",
"jsdoc": "^4.0.2",
"jsdoc-json": "^2.0.2",
"lerna": "^8.1.9",
"prettier": "^3.4.2",
"vitest": "^3.0.4"
"jsdoc-to-markdown": "^8.0.0",
"lerna": "^8.0.1",
"prettier": "^3.1.1",
"rollup-plugin-visualizer": "^5.12.0",
"vitest": "^1.1.0"
}
}
+12 -25
View File
@@ -1,5 +1,4 @@
import { closeBrackets } from '@codemirror/autocomplete';
export { toggleComment, toggleBlockComment, toggleLineComment, toggleBlockCommentByLine } from '@codemirror/commands';
// import { search, highlightSelectionMatches } from '@codemirror/search';
import { history } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript';
@@ -13,7 +12,7 @@ import {
lineNumbers,
drawSelection,
} from '@codemirror/view';
import { repl, registerControl } from '@strudel/core';
import { Pattern, repl } from '@strudel/core';
import { Drawer, cleanupDraw } from '@strudel/draw';
import { isAutoCompletionEnabled } from './autocomplete.mjs';
import { isTooltipEnabled } from './tooltip.mjs';
@@ -67,7 +66,6 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
const initialSettings = Object.keys(compartments).map((key) =>
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
);
initTheme(settings.theme);
let state = EditorState.create({
doc: initialCode,
@@ -100,8 +98,10 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
},
{
key: 'Alt-.',
preventDefault: true,
run: () => onStop?.(),
run: (_, e) => {
e.preventDefault();
onStop?.();
},
},
/* {
key: 'Ctrl-Shift-.',
@@ -134,7 +134,6 @@ export class StrudelMirror {
autodraw,
prebake,
bgFill = true,
solo = true,
...replOptions
} = options;
this.code = initialCode;
@@ -145,7 +144,6 @@ export class StrudelMirror {
this.drawContext = drawContext;
this.onDraw = onDraw || this.draw;
this.id = id || s4();
this.solo = solo;
this.drawer = new Drawer((haps, time, _, painters) => {
const currentFrame = haps.filter((hap) => hap.isActive(time));
@@ -162,14 +160,12 @@ export class StrudelMirror {
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,
}),
);
}
// stop other repls when this one is started
document.dispatchEvent(
new CustomEvent('start-repl', {
detail: this.id,
}),
);
} else {
this.drawer.stop();
updateMiniLocations(this.editor, []);
@@ -224,7 +220,7 @@ export class StrudelMirror {
// stop this repl when another repl is started
this.onStartRepl = (e) => {
if (this.solo && e.detail !== this.id) {
if (e.detail !== this.id) {
this.stop();
}
};
@@ -359,12 +355,3 @@ function s4() {
.toString(16)
.substring(1);
}
/**
* Overrides the css of highlighted events. Make sure to use single quotes!
* @name markcss
* @example
* note("c a f e")
* .markcss('text-decoration:underline')
*/
export const markcss = registerControl('markcss');
+1 -3
View File
@@ -11,9 +11,7 @@ export const flashField = StateField.define({
for (let e of tr.effects) {
if (e.is(setFlash)) {
if (e.value && tr.newDoc.length > 0) {
const mark = Decoration.mark({
attributes: { style: `background-color: rgba(255,255,255, .4); filter: invert(10%)` },
});
const mark = Decoration.mark({ attributes: { style: `background-color: #FFCA2880` } });
flash = Decoration.set([mark.range(0, tr.newDoc.length)]);
} else {
flash = Decoration.set([]);
+1 -2
View File
@@ -93,7 +93,6 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
if (haps.has(id)) {
const hap = haps.get(id);
const color = hap.value?.color ?? 'var(--foreground)';
const style = hap.value?.markcss || `outline: solid 2px ${color}`;
// Get explicit channels for color values
/*
const swatch = document.createElement('div');
@@ -115,7 +114,7 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
to,
Decoration.mark({
// attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` },
attributes: { style },
attributes: { style: `outline: solid 2px ${color}` },
}),
);
}
+16 -14
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/codemirror",
"version": "1.2.0",
"version": "1.1.0",
"description": "Codemirror Extensions for Strudel",
"main": "index.mjs",
"publishConfig": {
@@ -32,24 +32,26 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@codemirror/autocomplete": "^6.18.4",
"@codemirror/commands": "^6.8.0",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/language": "^6.10.8",
"@codemirror/search": "^6.5.8",
"@codemirror/state": "^6.5.1",
"@codemirror/view": "^6.36.2",
"@lezer/highlight": "^1.2.1",
"@nanostores/persistent": "^0.10.2",
"@replit/codemirror-emacs": "^6.1.0",
"@replit/codemirror-vim": "^6.2.1",
"@codemirror/autocomplete": "^6.11.1",
"@codemirror/commands": "^6.3.3",
"@codemirror/lang-javascript": "^6.2.1",
"@codemirror/language": "^6.10.0",
"@codemirror/search": "^6.5.5",
"@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.23.0",
"@lezer/highlight": "^1.2.0",
"@nanostores/persistent": "^0.9.1",
"@replit/codemirror-emacs": "^6.0.1",
"@replit/codemirror-vim": "^6.1.0",
"@replit/codemirror-vscode-keymap": "^6.0.2",
"@strudel/core": "workspace:*",
"@strudel/draw": "workspace:*",
"@strudel/transpiler": "workspace:*",
"nanostores": "^0.11.3"
"@uiw/codemirror-themes": "^4.21.21",
"@uiw/codemirror-themes-all": "^4.21.21",
"nanostores": "^0.9.5"
},
"devDependencies": {
"vite": "^6.0.11"
"vite": "^5.0.10"
}
}
+411 -88
View File
@@ -1,117 +1,440 @@
import strudelTheme, { settings as strudelThemeSettings } from './themes/strudel-theme.mjs';
import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen.mjs';
import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen.mjs';
import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen.mjs';
import teletext, { settings as teletextSettings } from './themes/teletext.mjs';
import algoboy, { settings as algoboySettings } from './themes/algoboy.mjs';
import CutiePi, { settings as CutiePiSettings } from './themes/CutiePi.mjs';
import sonicPink, { settings as sonicPinkSettings } from './themes/sonic-pink.mjs';
import redText, { settings as redTextSettings } from './themes/red-text.mjs';
import greenText, { settings as greenTextSettings } from './themes/green-text.mjs';
import androidstudio, { settings as androidstudioSettings } from './themes/androidstudio.mjs';
import atomone, { settings as atomOneSettings } from './themes/atomone.mjs';
import aura, { settings as auraSettings } from './themes/aura.mjs';
import darcula, { settings as darculaSettings } from './themes/darcula.mjs';
import dracula, { settings as draculaSettings } from './themes/dracula.mjs';
import duotoneDark, { settings as duotoneDarkSettings } from './themes/duotoneDark.mjs';
import eclipse, { settings as eclipseSettings } from './themes/eclipse.mjs';
import githubDark, { settings as githubDarkSettings } from './themes/githubDark.mjs';
import githubLight, { settings as githubLightSettings } from './themes/githubLight.mjs';
import gruvboxDark, { settings as gruvboxDarkSettings } from './themes/gruvboxDark.mjs';
import gruvboxLight, { settings as gruvboxLightSettings } from './themes/gruvboxLight.mjs';
import materialDark, { settings as materialDarkSettings } from './themes/materialDark.mjs';
import materialLight, { settings as materialLightSettings } from './themes/materialLight.mjs';
import nord, { settings as nordSettings } from './themes/nord.mjs';
import monokai, { settings as monokaiSettings } from './themes/monokai.mjs';
import solarizedDark, { settings as solarizedDarkSettings } from './themes/solarizedDark.mjs';
import solarizedLight, { settings as solarizedLightSettings } from './themes/solarizedLight.mjs';
import sublime, { settings as sublimeSettings } from './themes/sublime.mjs';
import tokyoNight, { settings as tokyoNightSettings } from './themes/tokyoNight.mjs';
import tokyoNightStorm, { settings as tokyoNightStormSettings } from './themes/tokioNightStorm.mjs';
import tokyoNightDay, { settings as tokyoNightDaySettings } from './themes/tokyoNightDay.mjs';
import vscodeDark, { settings as vscodeDarkSettings } from './themes/vscodeDark.mjs';
import vscodeLight, { settings as vscodeLightSettings } from './themes/vscodeLight.mjs';
import xcodeLight, { settings as xcodeLightSettings } from './themes/xcodeLight.mjs';
import bbedit, { settings as bbeditSettings } from './themes/bbedit.mjs';
import noctisLilac, { settings as noctisLilacSettings } from './themes/noctisLilac.mjs';
import { setTheme } from '@strudel/draw';
export const themes = {
strudelTheme,
algoboy,
import {
abcdef,
androidstudio,
atomone,
aura,
bbedit,
blackscreen,
bluescreen,
CutiePi,
bespin,
darcula,
dracula,
duotoneDark,
eclipse,
githubDark,
githubLight,
greenText,
gruvboxDark,
gruvboxLight,
sonicPink,
materialDark,
materialLight,
monokai,
noctisLilac,
nord,
redText,
okaidia,
solarizedDark,
solarizedLight,
sublime,
teletext,
tokyoNight,
tokyoNightDay,
tokyoNightStorm,
vscodeDark,
vscodeLight,
xcodeDark,
bbedit,
duotoneLight,
githubLight,
gruvboxLight,
materialLight,
noctisLilac,
solarizedLight,
tokyoNightDay,
xcodeLight,
} from '@uiw/codemirror-themes-all';
import strudelTheme from './themes/strudel-theme';
import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen';
import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen';
import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen';
import teletext, { settings as teletextSettings } from './themes/teletext';
import algoboy, { settings as algoboySettings } from './themes/algoboy';
import terminal, { settings as terminalSettings } from './themes/terminal';
import { setTheme } from '@strudel/draw';
export const themes = {
strudelTheme,
bluescreen,
blackscreen,
whitescreen,
teletext,
algoboy,
terminal,
abcdef,
androidstudio,
atomone,
aura,
bespin,
darcula,
dracula,
duotoneDark,
eclipse,
githubDark,
gruvboxDark,
materialDark,
nord,
okaidia,
solarizedDark,
sublime,
tokyoNight,
tokyoNightStorm,
vscodeDark,
xcodeDark,
bbedit,
duotoneLight,
githubLight,
gruvboxLight,
materialLight,
noctisLilac,
solarizedLight,
tokyoNightDay,
xcodeLight,
};
// lineBackground is background with 50% opacity, to make sure the selection below is visible
export const settings = {
strudelTheme: strudelThemeSettings,
strudelTheme: {
background: '#222',
lineBackground: '#22222299',
foreground: '#fff',
// foreground: '#75baff',
caret: '#ffcc00',
selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626',
// lineHighlight: '#8a91991a', // original
lineHighlight: '#00000050',
gutterBackground: 'transparent',
// gutterForeground: '#8a919966',
gutterForeground: '#8a919966',
},
bluescreen: bluescreenSettings,
blackscreen: blackscreenSettings,
whitescreen: whitescreenSettings,
teletext: teletextSettings,
algoboy: algoboySettings,
androidstudio: androidstudioSettings,
atomone: atomOneSettings,
aura: auraSettings,
bbedit: bbeditSettings,
darcula: darculaSettings,
dracula: draculaSettings,
duotoneDark: duotoneDarkSettings,
eclipse: eclipseSettings,
CutiePi: CutiePiSettings,
sonicPink: sonicPinkSettings,
githubLight: githubLightSettings,
githubDark: githubDarkSettings,
greenText: greenTextSettings,
gruvboxDark: gruvboxDarkSettings,
gruvboxLight: gruvboxLightSettings,
materialDark: materialDarkSettings,
materialLight: materialLightSettings,
noctisLilac: noctisLilacSettings,
nord: nordSettings,
monokai: monokaiSettings,
redText: redTextSettings,
solarizedLight: solarizedLightSettings,
solarizedDark: solarizedDarkSettings,
sublime: sublimeSettings,
tokyoNight: tokyoNightSettings,
tokyoNightStorm: tokyoNightStormSettings,
vscodeDark: vscodeDarkSettings,
vscodeLight: vscodeLightSettings,
xcodeLight: xcodeLightSettings,
tokyoNightDay: tokyoNightDaySettings,
terminal: terminalSettings,
abcdef: {
background: '#0f0f0f',
lineBackground: '#0f0f0f99',
foreground: '#defdef',
caret: '#00FF00',
selection: '#515151',
selectionMatch: '#515151',
gutterBackground: '#555',
gutterForeground: '#FFFFFF',
lineHighlight: '#314151',
},
androidstudio: {
background: '#282b2e',
lineBackground: '#282b2e99',
foreground: '#a9b7c6',
caret: '#00FF00',
selection: '#343739',
selectionMatch: '#343739',
lineHighlight: '#343739',
},
atomone: {
background: '#272C35',
lineBackground: '#272C3599',
foreground: '#9d9b97',
caret: '#797977',
selection: '#ffffff30',
selectionMatch: '#2B323D',
gutterBackground: '#272C35',
gutterForeground: '#465063',
gutterBorder: 'transparent',
lineHighlight: '#2B323D',
},
aura: {
background: '#21202e',
lineBackground: '#21202e99',
foreground: '#edecee',
caret: '#a277ff',
selection: '#3d375e7f',
selectionMatch: '#3d375e7f',
gutterBackground: '#21202e',
gutterForeground: '#edecee',
gutterBorder: 'transparent',
lineHighlight: '#a394f033',
},
bbedit: {
light: true,
background: '#FFFFFF',
lineBackground: '#FFFFFF99',
foreground: '#000000',
caret: '#FBAC52',
selection: '#FFD420',
selectionMatch: '#FFD420',
gutterBackground: '#f5f5f5',
gutterForeground: '#4D4D4C',
gutterBorder: 'transparent',
lineHighlight: '#00000012',
},
bespin: {
background: '#28211c',
lineBackground: '#28211c99',
foreground: '#9d9b97',
caret: '#797977',
selection: '#36312e',
selectionMatch: '#4f382b',
gutterBackground: '#28211c',
gutterForeground: '#666666',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
darcula: {
background: '#2B2B2B',
lineBackground: '#2B2B2B99',
foreground: '#f8f8f2',
caret: '#FFFFFF',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
gutterBackground: 'rgba(255, 255, 255, 0.1)',
gutterForeground: '#999',
gutterBorder: 'transparent',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
dracula: {
background: '#282a36',
lineBackground: '#282a3699',
foreground: '#f8f8f2',
caret: '#f8f8f0',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
gutterBackground: '#282a36',
gutterForeground: '#6D8A88',
gutterBorder: 'transparent',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
duotoneLight: {
light: true,
background: '#faf8f5',
lineBackground: '#faf8f599',
foreground: '#b29762',
caret: '#93abdc',
selection: '#e3dcce',
selectionMatch: '#e3dcce',
gutterBackground: '#faf8f5',
gutterForeground: '#cdc4b1',
gutterBorder: 'transparent',
lineHighlight: '#EFEFEF',
},
duotoneDark: {
background: '#2a2734',
lineBackground: '#2a273499',
foreground: '#6c6783',
caret: '#ffad5c',
selection: 'rgba(255, 255, 255, 0.1)',
gutterBackground: '#2a2734',
gutterForeground: '#545167',
lineHighlight: '#36334280',
},
eclipse: {
light: true,
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#000',
caret: '#FFFFFF',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
gutterBackground: '#f7f7f7',
gutterForeground: '#999',
lineHighlight: '#e8f2ff',
gutterBorder: 'transparent',
},
githubLight: {
light: true,
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#24292e',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#6e7781',
},
githubDark: {
background: '#0d1117',
lineBackground: '#0d111799',
foreground: '#c9d1d9',
caret: '#c9d1d9',
selection: '#003d73',
selectionMatch: '#003d73',
lineHighlight: '#36334280',
},
gruvboxDark: {
background: '#282828',
lineBackground: '#28282899',
foreground: '#ebdbb2',
caret: '#ebdbb2',
selection: '#bdae93',
selectionMatch: '#bdae93',
lineHighlight: '#3c3836',
gutterBackground: '#282828',
gutterForeground: '#7c6f64',
},
gruvboxLight: {
light: true,
background: '#fbf1c7',
lineBackground: '#fbf1c799',
foreground: '#3c3836',
caret: '#af3a03',
selection: '#ebdbb2',
selectionMatch: '#bdae93',
lineHighlight: '#ebdbb2',
gutterBackground: '#ebdbb2',
gutterForeground: '#665c54',
gutterBorder: 'transparent',
},
materialDark: {
background: '#2e3235',
lineBackground: '#2e323599',
foreground: '#bdbdbd',
caret: '#a0a4ae',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
gutterBackground: '#2e3235',
gutterForeground: '#999',
gutterActiveForeground: '#4f5b66',
lineHighlight: '#545b61',
},
materialLight: {
light: true,
background: '#FAFAFA',
lineBackground: '#FAFAFA99',
foreground: '#90A4AE',
caret: '#272727',
selection: '#80CBC440',
selectionMatch: '#FAFAFA',
gutterBackground: '#FAFAFA',
gutterForeground: '#90A4AE',
gutterBorder: 'transparent',
lineHighlight: '#CCD7DA50',
},
noctisLilac: {
light: true,
background: '#f2f1f8',
lineBackground: '#f2f1f899',
foreground: '#0c006b',
caret: '#5c49e9',
selection: '#d5d1f2',
selectionMatch: '#d5d1f2',
gutterBackground: '#f2f1f8',
gutterForeground: '#0c006b70',
lineHighlight: '#e1def3',
},
nord: {
background: '#2e3440',
lineBackground: '#2e344099',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#3b4252',
selectionMatch: '#e5e9f0',
gutterBackground: '#2e3440',
gutterForeground: '#4c566a',
gutterActiveForeground: '#d8dee9',
lineHighlight: '#4c566a',
},
okaidia: {
background: '#272822',
lineBackground: '#27282299',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#49483E',
selectionMatch: '#49483E',
gutterBackground: '#272822',
gutterForeground: '#FFFFFF70',
lineHighlight: '#00000059',
},
solarizedLight: {
light: true,
background: '#fdf6e3',
lineBackground: '#fdf6e399',
foreground: '#657b83',
caret: '#586e75',
selection: '#dfd9c8',
selectionMatch: '#dfd9c8',
gutterBackground: '#00000010',
gutterForeground: '#657b83',
lineHighlight: '#dfd9c8',
},
solarizedDark: {
background: '#002b36',
lineBackground: '#002b3699',
foreground: '#93a1a1',
caret: '#839496',
selection: '#173541',
selectionMatch: '#aafe661a',
gutterBackground: '#00252f',
gutterForeground: '#839496',
lineHighlight: '#173541',
},
sublime: {
background: '#303841',
lineBackground: '#30384199',
foreground: '#FFFFFF',
caret: '#FBAC52',
selection: '#4C5964',
selectionMatch: '#3A546E',
gutterBackground: '#303841',
gutterForeground: '#FFFFFF70',
lineHighlight: '#00000059',
},
tokyoNightDay: {
light: true,
background: '#e1e2e7',
lineBackground: '#e1e2e799',
foreground: '#3760bf',
caret: '#3760bf',
selection: '#99a7df',
selectionMatch: '#99a7df',
gutterBackground: '#e1e2e7',
gutterForeground: '#3760bf',
gutterBorder: 'transparent',
lineHighlight: '#5f5faf11',
},
tokyoNightStorm: {
background: '#24283b',
lineBackground: '#24283b99',
foreground: '#7982a9',
caret: '#c0caf5',
selection: '#6f7bb630',
selectionMatch: '#1f2335',
gutterBackground: '#24283b',
gutterForeground: '#7982a9',
gutterBorder: 'transparent',
lineHighlight: '#292e42',
},
tokyoNight: {
background: '#1a1b26',
lineBackground: '#1a1b2699',
foreground: '#787c99',
caret: '#c0caf5',
selection: '#515c7e40',
selectionMatch: '#16161e',
gutterBackground: '#1a1b26',
gutterForeground: '#787c99',
gutterBorder: 'transparent',
lineHighlight: '#1e202e',
},
vscodeDark: {
background: '#1e1e1e',
lineBackground: '#1e1e1e99',
foreground: '#9cdcfe',
caret: '#c6c6c6',
selection: '#6199ff2f',
selectionMatch: '#72a1ff59',
lineHighlight: '#ffffff0f',
gutterBackground: '#1e1e1e',
gutterForeground: '#838383',
gutterActiveForeground: '#fff',
},
xcodeLight: {
light: true,
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#3D3D3D',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#AFAFAF',
lineHighlight: '#EDF4FF',
},
xcodeDark: {
background: '#292A30',
lineBackground: '#292A3099',
foreground: '#CECFD0',
caret: '#fff',
selection: '#727377',
selectionMatch: '#727377',
lineHighlight: '#2F3239',
},
};
function getColors(str) {
-45
View File
@@ -1,45 +0,0 @@
/**
* @name 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 },
],
});
+31 -50
View File
@@ -1,61 +1,42 @@
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'];
import { createTheme } from '@uiw/codemirror-themes';
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',
background: '#9bbc0f',
foreground: '#0f380f', // whats that?
caret: '#0f380f',
selection: '#306230',
selectionMatch: '#ffffff26',
lineHighlight: '#8bac0f',
lineBackground: '#9bbc0f50',
//lineBackground: 'transparent',
gutterBackground: 'transparent',
gutterForeground: palette[0],
light: false,
gutterForeground: '#0f380f',
light: true,
// customStyle: '.cm-line { line-height: 1 }',
};
export default createTheme({
theme: 'dark',
theme: 'light',
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] },
{ tag: t.labelName, color: '#0f380f' },
{ tag: t.keyword, color: '#0f380f' },
{ tag: t.operator, color: '#0f380f' },
{ tag: t.special(t.variableName), color: '#0f380f' },
{ tag: t.typeName, color: '#0f380f' },
{ tag: t.atom, color: '#0f380f' },
{ tag: t.number, color: '#0f380f' },
{ tag: t.definition(t.variableName), color: '#0f380f' },
{ tag: t.string, color: '#0f380f' },
{ tag: t.special(t.string), color: '#0f380f' },
{ tag: t.comment, color: '#0f380f' },
{ tag: t.variableName, color: '#0f380f' },
{ tag: t.tagName, color: '#0f380f' },
{ tag: t.bracket, color: '#0f380f' },
{ tag: t.meta, color: '#0f380f' },
{ tag: t.attributeName, color: '#0f380f' },
{ tag: t.propertyName, color: '#0f380f' },
{ tag: t.className, color: '#0f380f' },
{ tag: t.invalid, color: '#0f380f' },
{ tag: [t.unit, t.punctuation], color: '#0f380f' },
],
});
-43
View File
@@ -1,43 +0,0 @@
/**
* @name androidstudio
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#282b2e',
lineBackground: '#282b2e99',
foreground: '#a9b7c6',
caret: '#00FF00',
selection: '#343739',
selectionMatch: '#343739',
lineHighlight: '#343739',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#282b2e',
foreground: '#a9b7c6',
caret: '#00FF00',
selection: '#4e5254',
selectionMatch: '#4e5254',
gutterForeground: '#cccccc50',
lineHighlight: '#7f85891f',
},
styles: [
{ tag: t.labelName, color: 'inherit' },
{ tag: [t.keyword, t.deleted, t.className], color: '#a9b7c6' },
{ tag: [t.number, t.literal], color: '#6897bb' },
//{ tag: [t.link, t.variableName], color: '#629755' },
{ tag: [t.link, t.variableName], color: '#a9b7c6' },
{ tag: [t.comment, t.quote], color: 'grey' },
{ tag: [t.meta, t.documentMeta], color: '#bbb529' },
//{ tag: [t.string, t.propertyName, t.attributeValue], color: '#6a8759' },
{ tag: [t.propertyName, t.attributeValue], color: '#a9b7c6' },
{ tag: [t.string], color: '#6a8759' },
{ tag: [t.heading, t.typeName], color: '#ffc66d' },
{ tag: [t.attributeName], color: '#a9b7c6' },
{ tag: [t.emphasis], fontStyle: 'italic' },
],
});
-51
View File
@@ -1,51 +0,0 @@
/**
* @name 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' },
],
});
-51
View File
@@ -1,51 +0,0 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#21202e',
lineBackground: '#21202e99',
foreground: '#edecee',
caret: '#a277ff',
selection: '#3d375e7f',
selectionMatch: '#3d375e7f',
gutterBackground: '#21202e',
gutterForeground: '#edecee',
gutterBorder: 'transparent',
lineHighlight: '#a394f033',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#21202e',
foreground: '#edecee',
caret: '#a277ff',
selection: '#5a51898f',
selectionMatch: '#5a51898f',
gutterBackground: '#21202e',
gutterForeground: '#edecee',
gutterBorder: 'transparent',
lineHighlight: '#a394f033',
},
styles: [
{ tag: t.keyword, color: '#a277ff' },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#edecee' },
{ tag: [t.propertyName], color: '#ffca85' },
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#61ffca' },
{ tag: [t.function(t.variableName), t.labelName], color: '#ffca85' },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#61ffca' },
{ tag: [t.definition(t.name), t.separator], color: '#edecee' },
{ tag: [t.className], color: '#82e2ff' },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#61ffca' },
{ tag: [t.typeName], color: '#82e2ff' },
{ tag: [t.operator, t.operatorKeyword], color: '#a277ff' },
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#61ffca' },
{ tag: [t.meta, t.comment], color: '#6d6d6d' },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.heading, fontWeight: 'bold', color: '#a277ff' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#edecee' },
{ tag: t.invalid, color: '#ff6767' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
],
});
-46
View File
@@ -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' },
],
});
+21 -21
View File
@@ -1,5 +1,5 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
import { createTheme } from '@uiw/codemirror-themes';
export const settings = {
background: 'black',
foreground: 'white', // whats that?
@@ -15,25 +15,25 @@ 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' },
{ tag: t.labelName, color: 'white' },
{ tag: t.keyword, color: 'white' },
{ tag: t.operator, color: 'white' },
{ tag: t.special(t.variableName), color: 'white' },
{ tag: t.typeName, color: 'white' },
{ tag: t.atom, color: 'white' },
{ tag: t.number, color: 'white' },
{ tag: t.definition(t.variableName), color: 'white' },
{ tag: t.string, color: 'white' },
{ tag: t.special(t.string), color: 'white' },
{ tag: t.comment, color: 'white' },
{ tag: t.variableName, color: 'white' },
{ tag: t.tagName, color: 'white' },
{ tag: t.bracket, color: 'white' },
{ tag: t.meta, color: 'white' },
{ tag: t.attributeName, color: 'white' },
{ tag: t.propertyName, color: 'white' },
{ tag: t.className, color: 'white' },
{ tag: t.invalid, color: 'white' },
{ tag: [t.unit, t.punctuation], color: 'white' },
],
});
+21 -21
View File
@@ -1,5 +1,5 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
import { createTheme } from '@uiw/codemirror-themes';
export const settings = {
background: '#051DB5',
lineBackground: '#051DB550',
@@ -18,25 +18,25 @@ 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' },
{ tag: t.labelName, color: 'white' },
{ tag: t.keyword, color: 'white' },
{ tag: t.operator, color: 'white' },
{ tag: t.special(t.variableName), color: 'white' },
{ tag: t.typeName, color: 'white' },
{ tag: t.atom, color: 'white' },
{ tag: t.number, color: 'white' },
{ tag: t.definition(t.variableName), color: 'white' },
{ tag: t.string, color: 'white' },
{ tag: t.special(t.string), color: 'white' },
{ tag: t.comment, color: 'white' },
{ tag: t.variableName, color: 'white' },
{ tag: t.tagName, color: 'white' },
{ tag: t.bracket, color: 'white' },
{ tag: t.meta, color: 'white' },
{ tag: t.attributeName, color: 'white' },
{ tag: t.propertyName, color: 'white' },
{ tag: t.className, color: 'white' },
{ tag: t.invalid, color: 'white' },
{ tag: [t.unit, t.punctuation], color: 'white' },
],
});
-48
View File
@@ -1,48 +0,0 @@
/**
* @name darcula
* @author 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' },
],
});
-51
View File
@@ -1,51 +0,0 @@
/**
* @name dracula
* @author dracula
* Michael Kaminsky (http://github.com/mkaminsky11)
* Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)
*/
// this is different from https://thememirror.net/dracula
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#282a36',
lineBackground: '#282a3699',
foreground: '#f8f8f2',
caret: '#f8f8f0',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
gutterBackground: '#282a36',
gutterForeground: '#6272a4',
gutterBorder: 'transparent',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
};
const purple = '#BD93F9';
export default createTheme({
theme: 'dark',
settings: {
background: '#282a36',
foreground: '#f8f8f2',
caret: '#f8f8f0',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
gutterBackground: '#282a36',
gutterForeground: '#6272a4',
gutterBorder: 'transparent',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
styles: [
{ tag: t.comment, color: '#6272a4' },
{ tag: t.string, color: '#f1fa8c' },
{ tag: [t.atom, t.number], color: purple },
{ tag: [t.meta, t.labelName, t.variableName], color: '#f8f8f2' },
{
tag: [t.keyword, t.tagName, t.arithmeticOperator],
color: '#ff79c6',
},
{ tag: [t.function(t.variableName), t.propertyName], color: '#50fa7b' },
{ tag: t.atom, color: '#bd93f9' },
],
});
-42
View File
@@ -1,42 +0,0 @@
/**
* @name duotone
* @author Bram de Haan
* by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#2a2734',
lineBackground: '#2a273499',
foreground: '#eeebff',
caret: '#ffad5c',
selection: 'rgba(255, 255, 255, 0.1)',
gutterBackground: '#2a2734',
gutterForeground: '#545167',
lineHighlight: '#36334280',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#2a2734',
foreground: '#6c6783',
caret: '#ffad5c',
selection: '#9a86fd',
selectionMatch: '#9a86fd',
gutterBackground: '#2a2734',
gutterForeground: '#545167',
lineHighlight: '#36334280',
},
styles: [
{ tag: [t.comment, t.bracket, t.operator], color: '#6c6783' },
{ tag: [t.atom, t.number, t.keyword, t.link, t.attributeName, t.quote], color: '#ffcc99' },
{ tag: [t.emphasis, t.heading, t.tagName, t.propertyName, t.className, t.variableName], color: '#eeebff' },
{ tag: [t.typeName, t.url], color: '#eeebff' },
{ tag: t.string, color: '#ffb870' },
/* { tag: [t.propertyName], color: '#9a86fd' }, */
{ tag: [t.propertyName], color: '#eeebff' },
{ tag: t.labelName, color: '#eeebff' },
],
});
-45
View File
@@ -1,45 +0,0 @@
/**
* @name duotone
* @author Bram de Haan
* by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
light: true,
background: '#faf8f5',
lineBackground: '#faf8f599',
foreground: '#b29762',
caret: '#93abdc',
selection: '#e3dcce',
selectionMatch: '#e3dcce',
gutterBackground: '#faf8f5',
gutterForeground: '#cdc4b1',
gutterBorder: 'transparent',
lineHighlight: '#EFEFEF',
};
export default createTheme({
theme: 'light',
settings: {
background: '#faf8f5',
foreground: '#b29762',
caret: '#93abdc',
selection: '#e3dcce',
selectionMatch: '#e3dcce',
gutterBackground: '#faf8f5',
gutterForeground: '#cdc4b1',
gutterBorder: 'transparent',
lineHighlight: '#ddceb154',
},
styles: [
{ tag: [t.comment, t.bracket], color: '#b6ad9a' },
{ tag: [t.atom, t.number, t.keyword, t.link, t.attributeName, t.quote], color: '#063289' },
{ tag: [t.emphasis, t.heading, t.tagName, t.propertyName, t.variableName], color: '#2d2006' },
{ tag: [t.typeName, t.url, t.string], color: '#896724' },
{ tag: [t.operator, t.string], color: '#1659df' },
{ tag: [t.propertyName], color: '#b29762' },
{ tag: [t.unit, t.punctuation], color: '#063289' },
],
});
-46
View File
@@ -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' },
],
});
-45
View File
@@ -1,45 +0,0 @@
/**
* @name github
*/
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' },
],
});
-45
View File
@@ -1,45 +0,0 @@
/**
* @name github
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
light: true,
background: '#fff',
lineBackground: '#ffffff99',
foreground: '#24292e',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#6e7781',
};
export default createTheme({
theme: 'light',
settings: {
background: '#fff',
foreground: '#24292e',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#6e7781',
},
styles: [
{ tag: [t.standard(t.tagName), t.tagName], color: '#116329' },
{ tag: [t.comment, t.bracket], color: '#6a737d' },
{ tag: [t.className, t.propertyName], color: '#6f42c1' },
{ tag: [t.variableName, t.attributeName, t.number, t.operator], color: '#005cc5' },
{ tag: [t.keyword, t.typeName, t.typeOperator, t.typeName], color: '#d73a49' },
{ tag: [t.string, t.meta, t.regexp], color: '#032f62' },
{ tag: [t.name, t.quote], color: '#22863a' },
{ tag: [t.heading, t.strong], color: '#24292e', fontWeight: 'bold' },
{ tag: [t.emphasis], color: '#24292e', fontStyle: 'italic' },
{ tag: [t.deleted], color: '#b31d28', backgroundColor: 'ffeef0' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#e36209' },
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#032f62' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: t.invalid, color: '#cb2431' },
],
});
-39
View File
@@ -1,39 +0,0 @@
/**
* @name 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] },
],
});
-83
View File
@@ -1,83 +0,0 @@
/**
* @name gruvbox-dark
* @author morhetz
* Name: Gruvbox
* 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` },
],
});
-131
View File
@@ -1,131 +0,0 @@
/**
* @name gruvbox-light
* @author morhetz
* Name: Gruvbox
* From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-light.css
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
light: true,
background: '#fbf1c7',
lineBackground: '#fbf1c799',
foreground: '#3c3836',
caret: '#af3a03',
selection: '#ebdbb2',
selectionMatch: '#bdae93',
lineHighlight: '#ebdbb2',
gutterBackground: '#ebdbb2',
gutterForeground: '#665c54',
gutterBorder: 'transparent',
};
export default createTheme({
theme: 'light',
settings: {
background: '#fbf1c7',
foreground: '#3c3836',
caret: '#af3a03',
selection: '#bdae9391',
selectionMatch: '#bdae9391',
lineHighlight: '#a37f2238',
gutterBackground: '#ebdbb2',
gutterForeground: '#665c54',
gutterBorder: 'transparent',
},
styles: [
{ tag: t.keyword, color: '#9d0006' },
{
tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
color: '#427b58',
},
{ tag: [t.variableName], color: '#076678' },
{ tag: [t.function(t.variableName)], color: '#79740e', fontStyle: 'bold' },
{ tag: [t.labelName], color: '#3c3836' },
{
tag: [t.color, t.constant(t.name), t.standard(t.name)],
color: '#8f3f71',
},
{ tag: [t.definition(t.name), t.separator], color: '#3c3836' },
{ tag: [t.brace], color: '#3c3836' },
{
tag: [t.annotation],
color: '#9d0006',
},
{
tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
color: '#8f3f71',
},
{
tag: [t.typeName, t.className],
color: '#b57614',
},
{
tag: [t.operator, t.operatorKeyword],
color: '#9d0006',
},
{
tag: [t.tagName],
color: '#427b58',
fontStyle: 'bold',
},
{
tag: [t.squareBracket],
color: '#af3a03',
},
{
tag: [t.angleBracket],
color: '#076678',
},
{
tag: [t.attributeName],
color: '#427b58',
},
{
tag: [t.regexp],
color: '#427b58',
},
{
tag: [t.quote],
color: '#928374',
},
{ tag: [t.string], color: '#3c3836' },
{
tag: t.link,
color: '#7c6f64',
textDecoration: 'underline',
textUnderlinePosition: 'under',
},
{
tag: [t.url, t.escape, t.special(t.string)],
color: '#8f3f71',
},
{ tag: [t.meta], color: '#b57614' },
{ tag: [t.comment], color: '#928374', fontStyle: 'italic' },
{ tag: t.strong, fontWeight: 'bold', color: '#af3a03' },
{ tag: t.emphasis, fontStyle: 'italic', color: '#79740e' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: t.heading, fontWeight: 'bold', color: '#79740e' },
{ tag: [t.heading1, t.heading2], fontWeight: 'bold', color: '#79740e' },
{
tag: [t.heading3, t.heading4],
fontWeight: 'bold',
color: '#b57614',
},
{
tag: [t.heading5, t.heading6],
color: '#b57614',
},
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#8f3f71' },
{
tag: [t.processingInstruction, t.inserted],
color: '#076678',
},
{
tag: [t.contentSeparator],
color: '#9d0006',
},
{ tag: t.invalid, color: '#af3a03', borderBottom: `1px dotted #9d0006` },
],
});
-77
View File
@@ -1,77 +0,0 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#212121',
lineBackground: '#21212199',
foreground: '#bdbdbd',
caret: '#a0a4ae',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
gutterBackground: '#212121',
gutterForeground: '#999',
gutterActiveForeground: '#4f5b66',
lineHighlight: '#111111',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#212121',
foreground: '#bdbdbd',
caret: '#a0a4ae',
selection: '#d7d4f063',
selectionMatch: '#d7d4f063',
gutterBackground: '#212121',
gutterForeground: '#999',
gutterActiveForeground: '#4f5b66',
lineHighlight: '#333333',
},
styles: [
{ tag: t.keyword, color: '#cf6edf' },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#56c8d8' },
{ tag: [t.propertyName], color: '#82AAFF' },
{ tag: [t.variableName], color: '#bdbdbd' },
{ tag: [t.function(t.variableName)], color: '#82AAFF' },
{ tag: [t.labelName], color: '#cf6edf' },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#facf4e' },
{ tag: [t.definition(t.name), t.separator], color: '#56c8d8' },
{ tag: [t.brace], color: '#cf6edf' },
{ tag: [t.annotation], color: '#f07178' },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#f07178' },
{ tag: [t.typeName, t.className], color: '#f07178' },
{ tag: [t.operator, t.operatorKeyword], color: '#82AAFF' },
{ tag: [t.tagName], color: '#99d066' },
{ tag: [t.squareBracket], color: '#f07178' },
{ tag: [t.angleBracket], color: '#606f7a' },
{ tag: [t.attributeName], color: '#bdbdbd' },
{ tag: [t.regexp], color: '#f07178' },
{ tag: [t.quote], color: '#6abf69' },
{ tag: [t.string], color: '#99d066' },
{
tag: t.link,
color: '#56c8d8',
textDecoration: 'underline',
textUnderlinePosition: 'under',
},
{ tag: [t.url, t.escape, t.special(t.string)], color: '#facf4e' },
{ tag: [t.meta], color: '#707d8b' },
{ tag: [t.comment], color: '#707d8b', fontStyle: 'italic' },
{ tag: t.monospace, color: '#bdbdbd' },
{ tag: t.strong, fontWeight: 'bold', color: '#f07178' },
{ tag: t.emphasis, fontStyle: 'italic', color: '#99d066' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: t.heading, fontWeight: 'bold', color: '#facf4e' },
{ tag: t.heading1, fontWeight: 'bold', color: '#facf4e' },
{
tag: [t.heading2, t.heading3, t.heading4],
fontWeight: 'bold',
color: '#facf4e',
},
{ tag: [t.heading5, t.heading6], color: '#facf4e' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#56c8d8' },
{ tag: [t.processingInstruction, t.inserted], color: '#f07178' },
{ tag: [t.contentSeparator], color: '#56c8d8' },
{ tag: t.invalid, color: '#606f7a', borderBottom: `1px dotted #f07178` },
],
});
-52
View File
@@ -1,52 +0,0 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
light: true,
background: '#FAFAFA',
lineBackground: '#FAFAFA99',
foreground: '#90A4AE',
caret: '#272727',
selection: '#80CBC440',
selectionMatch: '#FAFAFA',
gutterBackground: '#FAFAFA',
gutterForeground: '#90A4AE',
gutterBorder: 'transparent',
lineHighlight: '#CCD7DA50',
};
export default createTheme({
theme: 'light',
settings: {
background: '#FAFAFA',
foreground: '#90A4AE',
caret: '#272727',
selection: '#80CBC440',
selectionMatch: '#80CBC440',
gutterBackground: '#FAFAFA',
gutterForeground: '#90A4AE',
gutterBorder: 'transparent',
lineHighlight: '#CCD7DA50',
},
styles: [
{ tag: t.keyword, color: '#39ADB5' },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#90A4AE' },
{ tag: [t.propertyName], color: '#6182B8' },
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#91B859' },
{ tag: [t.function(t.variableName), t.labelName], color: '#6182B8' },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#39ADB5' },
{ tag: [t.definition(t.name), t.separator], color: '#90A4AE' },
{ tag: [t.className], color: '#E2931D' },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#F76D47' },
{ tag: [t.typeName], color: '#E2931D', fontStyle: '#E2931D' },
{ tag: [t.operator, t.operatorKeyword], color: '#39ADB5' },
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#91B859' },
{ tag: [t.meta, t.comment], color: '#90A4AE' },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.heading, fontWeight: 'bold', color: '#39ADB5' },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#90A4AE' },
{ tag: t.invalid, color: '#E5393570' },
{ tag: t.strikethrough, textDecoration: 'line-through' },
],
});
-45
View File
@@ -1,45 +0,0 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#272822',
lineBackground: '#27282299',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#49483E',
selectionMatch: '#49483E',
gutterBackground: '#272822',
gutterForeground: '#FFFFFF70',
lineHighlight: '#00000059',
};
export default createTheme({
theme: 'dark',
settings: {
background: '#272822',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#49483E',
selectionMatch: '#49483E',
gutterBackground: '#272822',
gutterForeground: '#FFFFFF70',
lineHighlight: '#0000003b',
},
styles: [
{ tag: t.labelName, color: '#bababa' },
{ tag: [t.comment, t.documentMeta], color: '#8292a2' },
{ tag: [t.number, t.bool, t.null, t.atom], color: '#ae81ff' },
{ tag: [t.attributeValue, t.className, t.name], color: '#e6db74' },
{ tag: [t.propertyName, t.attributeName], color: '#a6e22e' },
{ tag: [t.variableName], color: '#9effff' },
{ tag: [t.squareBracket], color: '#bababa' },
{ tag: [t.string, t.special(t.brace)], color: '#e6db74' },
{ tag: [t.regexp, t.className, t.typeName, t.definition(t.typeName)], color: '#66d9ef' },
{
tag: [t.definition(t.variableName), t.definition(t.propertyName), t.function(t.variableName)],
color: '#a6e22e',
},
// { tag: t.keyword, color: '#f92672' },
{ tag: [t.keyword, t.definitionKeyword, t.modifier, t.tagName, t.angleBracket], color: '#f92672' },
],
});
-50
View File
@@ -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' },
],
});
-78
View File
@@ -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` },
],
});
-39
View File
@@ -1,39 +0,0 @@
/**
* @name Atom One
* Atom One dark syntax theme
*
* https://github.com/atom/one-dark-syntax
*/
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
const hex = ['#000000', '#ff5356', '#bd312a', '#54636D', '#171717'];
export const settings = {
background: hex[0],
lineBackground: 'transparent',
foreground: hex[2],
selection: hex[4],
selectionMatch: hex[0],
gutterBackground: hex[0],
gutterForeground: hex[3],
gutterBorder: 'transparent',
lineHighlight: hex[0],
};
export default createTheme({
theme: 'dark',
settings,
styles: [
{
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
color: hex[2],
},
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[1] },
{ tag: t.comment, color: hex[3] },
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[2] },
{ tag: [t.attributeName, t.number], color: hex[1] },
{ tag: t.keyword, color: hex[2] },
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[1] },
],
});
-79
View File
@@ -1,79 +0,0 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
export const settings = {
background: '#002b36',
lineBackground: '#002b3699',
foreground: '#93a1a1',
caret: '#839496',
selection: '#173541',
selectionMatch: '#aafe661a',
gutterBackground: '#00252f',
gutterForeground: '#839496',
lineHighlight: '#173541',
};
const c = {
background: '#002B36',
foreground: '#839496',
selection: '#004454AA',
selectionMatch: '#005A6FAA',
cursor: '#D30102',
dropdownBackground: '#00212B',
dropdownBorder: '#2AA19899',
activeLine: '#00cafe11',
matchingBracket: '#073642',
keyword: '#859900',
storage: '#93A1A1',
variable: '#268BD2',
parameter: '#268BD2',
function: '#268BD2',
string: '#2AA198',
constant: '#CB4B16',
type: '#859900',
class: '#268BD2',
number: '#D33682',
comment: '#586E75',
heading: '#268BD2',
invalid: '#DC322F',
regexp: '#DC322F',
tag: '#268BD2',
};
export default createTheme({
theme: 'dark',
settings: {
background: c.background,
foreground: c.foreground,
caret: c.cursor,
selection: c.selection,
selectionMatch: c.selection,
gutterBackground: c.background,
gutterForeground: c.foreground,
gutterBorder: 'transparent',
lineHighlight: c.activeLine,
},
styles: [
{ tag: t.keyword, color: c.keyword },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: c.variable },
{ tag: [t.propertyName], color: c.function },
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: c.string },
{ tag: [t.function(t.variableName), t.labelName], color: c.function },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: c.constant },
{ tag: [t.definition(t.name), t.separator], color: c.variable },
{ tag: [t.className], color: c.class },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: c.number },
{ tag: [t.typeName], color: c.type, fontStyle: c.type },
{ tag: [t.operator, t.operatorKeyword], color: c.keyword },
{ tag: [t.url, t.escape, t.regexp, t.link], color: c.regexp },
{ tag: [t.meta, t.comment], color: c.comment },
{ tag: t.tagName, color: c.tag },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.heading, fontWeight: 'bold', color: c.heading },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: c.variable },
{ tag: t.invalid, color: c.invalid },
{ tag: t.strikethrough, textDecoration: 'line-through' },
],
});
-82
View File
@@ -1,82 +0,0 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
// this is slightly different from https://thememirror.net/solarized-light
export const settings = {
light: true,
background: '#fdf6e3',
lineBackground: '#fdf6e399',
foreground: '#657b83',
caret: '#586e75',
selection: '#dfd9c8',
selectionMatch: '#dfd9c8',
gutterBackground: '#00000010',
gutterForeground: '#657b83',
lineHighlight: '#dfd9c8',
};
const c = {
background: '#FDF6E3',
foreground: '#657B83',
selection: '#EEE8D5',
selectionMatch: '#EEE8D5',
cursor: '#657B83',
dropdownBackground: '#EEE8D5',
dropdownBorder: '#D3AF86',
activeLine: '#3d392d11',
matchingBracket: '#EEE8D5',
keyword: '#859900',
storage: '#586E75',
variable: '#268BD2',
parameter: '#268BD2',
function: '#268BD2',
string: '#2AA198',
constant: '#CB4B16',
type: '#859900',
class: '#268BD2',
number: '#D33682',
comment: '#93A1A1',
heading: '#268BD2',
invalid: '#DC322F',
regexp: '#DC322F',
tag: '#268BD2',
};
export default createTheme({
theme: 'light',
settings: {
background: c.background,
foreground: c.foreground,
caret: c.cursor,
selection: c.selection,
selectionMatch: c.selectionMatch,
gutterBackground: c.background,
gutterForeground: c.foreground,
gutterBorder: 'transparent',
lineHighlight: c.activeLine,
},
styles: [
{ tag: t.keyword, color: c.keyword },
{ tag: [t.name, t.deleted, t.character, t.macroName], color: c.variable },
{ tag: [t.propertyName], color: c.function },
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: c.string },
{ tag: [t.function(t.variableName), t.labelName], color: c.function },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: c.constant },
{ tag: [t.definition(t.name), t.separator], color: c.variable },
{ tag: [t.className], color: c.class },
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: c.number },
{ tag: [t.typeName], color: c.type, fontStyle: c.type },
{ tag: [t.operator, t.operatorKeyword], color: c.keyword },
{ tag: [t.url, t.escape, t.regexp, t.link], color: c.regexp },
{ tag: [t.meta, t.comment], color: c.comment },
{ tag: t.tagName, color: c.tag },
{ tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.link, textDecoration: 'underline' },
{ tag: t.heading, fontWeight: 'bold', color: c.heading },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: c.variable },
{ tag: t.invalid, color: c.invalid },
{ tag: t.strikethrough, textDecoration: 'line-through' },
],
});
-39
View File
@@ -1,39 +0,0 @@
/**
* @name 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] },
],
});
+13 -16
View File
@@ -1,23 +1,20 @@
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',
};
import { createTheme } from '@uiw/codemirror-themes';
export default createTheme({
theme: 'dark',
settings,
settings: {
background: '#222',
foreground: '#75baff', // whats that?
caret: '#ffcc00',
selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626',
// lineHighlight: '#8a91991a', // original
lineHighlight: '#00000050',
gutterBackground: 'transparent',
// gutterForeground: '#8a919966',
gutterForeground: '#8a919966',
},
styles: [
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#89ddff' },
{ tag: t.labelName, color: '#89ddff' },
{ tag: t.keyword, color: '#c792ea' },
{ tag: t.operator, color: '#89ddff' },
-43
View File
@@ -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' },
],
});
+1 -1
View File
@@ -1,5 +1,5 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
import { createTheme } from '@uiw/codemirror-themes';
let colorA = '#6edee4';
//let colorB = 'magenta';
+20 -20
View File
@@ -1,5 +1,5 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
import { createTheme } from '@uiw/codemirror-themes';
export const settings = {
background: 'black',
foreground: '#41FF00', // whats that?
@@ -14,24 +14,24 @@ 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.labelName, color: '#41FF00' },
{ tag: t.keyword, color: '#41FF00' },
{ tag: t.operator, color: '#41FF00' },
{ tag: t.special(t.variableName), color: '#41FF00' },
{ tag: t.typeName, color: '#41FF00' },
{ tag: t.atom, color: '#41FF00' },
{ tag: t.number, color: '#41FF00' },
{ tag: t.definition(t.variableName), color: '#41FF00' },
{ tag: t.string, color: '#41FF00' },
{ tag: t.special(t.string), color: '#41FF00' },
{ tag: t.comment, color: '#41FF00' },
{ tag: t.variableName, color: '#41FF00' },
{ tag: t.tagName, color: '#41FF00' },
{ tag: t.bracket, color: '#41FF00' },
{ tag: t.meta, color: '#41FF00' },
{ tag: t.attributeName, color: '#41FF00' },
{ tag: t.propertyName, color: '#41FF00' },
{ tag: t.className, color: '#41FF00' },
{ tag: t.invalid, color: '#41FF00' },
],
});
-42
View File
@@ -1,42 +0,0 @@
import { EditorView } from '@codemirror/view';
import { syntaxHighlighting } from '@codemirror/language';
import { HighlightStyle } from '@codemirror/language';
export const createTheme = ({ theme, settings, styles }) => {
const _theme = EditorView.theme(
{
'&': {
color: settings.foreground,
backgroundColor: settings.background,
},
'.cm-gutters': {
backgroundColor: settings.gutterBackground,
color: settings.gutterForeground,
//borderRightColor: settings.gutterBorder
},
'.cm-content': {
caretColor: settings.caret,
},
'.cm-cursor, .cm-dropCursor': {
borderLeftColor: settings.caret,
},
'.cm-activeLineGutter': {
// color: settings.gutterActiveForeground
backgroundColor: settings.lineHighlight,
},
'.cm-activeLine': {
backgroundColor: settings.lineHighlight,
},
'&.cm-focused .cm-selectionBackground, & .cm-line::selection, & .cm-selectionLayer .cm-selectionBackground, .cm-content ::selection':
{
background: settings.selection + ' !important',
},
'& .cm-selectionMatch': {
backgroundColor: settings.selectionMatch,
},
},
{ dark: theme === 'dark' },
);
const highlightStyle = HighlightStyle.define(styles);
return [_theme, syntaxHighlighting(highlightStyle)];
};
-52
View File
@@ -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' },
],
});
-52
View File
@@ -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' },
],
});
-53
View File
@@ -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' },
],
});
-80
View File
@@ -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' },
],
});
-81
View File
@@ -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' },
],
});
+20 -20
View File
@@ -1,5 +1,5 @@
import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs';
import { createTheme } from '@uiw/codemirror-themes';
export const settings = {
background: 'white',
foreground: 'black', // whats that?
@@ -16,24 +16,24 @@ 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' },
{ tag: t.labelName, color: 'black' },
{ tag: t.keyword, color: 'black' },
{ tag: t.operator, color: 'black' },
{ tag: t.special(t.variableName), color: 'black' },
{ tag: t.typeName, color: 'black' },
{ tag: t.atom, color: 'black' },
{ tag: t.number, color: 'black' },
{ tag: t.definition(t.variableName), color: 'black' },
{ tag: t.string, color: 'black' },
{ tag: t.special(t.string), color: 'black' },
{ tag: t.comment, color: 'black' },
{ tag: t.variableName, color: 'black' },
{ tag: t.tagName, color: 'black' },
{ tag: t.bracket, color: 'black' },
{ tag: t.meta, color: 'black' },
{ tag: t.attributeName, color: 'black' },
{ tag: t.propertyName, color: 'black' },
{ tag: t.className, color: 'black' },
{ tag: t.invalid, color: 'black' },
],
});
-38
View File
@@ -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' },
],
});
-7
View File
@@ -133,10 +133,3 @@ registerWidget('_pitchwheel', (id, options = {}, pat) => {
const ctx = getCanvasWidget(id, options).getContext('2d');
return pat.pitchwheel({ ...options, ctx, id });
});
registerWidget('_spectrum', (id, options = {}, pat) => {
let _size = options.size || 200;
options = { width: _size, height: _size, ...options, size: _size / 5 };
const ctx = getCanvasWidget(id, options).getContext('2d');
return pat.spectrum({ ...options, ctx, id });
});
+1 -1
View File
@@ -4,7 +4,7 @@ import { calculateTactus, sequence, stack } from '../index.mjs';
const pat64 = sequence(...Array(64).keys());
describe('steps', () => {
describe('tactus', () => {
calculateTactus(true);
bench(
'+tactus',
+11 -4
View File
@@ -4,9 +4,9 @@
// import createClock from './zyklus.mjs';
function getTime() {
const seconds = performance.now() * 0.001;
return seconds;
// return Math.round(seconds * precision) / precision;
const precision = 10 ** 4;
const seconds = performance.now() / 1000;
return Math.round(seconds * precision) / precision;
}
let num_cycles_at_cps_change = 0;
@@ -24,20 +24,27 @@ const sendMessage = (type, payload) => {
const sendTick = (phase, duration, tick, time) => {
const num_seconds_since_cps_change = num_ticks_since_cps_change * duration;
const tickdeadline = phase - time;
const lastTick = time + tickdeadline;
const num_cycles_since_cps_change = num_seconds_since_cps_change * cps;
const begin = num_cycles_at_cps_change + num_cycles_since_cps_change;
const secondsSinceLastTick = time - lastTick - duration;
const eventLength = duration * cps;
const end = begin + eventLength;
const cycle = begin + secondsSinceLastTick * cps;
sendMessage('tick', {
begin,
end,
cps,
time,
tickdeadline,
num_cycles_at_cps_change,
num_seconds_at_cps_change,
num_seconds_since_cps_change,
cycle,
});
num_ticks_since_cps_change++;
+17 -223
View File
@@ -1,5 +1,5 @@
/*
controls.mjs - Registers audio controls for pattern manipulation and effects.
controls.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/controls.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -47,16 +47,12 @@ export function createParam(names) {
return func;
}
// maps control alias names to the "main" control name
const controlAlias = new Map();
export function registerControl(names, ...aliases) {
const name = Array.isArray(names) ? names[0] : names;
let bag = {};
bag[name] = createParam(names);
aliases.forEach((alias) => {
bag[alias] = bag[name];
controlAlias.set(alias, name);
Pattern.prototype[alias] = Pattern.prototype[name];
});
return bag;
@@ -77,6 +73,8 @@ export function registerControl(names, ...aliases) {
*/
export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound');
export const { rec } = registerControl(['rec', 'n']);
/**
* Define a custom webaudio node to use as a sound source.
*
@@ -458,6 +456,9 @@ export const { drive } = registerControl('drive');
*/
export const { channels, ch } = registerControl('channels', 'ch');
// superdirt only
export const { phaserrate, phasr } = registerControl('phaserrate', 'phasr');
/**
* Phaser audio effect that approximates popular guitar pedals.
*
@@ -469,11 +470,7 @@ export const { channels, ch } = registerControl('channels', 'ch');
* .phaser("<1 2 4 8>")
*
*/
export const { phaserrate, ph, phaser } = registerControl(
['phaserrate', 'phaserdepth', 'phasercenter', 'phasersweep'],
'ph',
'phaser',
);
export const { phaser, ph } = registerControl(['phaser', 'phaserdepth', 'phasercenter', 'phasersweep'], 'ph');
/**
* The frequency sweep range of the lfo for the phaser effect. Defaults to 2000
@@ -1388,17 +1385,6 @@ export const { compressorRelease } = registerControl('compressorRelease');
*
*/
export const { speed } = registerControl('speed');
/**
* Changes the speed of sample playback, i.e. a cheap way of changing pitch.
*
* @name stretch
* @param {number | Pattern} factor -inf to inf, negative numbers play the sample backwards.
* @example
* s("gm_flute").stretch("1 2 .5")
*
*/
export const { stretch } = registerControl('stretch');
/**
* Used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`.
*
@@ -1409,7 +1395,6 @@ export const { stretch } = registerControl('stretch');
* @superdirtOnly
*
*/
export const { unit } = registerControl('unit');
/**
* Made by Calum Gunn. Reminiscent of some weird mixture of filter, ring-modulator and pitch-shifter. The SuperCollider manual defines Squiz as:
@@ -1513,11 +1498,20 @@ export const { scram } = registerControl('scram');
export const { binshift } = registerControl('binshift');
export const { hbrick } = registerControl('hbrick');
export const { lbrick } = registerControl('lbrick');
export const { midichan } = registerControl('midichan');
export const { control } = registerControl('control');
export const { ccn } = registerControl('ccn');
export const { ccv } = registerControl('ccv');
export const { polyTouch } = registerControl('polyTouch');
export const { midibend } = registerControl('midibend');
export const { miditouch } = registerControl('miditouch');
export const { ctlNum } = registerControl('ctlNum');
export const { frameRate } = registerControl('frameRate');
export const { frames } = registerControl('frames');
export const { hours } = registerControl('hours');
export const { midicmd } = registerControl('midicmd');
export const { minutes } = registerControl('minutes');
export const { progNum } = registerControl('progNum');
export const { seconds } = registerControl('seconds');
export const { songPtr } = registerControl('songPtr');
export const { uid } = registerControl('uid');
@@ -1609,203 +1603,3 @@ export const ar = register('ar', (t, pat) => {
const [attack, release = attack] = t;
return pat.set({ attack, release });
});
//MIDI
/**
* MIDI channel: Sets the MIDI channel for the event.
*
* @name midichan
* @param {number | Pattern} channel MIDI channel number (0-15)
* @example
* note("c4").midichan(1).midi()
*/
export const { midichan } = registerControl('midichan');
export const { midimap } = registerControl('midimap');
/**
* MIDI port: Sets the MIDI port for the event.
*
* @name midiport
* @param {number | Pattern} port MIDI port
* @example
* note("c a f e").midiport("<0 1 2 3>").midi()
*/
export const { midiport } = registerControl('midiport');
/**
* MIDI command: Sends a MIDI command message.
*
* @name midicmd
* @param {number | Pattern} command MIDI command
* @example
* midicmd("clock*48,<start stop>/2").midi()
*/
export const { midicmd } = registerControl('midicmd');
/**
* MIDI control: Sends a MIDI control change message.
*
* @name control
* @param {number | Pattern} MIDI control number (0-127)
* @param {number | Pattern} MIDI controller value (0-127)
*/
export const control = register('control', (args, pat) => {
if (!Array.isArray(args)) {
throw new Error('control expects an array of [ccn, ccv]');
}
const [_ccn, _ccv] = args;
return pat.ccn(_ccn).ccv(_ccv);
});
/**
* MIDI control number: Sends a MIDI control change message.
*
* @name ccn
* @param {number | Pattern} MIDI control number (0-127)
*/
export const { ccn } = registerControl('ccn');
/**
* MIDI control value: Sends a MIDI control change message.
*
* @name ccv
* @param {number | Pattern} MIDI control value (0-127)
*/
export const { ccv } = registerControl('ccv');
export const { ctlNum } = registerControl('ctlNum');
// TODO: ctlVal?
/**
* MIDI NRPN non-registered parameter number: Sends a MIDI NRPN non-registered parameter number message.
* @name nrpnn
* @param {number | Pattern} nrpnn MIDI NRPN non-registered parameter number (0-127)
* @example
* note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi()
*/
export const { nrpnn } = registerControl('nrpnn');
/**
* MIDI NRPN non-registered parameter value: Sends a MIDI NRPN non-registered parameter value message.
* @name nrpv
* @param {number | Pattern} nrpv MIDI NRPN non-registered parameter value (0-127)
* @example
* note("c4").nrpnn("1:8").nrpv("123").midichan(1).midi()
*/
export const { nrpv } = registerControl('nrpv');
/**
* MIDI program number: Sends a MIDI program change message.
*
* @name progNum
* @param {number | Pattern} program MIDI program number (0-127)
* @example
* note("c4").progNum(10).midichan(1).midi()
*/
export const { progNum } = registerControl('progNum');
/**
* MIDI sysex: Sends a MIDI sysex message.
* @name sysex
* @param {number | Pattern} id Sysex ID
* @param {number | Pattern} data Sysex data
* @example
* note("c4").sysex(["0x77", "0x01:0x02:0x03:0x04"]).midichan(1).midi()
*/
export const sysex = register('sysex', (args, pat) => {
if (!Array.isArray(args)) {
throw new Error('sysex expects an array of [id, data]');
}
const [id, data] = args;
return pat.sysexid(id).sysexdata(data);
});
/**
* MIDI sysex ID: Sends a MIDI sysex identifier message.
* @name sysexid
* @param {number | Pattern} id Sysex ID
* @example
* note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi()
*/
export const { sysexid } = registerControl('sysexid');
/**
* MIDI sysex data: Sends a MIDI sysex message.
* @name sysexdata
* @param {number | Pattern} data Sysex data
* @example
* note("c4").sysexid("0x77").sysexdata("0x01:0x02:0x03:0x04").midichan(1).midi()
*/
export const { sysexdata } = registerControl('sysexdata');
/**
* MIDI pitch bend: Sends a MIDI pitch bend message.
* @name midibend
* @param {number | Pattern} midibend MIDI pitch bend (-1 - 1)
* @example
* note("c4").midibend(sine.slow(4).range(-0.4,0.4)).midi()
*/
export const { midibend } = registerControl('midibend');
/**
* MIDI key after touch: Sends a MIDI key after touch message.
* @name miditouch
* @param {number | Pattern} miditouch MIDI key after touch (0-1)
* @example
* note("c4").miditouch(sine.slow(4).range(0,1)).midi()
*/
export const { miditouch } = registerControl('miditouch');
// TODO: what is this?
export const { polyTouch } = registerControl('polyTouch');
export const getControlName = (alias) => {
if (controlAlias.has(alias)) {
return controlAlias.get(alias);
}
return alias;
};
/**
* Sets properties in a batch.
*
* @name as
* @param {String | Array} mapping the control names that are set
* @example
* "c:.5 a:1 f:.25 e:.8".as("note:clip")
* @example
* "{0@2 0.25 0 0.5 .3 .5}%8".as("begin").s("sax_vib").clip(1)
*/
export const as = register('as', (mapping, pat) => {
mapping = Array.isArray(mapping) ? mapping : [mapping];
return pat.fmap((v) => {
v = Array.isArray(v) ? v : [v];
v = Object.fromEntries(mapping.map((prop, i) => [getControlName(prop), v[i]]));
return v;
});
});
/**
* Allows you to scrub an audio file like a tape loop by passing values that represents the position in the audio file
* in the optional array syntax ex: "0.5:2", the second value controls the speed of playback
* @name scrub
* @memberof Pattern
* @returns Pattern
* @example
* samples('github:switchangel/pad')
* s("swpad:0").scrub("{0.1!2 .25@3 0.7!2 <0.8:1.5>}%8")
* @example
* samples('github:yaxu/clean-breaks/main');
* s("amen/4").fit().scrub("{0@3 0@2 4@3}%8".div(16))
*/
export const scrub = register(
'scrub',
(beginPat, pat) => {
return beginPat.outerBind((v) => {
if (!Array.isArray(v)) {
v = [v];
}
const [beginVal, speedMultiplier = 1] = v;
return pat.begin(beginVal).mul(speed(speedMultiplier)).clip(1);
});
},
false,
);
-4
View File
@@ -68,10 +68,6 @@ export class Cyclist {
// see https://github.com/tidalcycles/strudel/pull/1004
const deadline = targetTime - phase;
onTrigger?.(hap, deadline, duration, this.cps, targetTime);
if (hap.value.cps !== undefined && this.cps != hap.value.cps) {
this.cps = hap.value.cps;
this.num_ticks_since_cps_change = 0;
}
}
});
} catch (e) {
+2 -1
View File
@@ -4,6 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { isPattern } from './index.mjs';
export const evalScope = async (...args) => {
const results = await Promise.allSettled(args);
const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value);
@@ -37,7 +39,6 @@ function safeEval(str, options = {}) {
export const evaluate = async (code, transpiler, transpilerOptions) => {
let meta = {};
if (transpiler) {
// transform syntactically correct js code to semantically usable code
const transpiled = transpiler(code, transpilerOptions);
+2 -2
View File
@@ -119,10 +119,10 @@ export const lcm = (...fractions) => {
if (fractions.length === 0) {
return undefined;
}
const x = fractions.pop();
return fractions.reduce(
(lcm, fraction) => (lcm === undefined || fraction === undefined ? undefined : lcm.lcm(fraction)),
x,
fraction(1),
);
};
-1
View File
@@ -14,7 +14,6 @@ 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';
+58 -18
View File
@@ -5,7 +5,6 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import { logger } from './logger.mjs';
import { ClockCollator, cycleToSeconds } from './util.mjs';
export class NeoCyclist {
constructor({ onTrigger, onToggle, getTime }) {
@@ -14,38 +13,79 @@ export class NeoCyclist {
this.lastTick = 0; // absolute time when last tick (clock callback) happened
this.getTime = getTime; // get absolute time
this.time_at_last_tick_message = 0;
// 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.num_cycles_at_cps_change = 0;
this.onToggle = onToggle;
this.latency = 0.1; // fixed trigger time offset
this.cycle = 0;
this.id = Math.round(Date.now() * Math.random());
this.worker_time_dif;
this.worker = new SharedWorker(new URL('./clockworker.js', import.meta.url));
this.worker.port.start();
this.channel = new BroadcastChannel('strudeltick');
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;
let weight = 0; // the amount of weight that is applied to the current average when averaging a new time dif
const maxWeight = 20;
const precision = 10 ** 3; //round off time diff to prevent accumulating outliers
// the clock of the worker and the audio context clock can drift apart over time
// aditionally, the message time of the worker pinging the callback to process haps can be inconsistent.
// we need to keep a rolling weighted average of the time difference between the worker clock and audio context clock
// in order to schedule events consistently.
const setTimeReference = (num_seconds_at_cps_change, num_seconds_since_cps_change, tickdeadline) => {
const time_dif = getTime() - (num_seconds_at_cps_change + num_seconds_since_cps_change) + tickdeadline;
if (this.worker_time_dif == null) {
this.worker_time_dif = time_dif;
} else {
const w = 1; //weight of new time diff;
const new_dif =
Math.round(((this.worker_time_dif * weight + time_dif * w) / (weight + w)) * precision) / precision;
if (new_dif != this.worker_time_dif) {
// reset the weight so the clock recovers faster from an audio context freeze/dropout if it happens
weight = 4;
}
this.worker_time_dif = new_dif;
}
weight = Math.min(weight + 1, maxWeight);
};
const processHaps = (begin, end, currentTime) => {
const tickCallback = (payload) => {
const {
num_cycles_at_cps_change,
cps,
num_seconds_at_cps_change,
num_seconds_since_cps_change,
begin,
end,
tickdeadline,
cycle,
} = payload;
this.cps = cps;
this.cycle = cycle;
setTimeReference(num_seconds_at_cps_change, num_seconds_since_cps_change, tickdeadline);
processHaps(begin, end, num_cycles_at_cps_change, num_seconds_at_cps_change);
this.time_at_last_tick_message = this.getTime();
};
const processHaps = (begin, end, num_cycles_at_cps_change, seconds_at_cps_change) => {
if (this.started === false) {
return;
}
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps });
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);
const targetTime =
(hap.whole.begin - num_cycles_at_cps_change) / this.cps +
seconds_at_cps_change +
this.latency +
this.worker_time_dif;
const duration = hap.duration / this.cps;
onTrigger?.(hap, 0, duration, this.cps, targetTime);
}
});
@@ -89,8 +129,8 @@ export class NeoCyclist {
this.setStarted(true);
}
stop() {
this.worker_time_dif = null;
logger('[cyclist] stop');
this.collator.reset();
this.setStarted(false);
}
setPattern(pat, autostart = false) {
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/core",
"version": "1.2.0",
"version": "1.1.0",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"type": "module",
@@ -31,11 +31,11 @@
},
"homepage": "https://strudel.cc",
"dependencies": {
"fraction.js": "^5.2.1"
"fraction.js": "^4.3.7"
},
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
"devDependencies": {
"vite": "^6.0.11",
"vitest": "^3.0.4"
"vite": "^5.0.10",
"vitest": "^1.1.0"
}
}
+243 -567
View File
File diff suppressed because it is too large Load Diff
-214
View File
@@ -1,214 +0,0 @@
/*
pick.mjs - methods that use one pattern to pick events from other patterns.
Copyright (C) 2024 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/signal.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern, reify, silence, register } from './pattern.mjs';
import { _mod, clamp, objectMap } from './util.mjs';
const _pick = function (lookup, pat, modulo = true) {
const array = Array.isArray(lookup);
const len = Object.keys(lookup).length;
lookup = objectMap(lookup, reify);
if (len === 0) {
return silence;
}
return pat.fmap((i) => {
let key = i;
if (array) {
key = modulo ? Math.round(key) % len : clamp(Math.round(key), 0, lookup.length - 1);
}
return lookup[key];
});
};
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
* Similar to `inhabit`, but maintains the structure of the original patterns.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
* @example
* note("<0 1 2!2 3>".pick(["g a", "e f", "f g f g" , "g c d"]))
* @example
* sound("<0 1 [2,0]>".pick(["bd sd", "cp cp", "hh hh"]))
* @example
* sound("<0!2 [0,1] 1>".pick(["bd(3,8)", "sd sd"]))
* @example
* s("<a!2 [a,b] b>".pick({a: "bd(3,8)", b: "sd sd"}))
*/
export const pick = function (lookup, pat) {
// backward compatibility - the args used to be flipped
if (Array.isArray(pat)) {
[pat, lookup] = [lookup, pat];
}
return __pick(lookup, pat);
};
const __pick = register('pick', function (lookup, pat) {
return _pick(lookup, pat, false).innerJoin();
});
/** * The same as `pick`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* For example, if you pick the fifth pattern of a list of three, you'll get the
* second one.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmod = register('pickmod', function (lookup, pat) {
return _pick(lookup, pat, true).innerJoin();
});
/** * pickF lets you use a pattern of numbers to pick which function to apply to another pattern.
* @param {Pattern} pat
* @param {Pattern} lookup a pattern of indices
* @param {function[]} funcs the array of functions from which to pull
* @returns {Pattern}
* @example
* s("bd [rim hh]").pickF("<0 1 2>", [rev,jux(rev),fast(2)])
* @example
* note("<c2 d2>(3,8)").s("square")
* .pickF("<0 2> 1", [jux(rev),fast(2),x=>x.lpf(800)])
*/
export const pickF = register('pickF', function (lookup, funcs, pat) {
return pat.apply(pick(lookup, funcs));
});
/** * The same as `pickF`, but if you pick a number greater than the size of the functions list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {Pattern} lookup a pattern of indices
* @param {function[]} funcs the array of functions from which to pull
* @returns {Pattern}
*/
export const pickmodF = register('pickmodF', function (lookup, funcs, pat) {
return pat.apply(pickmod(lookup, funcs));
});
/** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickOut = register('pickOut', function (lookup, pat) {
return _pick(lookup, pat, false).outerJoin();
});
/** * The same as `pickOut`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmodOut = register('pickmodOut', function (lookup, pat) {
return _pick(lookup, pat, true).outerJoin();
});
/** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickRestart = register('pickRestart', function (lookup, pat) {
return _pick(lookup, pat, false).restartJoin();
});
/** * The same as `pickRestart`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
* @example
* "<a@2 b@2 c@2 d@2>".pickRestart({
a: n("0 1 2 0"),
b: n("2 3 4 ~"),
c: n("[4 5] [4 3] 2 0"),
d: n("0 -3 0 ~")
}).scale("C:major").s("piano")
*/
export const pickmodRestart = register('pickmodRestart', function (lookup, pat) {
return _pick(lookup, pat, true).restartJoin();
});
/** * Similar to `pick`, but the choosen pattern is reset when its index is triggered.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickReset = register('pickReset', function (lookup, pat) {
return _pick(lookup, pat, false).resetJoin();
});
/** * The same as `pickReset`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmodReset = register('pickmodReset', function (lookup, pat) {
return _pick(lookup, pat, true).resetJoin();
});
/**
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
* Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern.
* @name inhabit
* @synonyms pickSqueeze
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
* @example
* "<a b [a,b]>".inhabit({a: s("bd(3,8)"),
b: s("cp sd")
})
* @example
* s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4)
*/
export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], function (lookup, pat) {
return _pick(lookup, pat, false).squeezeJoin();
});
/** * The same as `inhabit`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* For example, if you pick the fifth pattern of a list of three, you'll get the
* second one.
* @name inhabitmod
* @synonyms pickmodSqueeze
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSqueeze'], function (lookup, pat) {
return _pick(lookup, pat, true).squeezeJoin();
});
/**
* Pick from the list of values (or patterns of values) via the index using the given
* pattern of integers. The selected pattern will be compressed to fit the duration of the selecting event
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
* @example
* note(squeeze("<0@2 [1!2] 2>", ["g a", "f g f g" , "g a c d"]))
*/
export const squeeze = (pat, xs) => {
xs = xs.map(reify);
if (xs.length == 0) {
return silence;
}
return pat
.fmap((i) => {
const key = _mod(Math.round(i), xs.length);
return xs[key];
})
.squeezeJoin();
};
+2 -39
View File
@@ -63,13 +63,11 @@ export function repl({
let pPatterns = {};
let anonymousIndex = 0;
let allTransform;
let eachTransform;
const hush = function () {
pPatterns = {};
anonymousIndex = 0;
allTransform = undefined;
eachTransform = undefined;
return silence;
};
@@ -86,42 +84,15 @@ export function repl({
const toggle = () => scheduler.toggle();
const setCps = (cps) => scheduler.setCps(cps);
const setCpm = (cpm) => scheduler.setCps(cpm / 60);
// TODO - not documented as jsdoc examples as the test framework doesn't simulate enough context for `each` and `all`..
/** Applies a function to all the running patterns. Note that the patterns are groups together into a single `stack` before the function is applied. This is probably what you want, but see `each` for
* a version that applies the function to each pattern separately.
* ```
* $: sound("bd - cp sd")
* $: sound("hh*8")
* all(fast("<2 3>"))
* ```
* ```
* $: sound("bd - cp sd")
* $: sound("hh*8")
* all(x => x.pianoroll())
* ```
*/
const all = function (transform) {
allTransform = transform;
return silence;
};
/** Applies a function to each of the running patterns separately. This is intended for future use with upcoming 'stepwise' features. See `all` for a version that applies the function to all the patterns stacked together into a single pattern.
* ```
* $: sound("bd - cp sd")
* $: sound("hh*8")
* each(fast("<2 3>"))
* ```
*/
const each = function (transform) {
eachTransform = transform;
return silence;
};
// set pattern methods that use this repl via closure
const injectPatternMethods = () => {
Pattern.prototype.p = function (id) {
if (typeof id === 'string' && (id.startsWith('_') || id.endsWith('_'))) {
if (id.startsWith('_') || id.endsWith('_')) {
// allows muting a pattern x with x_ or _x
return silence;
}
@@ -160,7 +131,6 @@ export function repl({
});
return evalScope({
all,
each,
hush,
cpm,
setCps,
@@ -182,14 +152,7 @@ export function repl({
shouldHush && hush();
let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions);
if (Object.keys(pPatterns).length) {
let patterns = Object.values(pPatterns);
if (eachTransform) {
// Explicit lambda so only element (not index and array) are passed
patterns = patterns.map((x) => eachTransform(x));
}
pattern = stack(...patterns);
} else if (eachTransform) {
pattern = eachTransform(pattern);
pattern = stack(...Object.values(pPatterns));
}
if (allTransform) {
pattern = allTransform(pattern);
+230 -252
View File
@@ -1,14 +1,13 @@
/*
signal.mjs - continuous patterns
Copyright (C) 2024 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/signal.mjs>
signal.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/signal.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Hap } from './hap.mjs';
import { Pattern, fastcat, pure, register, reify, silence, stack, sequenceP } from './pattern.mjs';
import { Pattern, fastcat, reify, silence, stack, register } from './pattern.mjs';
import Fraction from './fraction.mjs';
import { id, keyAlias, getCurrentKeyboardState } from './util.mjs';
import { id, _mod, clamp, objectMap } from './util.mjs';
export function steady(value) {
// A continuous value
@@ -16,10 +15,13 @@ export function steady(value) {
}
export const signal = (func) => {
const query = (state) => [new Hap(undefined, state.span, func(state.span.begin))];
const query = (state) => [new Hap(undefined, state.span, func(state.span.midpoint()))];
return new Pattern(query);
};
export const isaw = signal((t) => 1 - (t % 1));
export const isaw2 = isaw.toBipolar();
/**
* A sawtooth signal between 0 and 1.
*
@@ -33,40 +35,8 @@ export const signal = (func) => {
*
*/
export const saw = signal((t) => t % 1);
/**
* A sawtooth signal between -1 and 1 (like `saw`, but bipolar).
*
* @return {Pattern}
*/
export const saw2 = saw.toBipolar();
/**
* A sawtooth signal between 1 and 0 (like `saw`, but flipped).
*
* @return {Pattern}
* @example
* note("<c3 [eb3,g3] g2 [g3,bb3]>*8")
* .clip(isaw.slow(2))
* @example
* n(isaw.range(0,8).segment(8))
* .scale('C major')
*
*/
export const isaw = signal((t) => 1 - (t % 1));
/**
* A sawtooth signal between 1 and -1 (like `saw2`, but flipped).
*
* @return {Pattern}
*/
export const isaw2 = isaw.toBipolar();
/**
* A sine signal between -1 and 1 (like `sine`, but bipolar).
*
* @return {Pattern}
*/
export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
/**
@@ -90,12 +60,6 @@ export const sine = sine2.fromBipolar();
*
*/
export const cosine = sine._early(Fraction(1).div(4));
/**
* A cosine signal between -1 and 1 (like `cosine`, but bipolar).
*
* @return {Pattern}
*/
export const cosine2 = sine2._early(Fraction(1).div(4));
/**
@@ -107,12 +71,6 @@ export const cosine2 = sine2._early(Fraction(1).div(4));
*
*/
export const square = signal((t) => Math.floor((t * 2) % 2));
/**
* A square signal between -1 and 1 (like `square`, but bipolar).
*
* @return {Pattern}
*/
export const square2 = square.toBipolar();
/**
@@ -123,37 +81,9 @@ 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(isaw, saw);
export const tri2 = fastcat(isaw2, saw2);
/**
* A triangle signal between -1 and 1 (like `tri`, but bipolar).
*
* @return {Pattern}
*/
export const tri2 = fastcat(saw2, isaw2);
/**
* An inverted triangle signal between 1 and 0 (like `tri`, but flipped).
*
* @return {Pattern}
* @example
* n(itri.segment(8).range(0,7)).scale("C:minor")
*
*/
export const itri = fastcat(isaw, saw);
/**
* An inverted triangle signal between -1 and 1 (like `itri`, but bipolar).
*
* @return {Pattern}
*/
export const itri2 = fastcat(isaw2, saw2);
/**
* A signal representing the cycle time.
*
* @return {Pattern}
*/
export const time = signal(id);
/**
@@ -208,7 +138,7 @@ const timeToRand = (x) => Math.abs(intSeedToRand(timeToIntSeed(x)));
const timeToRandsPrime = (seed, n) => {
const result = [];
// eslint-disable-next-line
for (let i = 0; i < n; ++i) {
for (let i = 0; i < n; ++n) {
result.push(intSeedToRand(seed));
seed = xorwise(seed);
}
@@ -227,82 +157,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);
/**
* Creates a pattern from a binary number.
*
* @name binary
* @param {number} n - input number to convert to binary
* @example
* "hh".s().struct(binary(5))
* // "hh".s().struct("1 0 1")
*/
export const binary = (n) => {
const nBits = reify(n).log2(0).floor().add(1);
return binaryN(n, nBits);
};
/**
* Creates a pattern from a binary number, padded to n bits long.
*
* @name binaryN
* @param {number} n - input number to convert to binary
* @param {number} nBits - pattern length, defaults to 16
* @example
* "hh".s().struct(binaryN(55532, 16))
* // "hh".s().struct("1 1 0 1 1 0 0 0 1 1 1 0 1 1 0 0")
*/
export const binaryN = (n, nBits = 16) => {
nBits = reify(nBits);
// Shift and mask, putting msb on the right-side
const bitPos = run(nBits).mul(-1).add(nBits.sub(1));
return reify(n).segment(nBits).brshift(bitPos).band(pure(1));
};
export const randrun = (n) => {
return signal((t) => {
// Without adding 0.5, the first cycle is always 0,1,2,3,...
const rands = timeToRands(t.floor().add(0.5), n);
const nums = rands
.map((n, i) => [n, i])
.sort((a, b) => a[0] > b[0] - a[0] < b[0])
.map((x) => x[1]);
const i = t.cyclePos().mul(n).floor() % n;
return nums[i];
})._segment(n);
};
const _rearrangeWith = (ipat, n, pat) => {
const pats = [...Array(n).keys()].map((i) => pat.zoom(Fraction(i).div(n), Fraction(i + 1).div(n)));
return ipat.fmap((i) => pats[i].repeatCycles(n)._fast(n)).innerJoin();
};
/**
* @name shuffle
* Slices a pattern into the given number of parts, then plays those parts in random order.
* Each part will be played exactly once per cycle.
* @example
* note("c d e f").sound("piano").shuffle(4)
* @example
* note("c d e f".shuffle(4), "g").sound("piano")
*/
export const shuffle = register('shuffle', (n, pat) => {
return _rearrangeWith(randrun(n), n, pat);
});
/**
* @name scramble
* Slices a pattern into the given number of parts, then plays those parts at random. Similar to `shuffle`,
* but parts might be played more than once, or not at all, per cycle.
* @example
* note("c d e f").sound("piano").scramble(4)
* @example
* note("c d e f".scramble(4), "g").sound("piano")
*/
export const scramble = register('scramble', (n, pat) => {
return _rearrangeWith(_irand(n)._segment(n), n, pat);
});
export const run = (n) => saw.range(0, n).floor().segment(n);
/**
* A continuous pattern of random numbers, between 0 and 1.
@@ -354,6 +209,204 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i));
*/
export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin();
const _pick = function (lookup, pat, modulo = true) {
const array = Array.isArray(lookup);
const len = Object.keys(lookup).length;
lookup = objectMap(lookup, reify);
if (len === 0) {
return silence;
}
return pat.fmap((i) => {
let key = i;
if (array) {
key = modulo ? Math.round(key) % len : clamp(Math.round(key), 0, lookup.length - 1);
}
return lookup[key];
});
};
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
* Similar to `inhabit`, but maintains the structure of the original patterns.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
* @example
* note("<0 1 2!2 3>".pick(["g a", "e f", "f g f g" , "g c d"]))
* @example
* sound("<0 1 [2,0]>".pick(["bd sd", "cp cp", "hh hh"]))
* @example
* sound("<0!2 [0,1] 1>".pick(["bd(3,8)", "sd sd"]))
* @example
* s("<a!2 [a,b] b>".pick({a: "bd(3,8)", b: "sd sd"}))
*/
export const pick = function (lookup, pat) {
// backward compatibility - the args used to be flipped
if (Array.isArray(pat)) {
[pat, lookup] = [lookup, pat];
}
return __pick(lookup, pat);
};
const __pick = register('pick', function (lookup, pat) {
return _pick(lookup, pat, false).innerJoin();
});
/** * The same as `pick`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* For example, if you pick the fifth pattern of a list of three, you'll get the
* second one.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmod = register('pickmod', function (lookup, pat) {
return _pick(lookup, pat, true).innerJoin();
});
/** * pickF lets you use a pattern of numbers to pick which function to apply to another pattern.
* @param {Pattern} pat
* @param {Pattern} lookup a pattern of indices
* @param {function[]} funcs the array of functions from which to pull
* @returns {Pattern}
* @example
* s("bd [rim hh]").pickF("<0 1 2>", [rev,jux(rev),fast(2)])
* @example
* note("<c2 d2>(3,8)").s("square")
* .pickF("<0 2> 1", [jux(rev),fast(2),x=>x.lpf(800)])
*/
export const pickF = register('pickF', function (lookup, funcs, pat) {
return pat.apply(pick(lookup, funcs));
});
/** * The same as `pickF`, but if you pick a number greater than the size of the functions list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {Pattern} lookup a pattern of indices
* @param {function[]} funcs the array of functions from which to pull
* @returns {Pattern}
*/
export const pickmodF = register('pickmodF', function (lookup, funcs, pat) {
return pat.apply(pickmod(lookup, funcs));
});
/** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickOut = register('pickOut', function (lookup, pat) {
return _pick(lookup, pat, false).outerJoin();
});
/** * The same as `pickOut`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmodOut = register('pickmodOut', function (lookup, pat) {
return _pick(lookup, pat, true).outerJoin();
});
/** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickRestart = register('pickRestart', function (lookup, pat) {
return _pick(lookup, pat, false).restartJoin();
});
/** * The same as `pickRestart`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmodRestart = register('pickmodRestart', function (lookup, pat) {
return _pick(lookup, pat, true).restartJoin();
});
/** * Similar to `pick`, but the choosen pattern is reset when its index is triggered.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickReset = register('pickReset', function (lookup, pat) {
return _pick(lookup, pat, false).resetJoin();
});
/** * The same as `pickReset`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmodReset = register('pickmodReset', function (lookup, pat) {
return _pick(lookup, pat, true).resetJoin();
});
/**
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
* Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern.
* @name inhabit
* @synonyms pickSqueeze
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
* @example
* "<a b [a,b]>".inhabit({a: s("bd(3,8)"),
b: s("cp sd")
})
* @example
* s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4)
*/
export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], function (lookup, pat) {
return _pick(lookup, pat, false).squeezeJoin();
});
/** * The same as `inhabit`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* For example, if you pick the fifth pattern of a list of three, you'll get the
* second one.
* @name inhabitmod
* @synonyms pickmodSqueeze
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSqueeze'], function (lookup, pat) {
return _pick(lookup, pat, true).squeezeJoin();
});
/**
* Pick from the list of values (or patterns of values) via the index using the given
* pattern of integers. The selected pattern will be compressed to fit the duration of the selecting event
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
* @example
* note(squeeze("<0@2 [1!2] 2>", ["g a", "f g f g" , "g a c d"]))
*/
export const squeeze = (pat, xs) => {
xs = xs.map(reify);
if (xs.length == 0) {
return silence;
}
return pat
.fmap((i) => {
const key = _mod(Math.round(i), xs.length);
return xs[key];
})
.squeezeJoin();
};
export const __chooseWith = (pat, xs) => {
xs = xs.map(reify);
if (xs.length == 0) {
@@ -433,30 +486,19 @@ export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs);
export const randcat = chooseCycles;
const _wchooseWith = function (pat, ...pairs) {
// A list of patterns of values
const values = pairs.map((pair) => reify(pair[0]));
// A list of weight patterns
const weights = [];
let total = pure(0);
let accum = 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]);
// accumulate our list of weight patterns
weights.push(total);
accum += pair[1];
weights.push(accum);
}
// a pattern of lists of weights
const weightspat = sequenceP(weights);
// Takes a number from 0-1, returns a pattern of patterns of values
const total = accum;
const match = function (r) {
const findpat = total.mul(r);
return weightspat.fmap((weights) => (find) => values[weights.findIndex((x) => x > find, weights)]).appLeft(findpat);
const find = r * total;
return values[weights.findIndex((x) => x > find, weights)];
};
// This returns a pattern of patterns.. The innerJoin is in wchooseCycles
return pat.bind(match);
return pat.fmap(match);
};
const wchooseWith = (...args) => _wchooseWith(...args).outerJoin();
@@ -478,9 +520,6 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
* wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8)
* @example
* wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s()
* @example
* // The probability can itself be a pattern
* wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s()
*/
export const wchooseCycles = (...pairs) => _wchooseWith(rand.segment(1), ...pairs).innerJoin();
@@ -506,11 +545,8 @@ export const perlinWith = (pat) => {
*/
export const perlin = perlinWith(time.fmap((v) => Number(v)));
export const degradeByWith = register(
'degradeByWith',
(withPat, x, pat) => pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
true,
true,
export const degradeByWith = register('degradeByWith', (withPat, x, pat) =>
pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
);
/**
@@ -526,18 +562,10 @@ export const degradeByWith = register(
* s("hh*8").degradeBy(0.2)
* @example
* s("[hh?0.2]*8")
* @example
* //beat generator
* s("bd").segment(16).degradeBy(.5).ribbon(16,1)
*/
export const degradeBy = register(
'degradeBy',
function (x, pat) {
return pat._degradeByWith(rand, x);
},
true,
true,
);
export const degradeBy = register('degradeBy', function (x, pat) {
return pat._degradeByWith(rand, x);
});
/**
*
@@ -551,7 +579,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));
/**
* Inverse of `degradeBy`: Randomly removes events from the pattern by a given amount.
@@ -571,17 +599,12 @@ export const degrade = register('degrade', (pat) => pat._degradeBy(0.5), true, t
* x => x.undegradeBy(0.8).pan(1)
* )
*/
export const undegradeBy = register(
'undegradeBy',
function (x, pat) {
return pat._degradeByWith(
rand.fmap((r) => 1 - r),
x,
);
},
true,
true,
);
export const undegradeBy = register('undegradeBy', function (x, pat) {
return pat._degradeByWith(
rand.fmap((r) => 1 - r),
x,
);
});
/**
* Inverse of `degrade`: Randomly removes 50% of events from the pattern. Shorthand for `.undegradeBy(0.5)`
@@ -598,7 +621,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));
/**
*
@@ -757,48 +780,3 @@ export const never = register('never', function (_, pat) {
export const always = register('always', function (func, pat) {
return func(pat);
});
//keyname: string | Array<string>
//keyname reference: https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
export function _keyDown(keyname) {
if (Array.isArray(keyname) === false) {
keyname = [keyname];
}
const keyState = getCurrentKeyboardState();
return keyname.every((x) => {
const keyName = keyAlias.get(x) ?? x;
return keyState[keyName];
});
}
/**
*
* Do something on a keypress, or array of keypresses
* [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values)
*
* @name whenKey
* @memberof Pattern
* @returns Pattern
* @example
* s("bd(5,8)").whenKey("Control:j", x => x.segment(16).color("red")).whenKey("Control:i", x => x.fast(2).color("blue"))
*/
export const whenKey = register('whenKey', function (input, func, pat) {
return pat.when(_keyDown(input), func);
});
/**
*
* returns true when a key or array of keys is held
* [Key name reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values)
*
* @name keyDown
* @memberof Pattern
* @returns Pattern
* @example
* keyDown("Control:j").pick([s("bd(5,8)"), s("cp(3,8)")])
*/
export const keyDown = register('keyDown', function (pat) {
return pat.fmap(_keyDown);
});
+7 -11
View File
@@ -4,7 +4,7 @@ Copyright (C) 2023 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { s, pan, getControlName } from '../controls.mjs';
import { s, pan } from '../controls.mjs';
import { mini } from '../../mini/mini.mjs';
import { describe, it, expect } from 'vitest';
import Fraction from '../fraction.mjs';
@@ -30,17 +30,13 @@ describe('controls', () => {
expect(s(mini('bd').pan(1)).firstCycleValues).toEqual([{ s: 'bd', pan: 1 }]);
expect(s(mini('bd:1').pan(1)).firstCycleValues).toEqual([{ s: 'bd', n: 1, pan: 1 }]);
});
it('preserves step count of the left pattern', () => {
expect(s(mini('bd cp mt').pan(mini('1 2 3 4')))._steps).toEqual(Fraction(3));
it('preserves tactus of the left pattern', () => {
expect(s(mini('bd cp mt').pan(mini('1 2 3 4'))).tactus).toEqual(Fraction(3));
});
it('preserves step count of the right pattern for .out', () => {
expect(s(mini('bd cp mt').set.out(pan(mini('1 2 3 4'))))._steps).toEqual(Fraction(4));
it('preserves tactus of the right pattern for .out', () => {
expect(s(mini('bd cp mt').set.out(pan(mini('1 2 3 4')))).tactus).toEqual(Fraction(4));
});
it('combines step count of the pattern for .mix as lcm', () => {
expect(s(mini('bd cp mt').set.mix(pan(mini('1 2 3 4'))))._steps).toEqual(Fraction(12));
});
it('finds control name by alias', () => {
expect(getControlName('lpf')).toEqual('cutoff');
expect(getControlName('cutoff')).toEqual('cutoff');
it('combines tactus of the pattern for .mix as lcm', () => {
expect(s(mini('bd cp mt').set.mix(pan(mini('1 2 3 4')))).tactus).toEqual(Fraction(12));
});
});
+76 -134
View File
@@ -21,7 +21,8 @@ import {
cat,
sequence,
palindrome,
polymeter,
s_polymeter,
s_polymeterSteps,
polyrhythm,
silence,
fast,
@@ -45,13 +46,12 @@ import {
rev,
time,
run,
binaryN,
pick,
stackLeft,
stackRight,
stackCentre,
stepcat,
sometimes,
s_cat,
calculateTactus,
} from '../index.mjs';
import { steady } from '../signal.mjs';
@@ -608,12 +608,19 @@ describe('Pattern', () => {
);
});
});
describe('polymeter()', () => {
describe('s_polymeter()', () => {
it('Can layer up cycles, stepwise, with lists', () => {
expect(polymeter(['a', 'b', 'c'], ['d', 'e']).fast(2).firstCycle()).toStrictEqual(
expect(s_polymeterSteps(3, ['d', 'e']).firstCycle()).toStrictEqual(
fastcat(pure('d'), pure('e'), pure('d')).firstCycle(),
);
expect(s_polymeter(['a', 'b', 'c'], ['d', 'e']).fast(2).firstCycle()).toStrictEqual(
stack(sequence('a', 'b', 'c', 'a', 'b', 'c'), sequence('d', 'e', 'd', 'e', 'd', 'e')).firstCycle(),
);
});
it('Can layer up cycles, stepwise, with weighted patterns', () => {
sameFirst(s_polymeterSteps(3, sequence('a', 'b')).fast(2), sequence('a', 'b', 'a', 'b', 'a', 'b'));
});
});
describe('firstOf()', () => {
@@ -728,15 +735,21 @@ describe('Pattern', () => {
describe('signal()', () => {
it('Can make saw/saw2', () => {
expect(saw.struct(true, true, true, true).firstCycle()).toStrictEqual(
sequence(0, 1 / 4, 1 / 2, 3 / 4).firstCycle(),
sequence(1 / 8, 3 / 8, 5 / 8, 7 / 8).firstCycle(),
);
expect(saw2.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(-1, -0.5, 0, 0.5).firstCycle());
expect(saw2.struct(true, true, true, true).firstCycle()).toStrictEqual(
sequence(-3 / 4, -1 / 4, 1 / 4, 3 / 4).firstCycle(),
);
});
it('Can make isaw/isaw2', () => {
expect(isaw.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(1, 0.75, 0.5, 0.25).firstCycle());
expect(isaw.struct(true, true, true, true).firstCycle()).toStrictEqual(
sequence(7 / 8, 5 / 8, 3 / 8, 1 / 8).firstCycle(),
);
expect(isaw2.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(1, 0.5, 0, -0.5).firstCycle());
expect(isaw2.struct(true, true, true, true).firstCycle()).toStrictEqual(
sequence(3 / 4, 1 / 4, -1 / 4, -3 / 4).firstCycle(),
);
});
});
describe('_setContext()', () => {
@@ -874,7 +887,7 @@ describe('Pattern', () => {
.squeezeJoin()
.queryArc(3, 4)
.map((x) => x.value),
).toStrictEqual([Fraction(3)]);
).toStrictEqual([Fraction(3.5)]);
});
});
describe('ply', () => {
@@ -924,9 +937,6 @@ describe('Pattern', () => {
.firstCycle(),
);
});
it('Can chop chops', () => {
expect(pure({ s: 'bev' }).chop(2).chop(2).firstCycle()).toStrictEqual(pure({ s: 'bev' }).chop(4).firstCycle());
});
});
describe('range', () => {
it('Can be patterned', () => {
@@ -945,18 +955,6 @@ describe('Pattern', () => {
expect(run(4).firstCycle()).toStrictEqual(sequence(0, 1, 2, 3).firstCycle());
});
});
describe('binaryN', () => {
it('Can make a binary pattern from a decimal', () => {
expect(binaryN(55532).firstCycle()).toStrictEqual(
sequence(1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0).firstCycle(),
);
});
it('Can make a binary pattern from patterned inputs', () => {
expect(binaryN(pure(0x1337), pure(14)).firstCycle()).toStrictEqual(
sequence(0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1).firstCycle(),
);
});
});
describe('ribbon', () => {
it('Can ribbon', () => {
expect(cat(0, 1, 2, 3, 4, 5, 6, 7).ribbon(2, 4).fast(4).firstCycle()).toStrictEqual(
@@ -1126,145 +1124,89 @@ describe('Pattern', () => {
);
});
});
describe('_steps', () => {
describe('tactus', () => {
it('Is correctly preserved/calculated through transformations', () => {
expect(sequence(0, 1, 2, 3).linger(4)._steps).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).iter(4)._steps).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).fast(4)._steps).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).hurry(4)._steps).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).rev()._steps).toStrictEqual(Fraction(4));
expect(sequence(1).segment(10)._steps).toStrictEqual(Fraction(10));
expect(sequence(1, 0, 1).invert()._steps).toStrictEqual(Fraction(3));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).chop(4)._steps).toStrictEqual(Fraction(8));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).striate(4)._steps).toStrictEqual(Fraction(8));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).slice(4, sequence(0, 1, 2, 3))._steps).toStrictEqual(
expect(sequence(0, 1, 2, 3).linger(4).tactus).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).iter(4).tactus).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).fast(4).tactus).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).hurry(4).tactus).toStrictEqual(Fraction(4));
expect(sequence(0, 1, 2, 3).rev().tactus).toStrictEqual(Fraction(4));
expect(sequence(1).segment(10).tactus).toStrictEqual(Fraction(10));
expect(sequence(1, 0, 1).invert().tactus).toStrictEqual(Fraction(3));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).chop(4).tactus).toStrictEqual(Fraction(8));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).striate(4).tactus).toStrictEqual(Fraction(8));
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).slice(4, sequence(0, 1, 2, 3)).tactus).toStrictEqual(
Fraction(4),
);
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).splice(4, sequence(0, 1, 2, 3))._steps).toStrictEqual(
expect(sequence({ s: 'bev' }, { s: 'amenbreak' }).splice(4, sequence(0, 1, 2, 3)).tactus).toStrictEqual(
Fraction(4),
);
expect(sequence({ n: 0 }, { n: 1 }, { n: 2 }).chop(4)._steps).toStrictEqual(Fraction(12));
expect(sequence({ n: 0 }, { n: 1 }, { n: 2 }).chop(4).tactus).toStrictEqual(Fraction(12));
expect(
pure((x) => x + 1)
.setSteps(3)
.appBoth(pure(1).setSteps(2))._steps,
.setTactus(3)
.appBoth(pure(1).setTactus(2)).tactus,
).toStrictEqual(Fraction(6));
expect(
pure((x) => x + 1)
.setSteps(undefined)
.appBoth(pure(1).setSteps(2))._steps,
.setTactus(undefined)
.appBoth(pure(1).setTactus(2)).tactus,
).toStrictEqual(Fraction(2));
expect(
pure((x) => x + 1)
.setSteps(3)
.appBoth(pure(1).setSteps(undefined))._steps,
.setTactus(3)
.appBoth(pure(1).setTactus(undefined)).tactus,
).toStrictEqual(Fraction(3));
expect(stack(fastcat(0, 1, 2), fastcat(3, 4))._steps).toStrictEqual(Fraction(6));
expect(stack(fastcat(0, 1, 2), fastcat(3, 4).setSteps(undefined))._steps).toStrictEqual(Fraction(3));
expect(stackLeft(fastcat(0, 1, 2, 3), fastcat(3, 4))._steps).toStrictEqual(Fraction(4));
expect(stackRight(fastcat(0, 1, 2), fastcat(3, 4))._steps).toStrictEqual(Fraction(3));
expect(stack(fastcat(0, 1, 2), fastcat(3, 4)).tactus).toStrictEqual(Fraction(6));
expect(stack(fastcat(0, 1, 2), fastcat(3, 4).setTactus(undefined)).tactus).toStrictEqual(Fraction(3));
expect(stackLeft(fastcat(0, 1, 2, 3), fastcat(3, 4)).tactus).toStrictEqual(Fraction(4));
expect(stackRight(fastcat(0, 1, 2), fastcat(3, 4)).tactus).toStrictEqual(Fraction(3));
// maybe this should double when they are either all even or all odd
expect(stackCentre(fastcat(0, 1, 2), fastcat(3, 4))._steps).toStrictEqual(Fraction(3));
expect(fastcat(0, 1).ply(3)._steps).toStrictEqual(Fraction(6));
expect(fastcat(0, 1).setSteps(undefined).ply(3)._steps).toStrictEqual(undefined);
expect(fastcat(0, 1).fast(3)._steps).toStrictEqual(Fraction(2));
expect(fastcat(0, 1).setSteps(undefined).fast(3)._steps).toStrictEqual(undefined);
expect(stackCentre(fastcat(0, 1, 2), fastcat(3, 4)).tactus).toStrictEqual(Fraction(3));
expect(fastcat(0, 1).ply(3).tactus).toStrictEqual(Fraction(6));
expect(fastcat(0, 1).setTactus(undefined).ply(3).tactus).toStrictEqual(undefined);
expect(fastcat(0, 1).fast(3).tactus).toStrictEqual(Fraction(2));
expect(fastcat(0, 1).setTactus(undefined).fast(3).tactus).toStrictEqual(undefined);
});
});
describe('stepcat', () => {
describe('s_cat', () => {
it('can cat', () => {
expect(sameFirst(stepcat(fastcat(0, 1, 2, 3), fastcat(4, 5)), fastcat(0, 1, 2, 3, 4, 5)));
expect(sameFirst(stepcat(pure(1), pure(2), pure(3)), fastcat(1, 2, 3)));
expect(sameFirst(s_cat(fastcat(0, 1, 2, 3), fastcat(4, 5)), fastcat(0, 1, 2, 3, 4, 5)));
expect(sameFirst(s_cat(pure(1), pure(2), pure(3)), fastcat(1, 2, 3)));
});
it('calculates undefined steps as the average', () => {
expect(sameFirst(stepcat(pure(1), pure(2), pure(3).setSteps(undefined)), fastcat(1, 2, 3)));
it('calculates undefined tactuses as the average', () => {
expect(sameFirst(s_cat(pure(1), pure(2), pure(3).setTactus(undefined)), fastcat(1, 2, 3)));
});
});
describe('shrink', () => {
it('can shrink', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).shrink(1), sequence(0, 1, 2, 3, 4, 1, 2, 3, 4, 2, 3, 4, 3, 4, 4)));
describe('s_taper', () => {
it('can taper', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_taper(1, 5), sequence(0, 1, 2, 3, 4, 0, 1, 2, 3, 0, 1, 2, 0, 1, 0)));
});
it('can shrink backwards', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).shrink(-1), sequence(0, 1, 2, 3, 4, 0, 1, 2, 3, 0, 1, 2, 0, 1, 0)));
it('can taper backwards', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_taper(-1, 5), sequence(0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4)));
});
});
describe('grow', () => {
it('can grow', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).grow(1), sequence(0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4)));
describe('s_add and s_sub', () => {
it('can add from the left', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_add(2), sequence(0, 1)));
});
it('can grow backwards', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).grow(-1), sequence(4, 3, 4, 2, 3, 4, 1, 2, 3, 4, 0, 1, 2, 3, 4)));
it('can sub to the left', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_sub(2), sequence(0, 1, 2)));
});
});
describe('take and drop', () => {
it('can take from the left', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).take(2), sequence(0, 1)));
it('can add from the right', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_add(-2), sequence(3, 4)));
});
it('can drop from the left', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).drop(2), sequence(2, 3, 4)));
it('can sub to the right', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).s_sub(-2), sequence(2, 3, 4)));
});
it('can take from the right', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).take(-2), sequence(3, 4)));
it('can subtract nothing', () => {
expect(sameFirst(pure('a').s_sub(0), pure('a')));
});
it('can drop from the right', () => {
expect(sameFirst(sequence(0, 1, 2, 3, 4).drop(-2), sequence(0, 1, 2)));
});
it('can drop nothing', () => {
expect(sameFirst(pure('a').drop(0), pure('a')));
});
it('can drop nothing, repeatedly', () => {
expect(sameFirst(pure('a').drop(0, 0), fastcat('a', 'a')));
it('can subtract nothing, repeatedly', () => {
expect(sameFirst(pure('a').s_sub(0, 0), fastcat('a', 'a')));
for (var i = 0; i < 100; ++i) {
expect(sameFirst(pure('a').drop(...Array(i).fill(0)), fastcat(...Array(i).fill('a'))));
expect(sameFirst(pure('a').s_sub(...Array(i).fill(0)), fastcat(...Array(i).fill('a'))));
}
});
});
describe('expand', () => {
it('can expand four things in half', () => {
expect(
sameFirst(sequence(0, 1, 2, 3).expand(1, 0.5), stepcat(sequence(0, 1, 2, 3), sequence(0, 1, 2, 3).expand(0.5))),
);
});
it('can expand five things in half', () => {
expect(
sameFirst(
sequence(0, 1, 2, 3, 4).expand(1, 0.5),
stepcat(sequence(0, 1, 2, 3, 4), sequence(0, 1, 2, 3, 4).expand(0.5)),
),
);
});
});
describe('stepJoin', () => {
it('can join a pattern with steps of 2', () => {
expect(
sameFirst(
sequence(pure(pure('a')), pure(pure('b').setSteps(2))).stepJoin(),
stepcat(pure('a'), pure('b').setSteps(2)),
),
);
});
it('can join a pattern with steps of 0.5', () => {
expect(
sameFirst(
sequence(pure(pure('a')), pure(pure('b').setSteps(0.5))).stepJoin(),
stepcat(pure('a'), pure('b').setSteps(0.5)),
),
);
});
});
describe('loopAt', () => {
it('maintains steps', () => {
expect(s('bev').chop(8).loopAt(2)._steps).toStrictEqual(Fraction(4));
});
});
describe('bite', () => {
it('works with uneven patterns', () => {
sameFirst(
fastcat(slowcat('a', 'b', 'c', 'd', 'e'), slowcat(1, 2, 3, 4, 5))
.bite(2, stepcat(pure(0), pure(1).expand(2)))
.fast(5),
stepcat(slowcat('a', 'b', 'c', 'd', 'e'), slowcat(1, 2, 3, 4, 5).expand(2)).fast(5),
);
});
});
});
-105
View File
@@ -91,9 +91,6 @@ export const midi2note = (n) => {
// modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does)
export const _mod = (n, m) => ((n % m) + m) % m;
// average numbers in an array
export const averageArray = (arr) => arr.reduce((a, b) => a + b) / arr.length;
export function nanFallback(value, fallback = 0) {
if (isNaN(Number(value))) {
logger(`"${value}" is not a number, falling back to ${fallback}`, 'warning');
@@ -363,108 +360,6 @@ export function objectMap(obj, fn) {
}
return Object.fromEntries(Object.entries(obj).map(([k, v], i) => [k, fn(v, k, i)]));
}
export function cycleToSeconds(cycle, cps) {
return cycle / cps;
}
// utility for averaging two clocks together to account for drift
export class ClockCollator {
constructor({
getTargetClockTime = getUnixTimeSeconds,
weight = 16,
offsetDelta = 0.005,
checkAfterTime = 2,
resetAfterTime = 8,
}) {
this.offsetTime;
this.timeAtPrevOffsetSample;
this.prevOffsetTimes = [];
this.getTargetClockTime = getTargetClockTime;
this.weight = weight;
this.offsetDelta = offsetDelta;
this.checkAfterTime = checkAfterTime;
this.resetAfterTime = resetAfterTime;
this.reset = () => {
this.prevOffsetTimes = [];
this.offsetTime = null;
this.timeAtPrevOffsetSample = null;
};
}
calculateOffset(currentTime) {
const targetClockTime = this.getTargetClockTime();
const diffBetweenTimeSamples = targetClockTime - this.timeAtPrevOffsetSample;
const newOffsetTime = targetClockTime - currentTime;
// recalcuate the diff from scratch if the clock has been paused for some time.
if (diffBetweenTimeSamples > this.resetAfterTime) {
this.reset();
}
if (this.offsetTime == null) {
this.offsetTime = newOffsetTime;
}
this.prevOffsetTimes.push(newOffsetTime);
if (this.prevOffsetTimes.length > this.weight) {
this.prevOffsetTimes.shift();
}
// after X time has passed, the average of the previous weight offset times is calculated and used as a stable reference
// for calculating the timestamp
if (this.timeAtPrevOffsetSample == null || diffBetweenTimeSamples > this.checkAfterTime) {
this.timeAtPrevOffsetSample = targetClockTime;
const rollingOffsetTime = averageArray(this.prevOffsetTimes);
//when the clock offsets surpass the delta, set the new reference time
if (Math.abs(rollingOffsetTime - this.offsetTime) > this.offsetDelta) {
this.offsetTime = rollingOffsetTime;
}
}
return this.offsetTime;
}
calculateTimestamp(currentTime, targetTime) {
return this.calculateOffset(currentTime) + targetTime;
}
}
export function getPerformanceTimeSeconds() {
return performance.now() * 0.001;
}
function getUnixTimeSeconds() {
return Date.now() * 0.001;
}
export const keyAlias = new Map([
['control', 'Control'],
['ctrl', 'Control'],
['alt', 'Alt'],
['shift', 'Shift'],
['down', 'ArrowDown'],
['up', 'ArrowUp'],
['left', 'ArrowLeft'],
['right', 'ArrowRight'],
]);
let keyState;
export function getCurrentKeyboardState() {
if (keyState == null) {
if (typeof window === 'undefined') {
return;
}
keyState = {};
// Listen for the keydown event to mark the key as pressed
window.addEventListener('keydown', (event) => {
keyState[event.key] = true; // Mark the key as pressed
});
// Listen for the keyup event to mark the key as released
window.addEventListener('keyup', (event) => {
keyState[event.key] = false; // Mark the key as released
});
}
return { ...keyState }; // Return a shallow copy of the key state object
}
// Floating point versions, see Fraction for rational versions
// // greatest common divisor
-1
View File
@@ -4,7 +4,6 @@ import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
base: './',
plugins: [],
build: {
lib: {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/csound",
"version": "1.2.0",
"version": "1.1.0",
"description": "csound bindings for strudel",
"main": "index.mjs",
"type": "module",
@@ -37,6 +37,6 @@
"@strudel/webaudio": "workspace:*"
},
"devDependencies": {
"vite": "^6.0.11"
"vite": "^5.0.10"
}
}
+39 -32
View File
@@ -1,36 +1,43 @@
import { Pattern, ClockCollator } from '@strudel/core';
import { parseControlsFromHap } from 'node_modules/@strudel/osc/osc.mjs';
import { parseNumeral, Pattern, getEventOffsetMs } from '@strudel/core';
import { Invoke } from './utils.mjs';
const collator = new ClockCollator({});
export async function oscTriggerTauri(t_deprecate, hap, currentTime, cps = 1, targetTime) {
const controls = parseControlsFromHap(hap, cps);
const params = [];
const timestamp = collator.calculateTimestamp(currentTime, targetTime);
Object.keys(controls).forEach((key) => {
const val = controls[key];
const value = typeof val === 'number' ? val.toString() : val;
if (value == null) {
return;
}
params.push({
name: key,
value,
valueisnumber: typeof val === 'number',
});
});
if (params.length === 0) {
return;
}
const message = { target: '/dirt/play', timestamp, params };
setTimeout(() => {
Invoke('sendosc', { messagesfromjs: [message] });
});
}
Pattern.prototype.osc = function () {
return this.onTrigger(oscTriggerTauri);
return this.onTrigger(async (time, hap, currentTime, cps = 1, targetTime) => {
hap.ensureObjectValue();
const cycle = hap.wholeOrPart().begin.valueOf();
const delta = hap.duration.valueOf();
const controls = Object.assign({}, { cps, cycle, delta }, hap.value);
// make sure n and note are numbers
controls.n && (controls.n = parseNumeral(controls.n));
controls.note && (controls.note = parseNumeral(controls.note));
const params = [];
const timestamp = Math.round(Date.now() + getEventOffsetMs(targetTime, currentTime));
Object.keys(controls).forEach((key) => {
const val = controls[key];
const value = typeof val === 'number' ? val.toString() : val;
if (value == null) {
return;
}
params.push({
name: key,
value,
valueisnumber: typeof val === 'number',
});
});
const messagesfromjs = [];
if (params.length) {
messagesfromjs.push({ target: '/dirt/play', timestamp, params });
}
if (messagesfromjs.length) {
setTimeout(() => {
Invoke('sendosc', { messagesfromjs });
});
}
});
};
+28 -28
View File
@@ -1,29 +1,29 @@
{
"name": "@strudel/desktopbridge",
"version": "0.1.0",
"private": true,
"description": "tools/shims for communicating between the JS and Tauri (Rust) sides of the Studel desktop app",
"main": "index.mjs",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"tidalcycles",
"strudel",
"pattern",
"livecoding",
"algorave"
],
"author": "Jade Rowland <jaderowlanddev@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"dependencies": {
"@strudel/core": "workspace:*",
"@tauri-apps/api": "^2.2.0"
},
"homepage": "https://github.com/tidalcycles/strudel#readme"
}
"name": "@strudel/desktopbridge",
"version": "0.1.0",
"private": true,
"description": "tools/shims for communicating between the JS and Tauri (Rust) sides of the Studel desktop app",
"main": "index.mjs",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"tidalcycles",
"strudel",
"pattern",
"livecoding",
"algorave"
],
"author": "Jade Rowland <jaderowlanddev@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"dependencies": {
"@strudel/core": "workspace:*",
"@tauri-apps/api": "^1.5.3"
},
"homepage": "https://github.com/tidalcycles/strudel#readme"
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { invoke } from '@tauri-apps/api/core';
import { invoke } from '@tauri-apps/api/tauri';
export const Invoke = invoke;
export const isTauri = () => window.__TAURI_IPC__ != null;
+1 -1
View File
@@ -26,7 +26,7 @@ export const getDrawContext = (id = 'test-canvas', options) => {
}, 200);
});
}
return canvas.getContext(contextType, { willReadFrequently: true });
return canvas.getContext(contextType);
};
let animationFrames = {};
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/draw",
"version": "1.2.0",
"version": "1.1.0",
"description": "Helpers for drawing with Strudel",
"main": "index.mjs",
"type": "module",
@@ -32,6 +32,6 @@
"@strudel/core": "workspace:*"
},
"devDependencies": {
"vite": "^6.0.11"
"vite": "^5.0.10"
}
}
+34 -44
View File
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern, noteToMidi, freqToMidi, isPattern } from '@strudel/core';
import { Pattern, noteToMidi, freqToMidi } from '@strudel/core';
import { getTheme, getDrawContext } from './draw.mjs';
const scale = (normalized, min, max) => normalized * (max - min) + min;
@@ -36,9 +36,35 @@ const getValue = (e) => {
return value;
};
Pattern.prototype.pianoroll = function (options = {}) {
let { cycles = 4, playhead = 0.5, overscan = 0, hideNegative = false, ctx = getDrawContext(), id = 1 } = options;
let from = -cycles * playhead;
let to = cycles * (1 - playhead);
const inFrame = (hap, t) => (!hideNegative || hap.whole.begin >= 0) && hap.isWithinTime(t + from, t + to);
this.draw(
(haps, time) => {
pianoroll({
...options,
time,
ctx,
haps: haps.filter((hap) => inFrame(hap, time)),
});
},
{
lookbehind: from - overscan,
lookahead: to + overscan,
id,
},
);
return this;
};
// this function allows drawing a pianoroll without ties to Pattern.prototype
// it will probably replace the above in the future
/**
* Visualises a pattern as a scrolling 'pianoroll', displayed in the background of the editor. To show a pianoroll for all running patterns, use `all(pianoroll)`. To have a pianoroll appear below
* a pattern instead, prefix with `_`, e.g.: `sound("bd sd")._pianoroll()`.
* Displays a midi-style piano roll
*
* @name pianoroll
* @synonyms punchcard
@@ -67,51 +93,15 @@ const getValue = (e) => {
* @param {integer} minMidi minimum note value to display on the value axis - defaults to 10
* @param {integer} maxMidi maximum note value to display on the value axis - defaults to 90
* @param {boolean} autorange automatically calculate the minMidi and maxMidi parameters - 0 by default
* @see _pianoroll
*
* @example
* note("c2 a2 eb2")
* .euclid(5,8)
* .s('sawtooth')
* .lpenv(4).lpf(300)
* .pianoroll({ labels: 1 })
* ._pianoroll({ labels: 1 })
*/
Pattern.prototype.pianoroll = function (options = {}) {
let { cycles = 4, playhead = 0.5, overscan = 0, hideNegative = false, ctx = getDrawContext(), id = 1 } = options;
let from = -cycles * playhead;
let to = cycles * (1 - playhead);
const inFrame = (hap, t) => (!hideNegative || hap.whole.begin >= 0) && hap.isWithinTime(t + from, t + to);
this.draw(
(haps, time) => {
__pianoroll({
...options,
time,
ctx,
haps: haps.filter((hap) => inFrame(hap, time)),
});
},
{
lookbehind: from - overscan,
lookahead: to + overscan,
id,
},
);
return this;
};
export function pianoroll(arg) {
if (isPattern(arg)) {
// Single argument as a pattern
// (to support `all(pianoroll)`)
return arg.pianoroll();
}
// Single argument with option - return function to get the pattern
// (to support `all(pianoroll(options))`)
return (pat) => pat.pianoroll(arg);
}
export function __pianoroll({
export function pianoroll({
time,
haps,
cycles = 4,
@@ -288,7 +278,7 @@ export function getDrawOptions(drawTime, options = {}) {
export const getPunchcardPainter =
(options = {}) =>
(ctx, time, haps, drawTime) =>
__pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, options) });
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, options) });
Pattern.prototype.punchcard = function (options) {
return this.onPaint(getPunchcardPainter(options));
@@ -312,5 +302,5 @@ Pattern.prototype.wordfall = function (options) {
export function drawPianoroll(options) {
const { drawTime, ...rest } = options;
__pianoroll({ ...getDrawOptions(drawTime), ...rest });
pianoroll({ ...getDrawOptions(drawTime), ...rest });
}
-93
View File
@@ -1,93 +0,0 @@
# @strudel/gamepad
This package adds gamepad input functionality to strudel Patterns.
## Install
```sh
npm i @strudel/gamepad --save
```
## Usage
```javascript
import { gamepad } from '@strudel/gamepad';
// Initialize gamepad (optional index parameter, defaults to 0)
const pad = gamepad(0);
// Use gamepad inputs in patterns
const pattern = sequence([
// Button inputs
pad.a, // A button value (0-1)
pad.tglA, // A button toggle (0 or 1)
// Analog stick inputs
pad.x1, // Left stick X (0-1)
pad.x1_2, // Left stick X (-1 to 1)
]);
```
## Available Controls
### Buttons
- Face Buttons
- `a`, `b`, `x`, `y` (or uppercase `A`, `B`, `X`, `Y`)
- Toggle versions: `tglA`, `tglB`, `tglX`, `tglY`
- Shoulder Buttons
- `lb`, `rb`, `lt`, `rt` (or uppercase `LB`, `RB`, `LT`, `RT`)
- Toggle versions: `tglLB`, `tglRB`, `tglLT`, `tglRT`
- D-Pad
- `up`, `down`, `left`, `right` (or `u`, `d`, `l`, `r` or uppercase)
- Toggle versions: `tglUp`, `tglDown`, `tglLeft`, `tglRight`(or `tglU`, `tglD`, `tglL`, `tglR`)
### Analog Sticks
- Left Stick
- `x1`, `y1` (0 to 1 range)
- `x1_2`, `y1_2` (-1 to 1 range)
- Right Stick
- `x2`, `y2` (0 to 1 range)
- `x2_2`, `y2_2` (-1 to 1 range)
## Examples
```javascript
// Use button values to control amplitude
$: sequence([
s("bd").gain(pad.X), // X button controls gain
s("[hh oh]").gain(pad.tglY), // Y button toggles gain
]);
// Use analog stick for continuous control
$: note("c4*4".add(pad.y1_2.range(-24,24))) // Left stick Y controls pitch shift
.pan(pad.x1_2); // Left stick X controls panning
// Use toggle buttons to switch patterns on/off
// Define button sequences
const HADOKEN = [
'd', // Down
'r', // Right
'a', // A
];
const KONAMI = 'uuddlrlrba' //Konami Code ↑↑↓↓←→←→BA
// Add these lines to enable buttons(but why?)
$:pad.D.segment(16).gain(0)
$:pad.R.segment(16).gain(0)
$:pad.A.segment(16).gain(0)
// Check button sequence (returns 1 when detected, 0 when not within last 1 second)
$: sound("hadoken").gain(pad.checkSequence(HADOKEN))
```
## Multiple Gamepads
You can connect multiple gamepads by specifying the gamepad index:
```javascript
const pad1 = gamepad(0); // First gamepad
const pad2 = gamepad(1); // Second gamepad
```
-117
View File
@@ -1,117 +0,0 @@
import { MiniRepl } from '../../../website/src/docs/MiniRepl';
# Gamepad
The Gamepad module allows you to integrate gamepad input functionality into your musical patterns. This can be particularly useful for live performances or interactive installations where you want to manipulate sounds using a game controller.
## Getting Started
Initialize a gamepad by calling the gamepad() function with an optional index parameter.
<MiniRepl
client:idle
tune={`// Initialize gamepad (optional index parameter, defaults to 0)
const gp = gamepad(0)
note("c a f e").mask(gp.a)`}
/>
## Available Controls
The gamepad module provides access to buttons and analog sticks as normalized signals (0-1) that can modulate your patterns.
### Buttons
| Type | Controls |
| ---------------- | ---------------------------------------------------------------------------------------------- |
| Face Buttons | `a`, `b`, `x`, `y` (or uppercase `A`, `B`, `X`, `Y`) |
| | Toggle versions: `tglA`, `tglB`, `tglX`, `tglY` |
| Shoulder Buttons | `lb`, `rb`, `lt`, `rt` (or uppercase `LB`, `RB`, `LT`, `RT`) |
| | Toggle versions: `tglLB`, `tglRB`, `tglLT`, `tglRT` |
| D-Pad | `up`, `down`, `left`, `right` (or `u`, `d`, `l`, `r` or uppercase) |
| | Toggle versions: `tglUp`, `tglDown`, `tglLeft`, `tglRight` (or `tglU`, `tglD`, `tglL`, `tglR`) |
### Analog Sticks
| Stick | Controls |
| ----------- | ------------------------------ |
| Left Stick | `x1`, `y1` (0 to 1 range) |
| | `x1_2`, `y1_2` (-1 to 1 range) |
| Right Stick | `x2`, `y2` (0 to 1 range) |
| | `x2_2`, `y2_2` (-1 to 1 range) |
### Button Sequence
| Stick | Controls |
| --------------- | --------------------------------------- |
| Button Sequence | `btnSequence()`, `btnSeq()`, `btnseq()` |
## Using Gamepad Inputs
Once initialized, you can use various gamepad inputs in your patterns. Here are some examples:
### Button Inputs
You can use button inputs to control different aspects of your music, such as gain or triggering events.
<MiniRepl
client:idle
tune={`const gp = gamepad(0)
// Use button values to control amplitude
$: stack(
s("[[hh hh] oh hh oh]/2").mask(gp.tglX).bank("RolandTR909"), // X btn for HH
s("cr*1").mask(gp.Y).bank("RolandTR909"), // LB btn for CR
s("bd").mask(gp.tglA).bank("RolandTR909"), // A btn for BD
s("[ht - - mt - - lt - ]/2").mask(gp.tglB).bank("RolandTR909"), // B btn for Toms
s("sd*4").mask(gp.RB).bank("RolandTR909"), // RB btn for SD
).cpm(120)
`}
/>
### Analog Stick Inputs
Analog sticks can be used for continuous control, such as pitch shifting or panning.
<MiniRepl
client:idle
tune={`const gp = gamepad(0)
// Use analog stick for continuous control
$: note("c4 d3 a3 e3").sound("sawtooth")
.lpf(gp.x1.range(100,4000))
.lpq(gp.y1.range(5,30))
.decay(gp.y2.range(0.1,2))
.lpenv(gp.x2.range(-5,5))
.cpm(120)
`}
/>
### Button Sequences
You can define button sequences to trigger specific actions, like playing a sound when a sequence is detected.
<MiniRepl client:idle tune={`const gp = gamepad(0)
// Define button sequences
const HADOUKEN = [
'd', // Down
'r', // Right
'a', // A
]
const KONAMI = 'uuddlrlrba' //Konami Code ↑↑↓↓←→←→BA
// Check butto-n sequence (returns 1 while detected, 0 when not within last 1 second)
$: s("free_hadouken -").slow(2)
.mask(gp.btnSequence(HADOUKEN)).room(1).cpm(120)
// hadouken.wav by Syna-Max
//https://freesound.org/people/Syna-Max/sounds/67674/
samples({free_hadouken: 'https://cdn.freesound.org/previews/67/67674_111920-lq.mp3'})
`} />
## Multiple Gamepads
Strudel supports multiple gamepads. You can specify the gamepad index to connect to different devices.
<MiniRepl
client:idle
tune={`const pad1 = gamepad(0); // First gamepad
const pad2 = gamepad(1); // Second gamepad`}
/>
-246
View File
@@ -1,246 +0,0 @@
// @strudel/gamepad/index.mjs
import { signal } from '@strudel/core';
// Button mapping for Logitech Dual Action (STANDARD GAMEPAD Vendor: 046d Product: c216)
export const buttonMap = {
a: 0,
b: 1,
x: 2,
y: 3,
lb: 4,
rb: 5,
lt: 6,
rt: 7,
back: 8,
start: 9,
u: 12,
up: 12,
d: 13,
down: 13,
l: 14,
left: 14,
r: 15,
right: 15,
};
class ButtonSequenceDetector {
constructor(timeWindow = 1000) {
this.sequence = [];
this.timeWindow = timeWindow;
this.lastInputTime = 0;
this.buttonStates = Array(16).fill(0); // Track previous state of each button
// Button mapping for character inputs
}
addInput(buttonIndex, buttonValue) {
const currentTime = Date.now();
// Only add input on button press (rising edge)
if (buttonValue === 1 && this.buttonStates[buttonIndex] === 0) {
// Clear sequence if too much time has passed
if (currentTime - this.lastInputTime > this.timeWindow) {
this.sequence = [];
}
// Store the button name instead of index
const buttonName = Object.keys(buttonMap).find((key) => buttonMap[key] === buttonIndex) || buttonIndex.toString();
this.sequence.push({
input: buttonName,
timestamp: currentTime,
});
this.lastInputTime = currentTime;
//console.log(this.sequence);
// Keep only inputs within the time window
this.sequence = this.sequence.filter((entry) => currentTime - entry.timestamp <= this.timeWindow);
}
// Update button state
this.buttonStates[buttonIndex] = buttonValue;
}
checkSequence(targetSequence) {
if (!Array.isArray(targetSequence) && typeof targetSequence !== 'string') {
console.error('ButtonSequenceDetector: targetSequence must be an array or string');
return 0;
}
if (this.sequence.length < targetSequence.length) return 0;
// Convert string input to array if needed
const sequence =
typeof targetSequence === 'string'
? targetSequence.toLowerCase().split('')
: targetSequence.map((s) => s.toString().toLowerCase());
//console.log(this.sequence);
// Get the last n inputs where n is the target sequence length
const lastInputs = this.sequence.slice(-targetSequence.length).map((entry) => entry.input);
// Compare sequences
return lastInputs.every((input, index) => {
const target = sequence[index];
// Check if either the input matches directly or they refer to the same button in the map
return (
input === target ||
buttonMap[input] === buttonMap[target] ||
// Also check if the numerical index matches
buttonMap[input] === parseInt(target)
);
})
? 1
: 0;
}
}
class GamepadHandler {
constructor(index = 0) {
// Add index parameter
this._gamepads = {};
this._activeGamepad = index; // Use provided index
this._axes = [0, 0, 0, 0];
this._buttons = Array(16).fill(0);
this.setupEventListeners();
}
setupEventListeners() {
window.addEventListener('gamepadconnected', (e) => {
this._gamepads[e.gamepad.index] = e.gamepad;
if (!this._activeGamepad) {
this._activeGamepad = e.gamepad.index;
}
});
window.addEventListener('gamepaddisconnected', (e) => {
delete this._gamepads[e.gamepad.index];
if (this._activeGamepad === e.gamepad.index) {
this._activeGamepad = Object.keys(this._gamepads)[0] || null;
}
});
}
poll() {
if (this._activeGamepad !== null) {
const gamepad = navigator.getGamepads()[this._activeGamepad];
if (gamepad) {
// Update axes (normalized to 0-1 range)
this._axes = gamepad.axes.map((axis) => (axis + 1) / 2);
// Update buttons
this._buttons = gamepad.buttons.map((button) => button.value);
}
}
}
getAxes() {
return this._axes;
}
getButtons() {
return this._buttons;
}
}
// Module-level state store for toggle states
const gamepadStates = new Map();
export const gamepad = (index = 0) => {
const handler = new GamepadHandler(index);
const sequenceDetector = new ButtonSequenceDetector(2000);
// Base signal that polls gamepad state and handles sequence detection
const baseSignal = signal((t) => {
handler.poll();
const axes = handler.getAxes();
const buttons = handler.getButtons();
// Add all button inputs to sequence detector
buttons.forEach((value, i) => {
sequenceDetector.addInput(i, value);
});
return { axes, buttons, t };
});
// Create axes patterns
const axes = {
x1: baseSignal.fmap((state) => state.axes[0]),
y1: baseSignal.fmap((state) => state.axes[1]),
x2: baseSignal.fmap((state) => state.axes[2]),
y2: baseSignal.fmap((state) => state.axes[3]),
};
// Add bipolar versions
axes.x1_2 = axes.x1.toBipolar();
axes.y1_2 = axes.y1.toBipolar();
axes.x2_2 = axes.x2.toBipolar();
axes.y2_2 = axes.y2.toBipolar();
// Create button patterns
const buttons = Array(16)
.fill(null)
.map((_, i) => {
// Create unique key for this gamepad+button combination
const stateKey = `gamepad${index}_btn${i}`;
// Initialize toggle state if it doesn't exist
if (!gamepadStates.has(stateKey)) {
gamepadStates.set(stateKey, {
lastButtonState: 0,
toggleState: 0,
});
}
// Direct button value pattern (no longer needs to call addInput)
const btn = baseSignal.fmap((state) => state.buttons[i]);
// Button toggle pattern with persistent state
const toggle = baseSignal.fmap((state) => {
const currentState = state.buttons[i];
const buttonState = gamepadStates.get(stateKey);
if (currentState === 1 && buttonState.lastButtonState === 0) {
// Toggle the state on rising edge
buttonState.toggleState = buttonState.toggleState === 0 ? 1 : 0;
}
buttonState.lastButtonState = currentState;
return buttonState.toggleState;
});
return { value: btn, toggle };
});
// Create sequence checker pattern
const btnSequence = (sequence) => {
return baseSignal.fmap(() => sequenceDetector.checkSequence(sequence));
};
const checkSequence = btnSequence;
const btnSeq = btnSequence;
const btnseq = btnSeq;
// Return an object with all controls
return {
...axes,
buttons,
...Object.fromEntries(
Object.entries(buttonMap).flatMap(([key, index]) => [
[key.toLowerCase(), buttons[index].value],
[key.toUpperCase(), buttons[index].value],
[`tgl${key.toLowerCase()}`, buttons[index].toggle],
[`tgl${key.toUpperCase()}`, buttons[index].toggle],
]),
),
checkSequence,
btnSequence,
btnSeq,
btnseq,
raw: baseSignal,
};
};
// Optional: Export for debugging or state management
export const getGamepadStates = () => Object.fromEntries(gamepadStates);
export const clearGamepadStates = () => gamepadStates.clear();
-3
View File
@@ -1,3 +0,0 @@
import './gamepad.mjs';
export * from './gamepad.mjs';
-37
View File
@@ -1,37 +0,0 @@
{
"name": "@strudel/gamepad",
"version": "1.2.0",
"description": "Gamepad Inputs for strudel",
"main": "index.mjs",
"type": "module",
"publishConfig": {
"main": "dist/index.mjs"
},
"scripts": {
"build": "vite build",
"prepublishOnly": "npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"titdalcycles",
"strudel",
"pattern",
"livecoding",
"algorave"
],
"author": "Yuta Nakayama <nkymut@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel/core": "workspace:*"
},
"devDependencies": {
"vite": "^6.0.11"
}
}
-19
View File
@@ -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',
},
});
+4 -5
View File
@@ -1,7 +1,6 @@
{
"name": "hs2js",
"version": "0.2.0",
"private": true,
"version": "0.1.0",
"description": "Experimental Haskell in JavaScript interpreter",
"main": "src/index.mjs",
"type": "module",
@@ -29,10 +28,10 @@
},
"homepage": "https://github.com/tidalcycles/strudel",
"dependencies": {
"web-tree-sitter": "^0.24.7"
"web-tree-sitter": "^0.20.8"
},
"devDependencies": {
"tree-sitter-haskell": "^0.23.1",
"vite": "^6.0.11"
"tree-sitter-haskell": "^0.21.0",
"vite": "^5.0.10"
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/hydra",
"version": "1.2.0",
"version": "1.1.0",
"description": "Hydra integration for strudel",
"main": "hydra.mjs",
"type": "module",
@@ -39,6 +39,6 @@
},
"devDependencies": {
"pkg": "^5.8.1",
"vite": "^6.0.11"
"vite": "^5.0.10"
}
}
-184
View File
@@ -7,187 +7,3 @@ This package adds midi functionality to strudel Patterns.
```sh
npm i @strudel/midi --save
```
## Available Controls
The following MIDI controls are available:
OUTPUT:
- `midi` - opens a midi output device.
- `note` - Sends MIDI note messages. Can accept note names (e.g. "c4") or MIDI note numbers (0-127)
- `midichan` - Sets the MIDI channel (1-16, defaults to 1)
- `velocity` - Sets note velocity (0-1, defaults to 0.9)
- `gain` - Modifies velocity by multiplying with it (0-1, defaults to 1)
- `control` - Sets MIDI control change messages
- `ccn` - Sets MIDI CC controller number (0-127)
- `ccv` - Sets MIDI CC value (0-1)
- `progNum` - Sends MIDI program change messages (0-127)
- `sysex` - Sends MIDI System Exclusive messages (id: number 0-127 or array of bytes 0-127, data: array of bytes 0-127)
- `sysexid` - Sets MIDI System Exclusive ID (number 0-127 or array of bytes 0-127)
- `sysexdata` - Sets MIDI System Exclusive data (array of bytes 0-127)
- `midibend` - Sets MIDI pitch bend (-1 - 1)
- `miditouch` - Sets MIDI key after touch (0-1)
- `midicmd` - Sends MIDI system real-time messages to control timing and transport on MIDI devices.
- `nrpnn` - Sets MIDI NRPN non-registered parameter number (array of bytes 0-127)
- `nrpv` - Sets MIDI NRPN non-registered parameter value (0-127)
INPUT:
- `midin` - Opens a MIDI input port to receive MIDI control change messages.
Additional controls can be mapped using the mapping object passed to `.midi()`:
## Examples
### midi(outputName?, options?)
Either connect a midi device or use the IAC Driver (Mac) or Midi Through Port (Linux) for internal midi messages.
If no outputName is given, it uses the first midi output it finds.
```javascript
$: chord("<C^7 A7 Dm7 G7>").voicing().midi('IAC Driver')
```
In the console, you will see a log of the available MIDI devices as soon as you run the code, e.g. `Midi connected! Using "Midi Through Port-0".`
### Options
The `.midi()` function accepts an options object with the following properties:
```javascript
$: note("c a f e").midi('IAC Driver', { isController: true, midimap: 'default'})
```
<details>
<summary>Available Options</summary>
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| isController | boolean | false | When true, disables sending note messages. Useful for MIDI controllers |
| latencyMs | number | 34 | Latency in milliseconds to align MIDI with audio engine |
| noteOffsetMs | number | 10 | Offset in milliseconds for note-off messages to prevent glitching |
| midichannel | number | 1 | Default MIDI channel (1-16) |
| velocity | number | 0.9 | Default note velocity (0-1) |
| gain | number | 1 | Default gain multiplier for velocity (0-1) |
| midimap | string | 'default' | Name of MIDI mapping to use for control changes |
| midiport | string/number | - | MIDI device name or index |
</details>
### midiport(outputName)
Selects the MIDI output device to use, pattern can be used to switch between devices.
```javascript
$: midiport('IAC Driver')
$: note("c a f e").midiport("<0 1 2 3>").midi()
```
### midichan(number)
Selects the MIDI channel to use. If not used, `.midi` will use channel 1 by default.
### control, ccn && ccv
`control` sends MIDI control change messages to your MIDI device.
- `ccn` sets the cc number. Depends on your synths midi mapping
- `ccv` sets the cc value. normalized from 0 to 1.
```javascript
$: note("c a f e").control([74, sine.slow(4)]).midi()
$: note("c a f e").ccn(74).ccv(sine.slow(4)).midi()
```
In the above snippet, `ccn` is set to 74, which is the filter cutoff for many synths. `ccv` is controlled by a saw pattern.
Having everything in one pattern, the `ccv` pattern will be aligned to the note pattern, because the structure comes from the left by default.
But you can also control cc messages separately like this:
```javascript
$: note("c a f e").midi()
$: ccv(sine.segment(16).slow(4)).ccn(74).midi()
```
### progNum (Program Change)
`progNum` control sends MIDI program change messages to switch between different presets/patches on your MIDI device.
Program change values should be numbers between 0 and 127.
```javascript
// Play notes while changing programs
note("c3 e3 g3").progNum("<0 1 2>").midi()
```
Program change messages are useful for switching between different instrument sounds or presets during a performance.
The exact sound that each program number maps to depends on your MIDI device's configuration.
## sysex, sysexid && sysexdata (System Exclusive Message)
`sysex`, `sysexid` and `sysexdata` control sends MIDI System Exclusive (SysEx) messages to your MIDI device.
sysEx messages are device-specific commands that allow deeper control over synthesizer parameters.
The value should be an array of numbers between 0-255 representing the SysEx data bytes.
```javascript
// Send a simple SysEx message
let id = 0x43; //Yamaha
//let id = "0x00:0x20:0x32"; //Behringer ID can be an array of numbers
let data = "0x79:0x09:0x11:0x0A:0x00:0x00"; // Set NSX-39 voice to say "Aa"
$: note("c d e f e d c").sysex(id, data).midi();
$: note("c d e f e d c").sysexid(id).sysexdata(data).midi();
```
The exact format of SysEx messages depends on your MIDI device's specification.
Consult your device's MIDI implementation guide for details on supported SysEx messages.
### midibend && miditouch
`midibend` sets MIDI pitch bend (-1 - 1)
`miditouch` sets MIDI key after touch (0-1)
```javascript
$: note("c d e f e d c").midibend(sine.slow(4).range(-0.4,0.4)).midi();
$: note("c d e f e d c").miditouch(sine.slow(4).range(0,1)).midi();
```
### midicmd
`midicmd` sends MIDI system real-time messages to control timing and transport on MIDI devices.
It supports the following commands:
- `clock`/`midiClock` - Sends MIDI timing clock messages
- `start` - Sends MIDI start message
- `stop` - Sends MIDI stop message
- `continue` - Sends MIDI continue message
```javascript
// You can control the clock with a pattern and ensure it starts in sync when the repl begins.
// Note: It might act unexpectedly if MIDI isn't set up initially.
stack(
midicmd("clock*48,<start stop>/2").midi('IAC Driver')
)
```
`midicmd` also supports sending control change, program change and sysex messages.
- `cc` - sends MIDI control change messages.
- `progNum` - sends MIDI program change messages.
- `sysex` - sends MIDI system exclusive messages.
```javascript
stack(
// "cc:ccn:ccv"
midicmd("cc:74:1").midi('IAC Driver'),
// "progNum:progNum"
midicmd("progNum:1").midi('IAC Driver'),
// "sysex:[sysexid]:[sysexdata]"
midicmd("sysex:[0x43]:[0x79:0x09:0x11:0x0A:0x00:0x00]").midi('IAC Driver')
)
```
+34 -344
View File
@@ -6,9 +6,8 @@ This program is free software: you can redistribute it and/or modify it under th
import * as _WebMidi from 'webmidi';
import { Pattern, getEventOffsetMs, isPattern, logger, ref } from '@strudel/core';
import { noteToMidi, getControlName } from '@strudel/core';
import { noteToMidi } from '@strudel/core';
import { Note } from 'webmidi';
// if you use WebMidi from outside of this package, make sure to import that instance:
export const { WebMidi } = _WebMidi;
@@ -44,16 +43,13 @@ export function enableWebMidi(options = {}) {
resolve(WebMidi);
return;
}
WebMidi.enable(
(err) => {
if (err) {
reject(err);
}
onReady?.(WebMidi);
resolve(WebMidi);
},
{ sysex: true },
);
WebMidi.enable((err) => {
if (err) {
reject(err);
}
onReady?.(WebMidi);
resolve(WebMidi);
});
});
}
@@ -93,235 +89,18 @@ if (typeof window !== 'undefined') {
});
}
// registry for midi mappings, converting control names to cc messages
export const midicontrolMap = new Map();
// takes midimap and converts each control key to the main control name
function unifyMapping(mapping) {
return Object.fromEntries(
Object.entries(mapping).map(([key, mapping]) => {
if (typeof mapping === 'number') {
mapping = { ccn: mapping };
}
return [getControlName(key), mapping];
}),
);
}
function githubPath(base, subpath = '') {
if (!base.startsWith('github:')) {
throw new Error('expected "github:" at the start of pseudoUrl');
}
let [_, path] = base.split('github:');
path = path.endsWith('/') ? path.slice(0, -1) : path;
if (path.split('/').length === 2) {
// assume main as default branch if none set
path += '/main';
}
return `https://raw.githubusercontent.com/${path}/${subpath}`;
}
/**
* configures the default midimap, which is used when no "midimap" port is set
* @example
* defaultmidimap({ lpf: 74 })
* $: note("c a f e").midi();
* $: lpf(sine.slow(4).segment(16)).midi();
*/
export function defaultmidimap(mapping) {
midicontrolMap.set('default', unifyMapping(mapping));
}
let loadCache = {};
/**
* Adds midimaps to the registry. Inside each midimap, control names (e.g. lpf) are mapped to cc numbers.
* @example
* midimaps({ mymap: { lpf: 74 } })
* $: note("c a f e")
* .lpf(sine.slow(4))
* .midimap('mymap')
* .midi()
* @example
* midimaps({ mymap: {
* lpf: { ccn: 74, min: 0, max: 20000, exp: 0.5 }
* }})
* $: note("c a f e")
* .lpf(sine.slow(2).range(400,2000))
* .midimap('mymap')
* .midi()
*/
export async function midimaps(map) {
if (typeof map === 'string') {
if (map.startsWith('github:')) {
map = githubPath(map, 'midimap.json');
}
if (!loadCache[map]) {
loadCache[map] = fetch(map).then((res) => res.json());
}
map = await loadCache[map];
}
if (typeof map === 'object') {
Object.entries(map).forEach(([name, mapping]) => midicontrolMap.set(name, unifyMapping(mapping)));
}
}
// registry for midi sounds, converting sound names to controls
export const midisoundMap = new Map();
// normalizes the given value from the given range and exponent
function normalize(value = 0, min = 0, max = 1, exp = 1) {
if (min === max) {
throw new Error('min and max cannot be the same value');
}
let normalized = (value - min) / (max - min);
normalized = Math.min(1, Math.max(0, normalized));
return Math.pow(normalized, exp);
}
function mapCC(mapping, value) {
return Object.keys(value)
.filter((key) => !!mapping[getControlName(key)])
.map((key) => {
const { ccn, min = 0, max = 1, exp = 1 } = mapping[key];
const ccv = normalize(value[key], min, max, exp);
return { ccn, ccv };
});
}
// sends a cc message to the given device on the given channel
function sendCC(ccn, ccv, device, midichan, timeOffsetString) {
if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) {
throw new Error('expected ccv to be a number between 0 and 1');
}
if (!['string', 'number'].includes(typeof ccn)) {
throw new Error('expected ccn to be a number or a string');
}
const scaled = Math.round(ccv * 127);
device.sendControlChange(ccn, scaled, midichan, { time: timeOffsetString });
}
// sends a program change message to the given device on the given channel
function sendProgramChange(progNum, device, midichan, timeOffsetString) {
if (typeof progNum !== 'number' || progNum < 0 || progNum > 127) {
throw new Error('expected progNum (program change) to be a number between 0 and 127');
}
device.sendProgramChange(progNum, midichan, { time: timeOffsetString });
}
// sends a sysex message to the given device on the given channel
function sendSysex(sysexid, sysexdata, device, timeOffsetString) {
if (Array.isArray(sysexid)) {
if (!sysexid.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
throw new Error('all sysexid bytes must be integers between 0 and 255');
}
} else if (!Number.isInteger(sysexid) || sysexid < 0 || sysexid > 255) {
throw new Error('A:sysexid must be an number between 0 and 255 or an array of such integers');
}
if (!Array.isArray(sysexdata)) {
throw new Error('expected sysex to be an array of numbers (0-255)');
}
if (!sysexdata.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
throw new Error('all sysex bytes must be integers between 0 and 255');
}
device.sendSysex(sysexid, sysexdata, { time: timeOffsetString });
}
// sends a NRPN message to the given device on the given channel
function sendNRPN(nrpnn, nrpv, device, midichan, timeOffsetString) {
if (Array.isArray(nrpnn)) {
if (!nrpnn.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
throw new Error('all nrpnn bytes must be integers between 0 and 255');
}
} else if (!Number.isInteger(nrpv) || nrpv < 0 || nrpv > 255) {
throw new Error('A:sysexid must be an number between 0 and 255 or an array of such integers');
}
device.sendNRPN(nrpnn, nrpv, midichan, { time: timeOffsetString });
}
// sends a pitch bend message to the given device on the given channel
function sendPitchBend(midibend, device, midichan, timeOffsetString) {
if (typeof midibend !== 'number' || midibend < -1 || midibend > 1) {
throw new Error('expected midibend to be a number between -1 and 1');
}
device.sendPitchBend(midibend, midichan, { time: timeOffsetString });
}
// sends a channel aftertouch message to the given device on the given channel
function sendAftertouch(miditouch, device, midichan, timeOffsetString) {
if (typeof miditouch !== 'number' || miditouch < 0 || miditouch > 1) {
throw new Error('expected miditouch to be a number between 0 and 1');
}
device.sendChannelAftertouch(miditouch, midichan, { time: timeOffsetString });
}
// sends a note message to the given device on the given channel
function sendNote(note, velocity, duration, device, midichan, timeOffsetString) {
if (note == null || note === '') {
throw new Error('note cannot be null or empty');
}
if (velocity != null && (typeof velocity !== 'number' || velocity < 0 || velocity > 1)) {
throw new Error('velocity must be a number between 0 and 1');
}
if (duration != null && (typeof duration !== 'number' || duration < 0)) {
throw new Error('duration must be a positive number');
}
const midiNumber = typeof note === 'number' ? note : noteToMidi(note);
const midiNote = new Note(midiNumber, { attack: velocity, duration });
device.playNote(midiNote, midichan, {
time: timeOffsetString,
});
}
/**
* MIDI output: Opens a MIDI output port.
* @param {string | number} midiport MIDI device name or index defaulting to 0
* @param {object} options Additional MIDI configuration options
* @example
* note("c4").midichan(1).midi('IAC Driver Bus 1')
* @example
* note("c4").midichan(1).midi('IAC Driver Bus 1', { controller: true, latency: 50 })
*/
Pattern.prototype.midi = function (midiport, options = {}) {
if (isPattern(midiport)) {
Pattern.prototype.midi = function (output) {
if (isPattern(output)) {
throw new Error(
`.midi does not accept Pattern input for midiport. Make sure to pass device name with single quotes. Example: .midi('${
`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${
WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1'
}')`,
);
}
// For backward compatibility
if (typeof midiport === 'object') {
const { port, isController = false, ...configOptions } = midiport;
options = {
isController,
...configOptions,
...options, // Keep any options passed separately
};
midiport = port;
}
let midiConfig = {
// Default configuration values
isController: false, // Disable sending notes for midi controllers
latencyMs: 34, // Default latency to get audio engine to line up in ms
noteOffsetMs: 10, // Default note-off offset to prevent glitching in ms
midichannel: 1, // Default MIDI channel
velocity: 0.9, // Default velocity
gain: 1, // Default gain
midimap: 'default', // Default MIDI map
midiport: midiport, // Store the port in the config
...options, // Override defaults with provided options
};
enableWebMidi({
onEnabled: ({ outputs }) => {
const device = getDevice(midiConfig.midiport, outputs);
const device = getDevice(output, outputs);
const otherOutputs = outputs.filter((o) => o.name !== device.name);
logger(
`Midi enabled! Using "${device.name}". ${
@@ -335,102 +114,39 @@ Pattern.prototype.midi = function (midiport, options = {}) {
return this.onTrigger((time_deprecate, hap, currentTime, cps, targetTime) => {
if (!WebMidi.enabled) {
logger('Midi not enabled');
console.log('not enabled');
return;
}
const device = getDevice(output, WebMidi.outputs);
hap.ensureObjectValue();
//magic number to get audio engine to line up, can probably be calculated somehow
const latencyMs = midiConfig.latencyMs;
const latencyMs = 34;
// 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}`;
// midi event values from hap with configurable defaults
let {
note,
nrpnn,
nrpv,
ccn,
ccv,
midichan = midiConfig.midichannel,
midicmd,
midibend,
miditouch,
polyTouch,
gain = midiConfig.gain,
velocity = midiConfig.velocity,
progNum,
sysexid,
sysexdata,
midimap = midiConfig.midimap,
midiport = midiConfig.midiport,
} = hap.value;
const device = getDevice(midiport, WebMidi.outputs);
if (!device) {
logger(
`[midi] midiport "${midiport}" not found! available: ${WebMidi.outputs.map((output) => `'${output.name}'`).join(', ')}`,
);
return;
}
// destructure value
let { note, nrpnn, nrpv, ccn, ccv, midichan = 1, midicmd, gain = 1, velocity = 0.9 } = hap.value;
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));
} else if (midimap !== 'default') {
// Add warning when a non-existent midimap is specified
logger(`[midi] midimap "${midimap}" not found! Available maps: ${[...midicontrolMap.keys()].join(', ')}`);
// note off messages will often a few ms arrive late, try to prevent glitching by subtracting from the duration length
const duration = (hap.duration.valueOf() / cps) * 1000 - 10;
if (note != null) {
const midiNumber = typeof note === 'number' ? note : noteToMidi(note);
const midiNote = new Note(midiNumber, { attack: velocity, duration });
device.playNote(midiNote, midichan, {
time: timeOffsetString,
});
}
// Handle note
if (note !== undefined && !midiConfig.isController) {
// note off messages will often a few ms arrive late,
// try to prevent glitching by subtracting noteOffsetMs from the duration length
const duration = (hap.duration.valueOf() / cps) * 1000 - midiConfig.noteOffsetMs;
sendNote(note, velocity, duration, device, midichan, timeOffsetString);
}
// Handle program change
if (progNum !== undefined) {
sendProgramChange(progNum, device, midichan, timeOffsetString);
}
// Handle sysex
// sysex data is consist of 2 arrays, first is sysexid, second is sysexdata
// sysexid is a manufacturer id it is either a number or an array of 3 numbers.
// list of manufacturer ids can be found here : https://midi.org/sysexidtable
// if sysexid is an array the first byte is 0x00
if (sysexid !== undefined && sysexdata !== undefined) {
sendSysex(sysexid, sysexdata, device, timeOffsetString);
}
// Handle control change
if (ccv !== undefined && ccn !== undefined) {
sendCC(ccn, ccv, device, midichan, timeOffsetString);
if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) {
throw new Error('expected ccv to be a number between 0 and 1');
}
if (!['string', 'number'].includes(typeof ccn)) {
throw new Error('expected ccn to be a number or a string');
}
const scaled = Math.round(ccv * 127);
device.sendControlChange(ccn, scaled, midichan, { time: timeOffsetString });
}
// Handle NRPN non-registered parameter number
if (nrpnn !== undefined && nrpv !== undefined) {
sendNRPN(nrpnn, nrpv, device, midichan, timeOffsetString);
}
// Handle midibend
if (midibend !== undefined) {
sendPitchBend(midibend, device, midichan, timeOffsetString);
}
// Handle miditouch
if (miditouch !== undefined) {
sendAftertouch(miditouch, device, midichan, timeOffsetString);
}
// Handle midicmd
if (hap.whole.begin + 0 === 0) {
// we need to start here because we have the timing info
device.sendStart({ time: timeOffsetString });
@@ -443,19 +159,6 @@ Pattern.prototype.midi = function (midiport, options = {}) {
device.sendStop({ time: timeOffsetString });
} else if (['continue'].includes(midicmd)) {
device.sendContinue({ time: timeOffsetString });
} else if (Array.isArray(midicmd)) {
if (midicmd[0] === 'progNum') {
sendProgramChange(midicmd[1], device, midichan, timeOffsetString);
} else if (midicmd[0] === 'cc') {
if (midicmd.length === 2) {
sendCC(midicmd[0], midicmd[1] / 127, device, midichan, timeOffsetString);
}
} else if (midicmd[0] === 'sysex') {
if (midicmd.length === 3) {
const [_, id, data] = midicmd;
sendSysex(id, data, device, timeOffsetString);
}
}
}
});
};
@@ -463,29 +166,16 @@ Pattern.prototype.midi = function (midiport, options = {}) {
let listeners = {};
const refs = {};
/**
* MIDI input: Opens a MIDI input port to receive MIDI control change messages.
* @param {string | number} input MIDI device name or index defaulting to 0
* @returns {Function}
* @example
* let cc = await midin('IAC Driver Bus 1')
* note("c a f e").lpf(cc(0).range(0, 1000)).lpq(cc(1).range(0, 10)).sound("sawtooth")
*/
export async function midin(input) {
if (isPattern(input)) {
throw new Error(
`midin: does not accept Pattern as input. Make sure to pass device name with single quotes. Example: midin('${
`.midi does not accept Pattern input. Make sure to pass device name with single quotes. Example: .midi('${
WebMidi.outputs?.[0]?.name || 'IAC Driver Bus 1'
}')`,
);
}
const initial = await enableWebMidi(); // only returns on first init
const device = getDevice(input, WebMidi.inputs);
if (!device) {
throw new Error(
`midiin: device "${input}" not found.. connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`,
);
}
if (initial) {
const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name);
logger(
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/midi",
"version": "1.2.0",
"version": "1.1.0",
"description": "Midi API for strudel",
"main": "index.mjs",
"type": "module",
@@ -31,9 +31,9 @@
"dependencies": {
"@strudel/core": "workspace:*",
"@strudel/webaudio": "workspace:*",
"webmidi": "^3.1.12"
"webmidi": "^3.1.8"
},
"devDependencies": {
"vite": "^6.0.11"
"vite": "^5.0.10"
}
}
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -19,10 +19,10 @@ This program is free software: you can redistribute it and/or modify it under th
this.location_ = location();
}
var PatternStub = function(source, alignment, seed, _steps)
var PatternStub = function(source, alignment, seed, tactus)
{
this.type_ = "pattern";
this.arguments_ = { alignment: alignment, _steps: _steps };
this.arguments_ = { alignment: alignment, tactus: tactus };
if (seed !== undefined) {
this.arguments_.seed = seed;
}
@@ -172,8 +172,8 @@ slice_with_ops = s:slice ops:slice_op*
}
// a sequence is a combination of one or more successive slices (as an array)
sequence = _steps:'^'? s:(slice_with_ops)+
{ return new PatternStub(s, 'fastcat', undefined, !!_steps); }
sequence = tactus:'^'? s:(slice_with_ops)+
{ return new PatternStub(s, 'fastcat', undefined, !!tactus); }
// a stack is a series of vertically aligned sequence, separated by a comma
stack_tail = tail:(comma @sequence)+
+17 -17
View File
@@ -14,7 +14,7 @@ const applyOptions = (parent, enter) => (pat, i) => {
const ast = parent.source_[i];
const options = ast.options_;
const ops = options?.ops;
const steps_source = pat.__steps_source;
if (ops) {
for (const op of ops) {
switch (op.type_) {
@@ -69,7 +69,7 @@ const applyOptions = (parent, enter) => (pat, i) => {
}
}
}
pat.__steps_source = pat.__steps_source || steps_source;
return pat;
};
@@ -82,20 +82,20 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
// resolveReplications(ast);
const children = ast.source_.map((child) => enter(child)).map(applyOptions(ast, enter));
const alignment = ast.arguments_.alignment;
const with_steps = children.filter((child) => child.__steps_source);
const with_tactus = children.filter((child) => child.__tactus_source);
let pat;
switch (alignment) {
case 'stack': {
pat = strudel.stack(...children);
if (with_steps.length) {
pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps)));
if (with_tactus.length) {
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus)));
}
break;
}
case 'polymeter_slowcat': {
pat = strudel.stack(...children.map((child) => child._slow(child.__weight)));
if (with_steps.length) {
pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps)));
if (with_tactus.length) {
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus)));
}
break;
}
@@ -111,8 +111,8 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
}
case 'rand': {
pat = strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
if (with_steps.length) {
pat._steps = lcm(...with_steps.map((x) => Fraction(x._steps)));
if (with_tactus.length) {
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus)));
}
break;
}
@@ -131,21 +131,21 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
...ast.source_.map((child, i) => [child.options_?.weight || strudel.Fraction(1), children[i]]),
);
pat.__weight = weightSum; // for polymeter
pat._steps = weightSum;
if (with_steps.length) {
pat._steps = pat._steps.mul(lcm(...with_steps.map((x) => Fraction(x._steps))));
pat.tactus = weightSum;
if (with_tactus.length) {
pat.tactus = pat.tactus.mul(lcm(...with_tactus.map((x) => Fraction(x.tactus))));
}
} else {
pat = strudel.sequence(...children);
pat._steps = children.length;
pat.tactus = children.length;
}
if (ast.arguments_._steps) {
pat.__steps_source = true;
if (ast.arguments_.tactus) {
pat.__tactus_source = true;
}
}
}
if (with_steps.length) {
pat.__steps_source = true;
if (with_tactus.length) {
pat.__tactus_source = true;
}
return pat;
}
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/mini",
"version": "1.2.0",
"version": "1.1.0",
"description": "Mini notation for strudel",
"main": "index.mjs",
"type": "module",
@@ -35,8 +35,8 @@
"@strudel/core": "workspace:*"
},
"devDependencies": {
"peggy": "^4.2.0",
"vite": "^6.0.11",
"vitest": "^3.0.4"
"peggy": "^3.0.2",
"vite": "^5.0.10",
"vitest": "^1.1.0"
}
}
+8 -10
View File
@@ -208,16 +208,14 @@ describe('mini', () => {
it('_ and @ are almost interchangeable', () => {
expect(minS('a @ b @ @')).toEqual(minS('a _2 b _3'));
});
it('supports ^ step marking', () => {
expect(mini('a [^b c]')._steps).toEqual(Fraction(4));
expect(mini('[^b c]!3')._steps).toEqual(Fraction(6));
expect(mini('[a b c] [d [e f]]')._steps).toEqual(Fraction(2));
expect(mini('^[a b c] [d [e f]]')._steps).toEqual(Fraction(2));
expect(mini('[a b c] [d [^e f]]')._steps).toEqual(Fraction(8));
expect(mini('[a b c] [^d [e f]]')._steps).toEqual(Fraction(4));
expect(mini('[^a b c] [^d [e f]]')._steps).toEqual(Fraction(12));
expect(mini('[^a b c] [d [^e f]]')._steps).toEqual(Fraction(24));
expect(mini('[^a b c d e]')._steps).toEqual(Fraction(5));
it('supports ^ tactus marking', () => {
expect(mini('a [^b c]').tactus).toEqual(Fraction(4));
expect(mini('[a b c] [d [e f]]').tactus).toEqual(Fraction(2));
expect(mini('^[a b c] [d [e f]]').tactus).toEqual(Fraction(2));
expect(mini('[a b c] [d [^e f]]').tactus).toEqual(Fraction(8));
expect(mini('[a b c] [^d [e f]]').tactus).toEqual(Fraction(4));
expect(mini('[^a b c] [^d [e f]]').tactus).toEqual(Fraction(12));
expect(mini('[^a b c] [d [^e f]]').tactus).toEqual(Fraction(24));
});
});

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