Compare commits

..

71 Commits

Author SHA1 Message Date
Felix Roos 193324b2b3 add link to example 2023-02-27 14:33:07 +01:00
Felix Roos f3a2cff5db remove footnote syntax
fixes https://github.com/tidalcycles/strudel/issues/439
2023-02-27 13:34:51 +01:00
Felix Roos 97c8ab0f71 fix format 2023-02-27 12:49:23 +01:00
Felix Roos 28534ea547 add screenshots 2023-02-27 12:47:38 +01:00
Felix Roos 1a75b79a33 better offline docs
fixes https://github.com/tidalcycles/strudel/issues/424
2023-02-27 12:34:17 +01:00
Felix Roos 75dfb0af35 add dependencygraph 2023-02-27 11:12:21 +01:00
Felix Roos 073fa09648 packages doc 2023-02-27 11:10:47 +01:00
Felix Roos 57157fde9c Merge branch 'main' into docs 2023-02-17 21:53:02 +01:00
Alex McLean cbae355896 Another attempt at composable functions - WIP (#390)
Summary of changes:

- Made unary functions composable, including controls. So e.g. s("bd sd").every(3,fast(2).iter(4).n(4)) works the same as s("bd sd").every(3,x => x.fast(2).iter(4).n(4))
- Made operators/alignments composable too, so s("bd sd").every(3, set.squeeze.n(3, 4)) works
- Patterns are not treated as functions, so s("bd sd").every(3, n(5)) is an annoying runtime error. s("bd sd").every(3, set.n(5)) does work though.

Other minor changes:

- standardised alignment 'squeezeOut' as lowercase 'squeezeout'
- made firstCycleValues turn haps sorted in order of 'part'
2023-02-16 23:15:21 +00:00
Felix Roos 924e8a764a Merge pull request #461 from bwagner/main
FIXES: Warning about jsxBracketSameLine deprecation
2023-02-16 20:38:45 +01:00
Bernhard Wagner 40e629cb0e ADDS: Continuation to Testing 2023-02-16 15:24:44 +01:00
Bernhard Wagner 99ec261f79 ADDS: Continuation to Docs 2023-02-16 15:23:34 +01:00
Bernhard Wagner 9daea5bbc2 ADDS: Continuation to REPL 2023-02-16 15:22:35 +01:00
Bernhard Wagner 4704c03d48 ADDS: Continuation to Strudel vs Tidal 2023-02-16 15:21:25 +01:00
Bernhard Wagner 9dbf52bedb ADDS: Continuation to Pattern Alignment 2023-02-16 15:20:26 +01:00
Bernhard Wagner cfd04ab7fa ADDS: Continuation to Patterns 2023-02-16 15:19:14 +01:00
Bernhard Wagner 33dbf75e3b ADDS: Continuation to Offline 2023-02-16 15:18:32 +01:00
Bernhard Wagner 8ffaeeb171 ADDS: Continuation to MIDI and OSC 2023-02-16 15:16:51 +01:00
Bernhard Wagner 451828e5d4 ADDS: Continuation to Tonal Modifiers 2023-02-16 15:15:54 +01:00
Bernhard Wagner 176877f2b8 ADDS: Continuation to Accumulation Modifiers 2023-02-16 15:14:38 +01:00
Bernhard Wagner 61b0d08b5f ADDS: Continuation to Conditional Modifiers 2023-02-16 15:12:37 +01:00
Bernhard Wagner f50a6ce1ae ADDS: Continuation to Signals 2023-02-16 15:11:28 +01:00
Bernhard Wagner 8549d6ea8b ADDS: Continuation to Control Parameters 2023-02-16 15:09:37 +01:00
Bernhard Wagner 90a90a87a5 ADDS: Continuation to Time Modifiers 2023-02-16 15:08:45 +01:00
Bernhard Wagner 760bdea521 ADDS: Continuation to Pattern Constructors 2023-02-16 15:07:03 +01:00
Bernhard Wagner 0fd783e83f ADDS: Continuation to Functional JavaScript API 2023-02-16 15:03:58 +01:00
Bernhard Wagner 42caa9058b ADDS: Continuation to Synths 2023-02-16 15:00:17 +01:00
Bernhard Wagner 9ab315ac2c ADDS: Continuation to Samples 2023-02-16 14:58:02 +01:00
Bernhard Wagner a08ad07537 ADDS: Continuation to Csound 2023-02-16 14:55:01 +01:00
Bernhard Wagner c6ce763f7c FIXES: Warning about jsxBracketSameLine deprecation 2023-02-16 14:46:33 +01:00
Felix Roos 0393205eda Merge pull request #438 from bwagner/patch-8
Update synths.mdx
2023-02-14 22:11:02 +01:00
Bernhard Wagner 5c05a5a63e Update synths.mdx
in future -> in the future
ADDS: Continuation to Audio Effects.
2023-02-14 22:08:40 +01:00
Felix Roos b01d9f849b Merge pull request #437 from bwagner/patch-5
Update mini-notation.mdx
2023-02-14 19:54:41 +01:00
Felix Roos 0ab904275c Merge pull request #436 from bwagner/patch-4
Update code.mdx
2023-02-14 19:54:24 +01:00
Bernhard Wagner 5f82a86801 Update mini-notation.mdx
descreases -> decreases
twice a cycle -> twice per cycle
2023-02-14 19:40:03 +01:00
Bernhard Wagner 13a5d8de86 Update code.mdx
The code example says "sine", but the description says "sawtooth".
2023-02-14 19:08:47 +01:00
Felix Roos f024b71808 Update CONTRIBUTING.md
add more infos on code style, eslint and tests
2023-02-14 18:16:39 +01:00
Felix Roos cd35a17904 Merge pull request #433 from tidalcycles/fix-anchors
Fix anchors
2023-02-13 00:43:29 +01:00
Felix Roos be3f68ed06 fix comment 2023-02-13 00:42:16 +01:00
Felix Roos 64b8fd9584 fix: right sidebar anchors earlier 2023-02-13 00:41:42 +01:00
Felix Roos b001d1d0f1 update lockfile 2023-02-13 00:37:20 +01:00
Felix Roos 64ad75c79e fix: anchor links
fixes https://github.com/tidalcycles/strudel/issues/432
+ fix style regression in mini repl
2023-02-13 00:35:40 +01:00
Felix Roos 12eb361d69 remove slash before anchor link 2023-02-12 20:13:59 +01:00
Felix Roos dd51bd0a2f Merge pull request #427 from tidalcycles/docs
autocomplete preparations
2023-02-11 21:02:11 +01:00
Felix Roos 0fb478a7c8 no autocomplete until upstream merge 2023-02-11 20:55:35 +01:00
Felix Roos 9694ecf55b autocomplete dark / light 2023-02-11 13:02:04 +01:00
Felix Roos fe80478b8a Merge remote-tracking branch 'origin/main' into docs 2023-02-10 23:18:04 +01:00
Felix Roos a253c26bee Merge pull request #431 from tidalcycles/themes
Themes
2023-02-10 23:14:47 +01:00
Felix Roos a500f983f5 Merge branch 'main' into themes 2023-02-10 22:59:52 +01:00
Felix Roos 3579b6f8f3 dynamic highlight color
+ refactor hooks
2023-02-10 22:52:34 +01:00
Felix Roos 14cb954213 themed minirepl
+ add strudelTheme
+ add lineBackground with opacity
+ add some missing light flags
+ add fallback css variables for non js context
2023-02-10 21:42:27 +01:00
Felix Roos 6170eef441 format 2023-02-10 20:09:23 +01:00
Felix Roos ddb9af4d57 themify rest of layout (except minirepls) 2023-02-10 20:07:36 +01:00
Felix Roos 6db2c0bf93 only invert prose for dark themes 2023-02-10 18:34:02 +01:00
Felix Roos 46f70e6a5e persisted theme + add all settings 2023-02-10 12:54:15 +01:00
Felix Roos 0b994a1b0f switchable css variables for theming 2023-02-10 11:32:36 +01:00
Felix Roos 0326244956 update themes 2023-02-10 09:25:58 +01:00
Felix Roos ea1b2252fc remove unused descriptions
+ fix some titles
+ comment out unfinished packages page
+ start offline page
2023-02-09 21:42:48 +01:00
Felix Roos 0da25ea7cc Merge pull request #429 from tidalcycles/minirepl-keyboard-support
minirepl: add keyboard shortcuts
2023-02-09 19:22:56 +01:00
Felix Roos 3209afa4ff minirepl: add keyboard shortcuts 2023-02-09 19:20:44 +01:00
Felix Roos 33e2631e5d update snapshot 2023-02-09 18:48:34 +01:00
Felix Roos f02a41b675 fix param description 2023-02-09 18:44:41 +01:00
Felix Roos f497d7b212 reactify autocomplete info 2023-02-09 18:37:29 +01:00
Felix Roos 0f18f5eb81 remove logs 2023-02-09 14:16:37 +01:00
Felix Roos 8da81104aa doc: note 2023-02-09 12:42:37 +01:00
Felix Roos 97cbdc6b0f use jsdoc in autocomplete, still not context aware
+ add noAutocomplete flags
+ add superdirtOnly flags
2023-02-09 12:19:38 +01:00
Felix Roos 7f53f4e36b Merge branch 'main' into docs 2023-02-09 08:57:20 +01:00
Felix Roos 3e727893bf basic theme picker 2023-02-08 23:43:02 +01:00
Felix Roos 4f0885d3af Merge branch 'main' into themes 2023-02-08 20:49:20 +01:00
Felix Roos 30b149053c basic theme switcher 2023-02-08 18:54:34 +01:00
Felix Roos cdd40df9d3 disable sw in dev 2023-02-08 18:54:19 +01:00
79 changed files with 2213 additions and 454 deletions
+3 -1
View File
@@ -15,4 +15,6 @@ vite.config.js
!**/*.mjs
**/*.tsx
**/*.ts
**/*.json
**/*.json
**/dev-dist
**/dist
+2 -1
View File
@@ -8,4 +8,5 @@ packages/mini/krill-parser.js
packages/xen/tunejs.js
paper
pnpm-lock.yaml
pnpm-workspace.yaml
pnpm-workspace.yaml
**/dev-dist
+1 -1
View File
@@ -7,7 +7,7 @@
"jsxSingleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"bracketSameLine": false,
"arrowParens": "always",
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
+22
View File
@@ -82,6 +82,10 @@ Please report any problems you've had with the setup instructions!
## Code Style
To make sure the code changes only where it should, we are using prettier to unify the code style.
- You can format all files at once by running `pnpm prettier` from the project root
- Run `pnpm format-check` from the project root to check if all files are well formatted
If you use VSCode, you can
1. install [the prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
@@ -89,6 +93,24 @@ If you use VSCode, you can
3. Choose "Configure Default Formatter..."
4. Select prettier
## ESLint
To prevent unwanted runtime errors, this project uses [eslint](https://eslint.org/).
- You can check for lint errors by running `pnpm lint`
There are also eslint extensions / plugins for most editors.
## Running Tests
- Run all tests with `pnpm test`
- Run all tests with UI using `pnpm test-ui`
## Running all CI Checks
When opening a PR, the CI runner will automatically check the code style and eslint, as well as run all tests.
You can run the same check with `pnpm check`
## Package Workflow
The project is split into multiple [packages](https://github.com/tidalcycles/strudel/tree/main/packages) with independent versioning.
+2 -5
View File
@@ -20,11 +20,8 @@ Example: <https://felixroos.github.io/strudel/swatch/>
### 6. edit `website/astro.config.mjs` to use site: `https://<your-username>.github.io` and base `/strudel`, like this
```js
export default defineConfig({
/* ... rest of config ... */
site: 'https://<your-username>.github.io',
base: '/strudel',
});
const site = 'https://<your-username>.github.io';
const base = '/strudel';
```
### 7. commit & push the changes
+43 -28
View File
@@ -4,22 +4,13 @@ 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, sequence } from './pattern.mjs';
import { Pattern, sequence, registerControl } from './pattern.mjs';
const controls = {};
const generic_params = [
/**
* Select a sound / sample by name.
*
* <details style={{display:'none'}}>
* <summary>show all sounds</summary>
*
* 808 (6) 808bd (25) 808cy (25) 808hc (5) 808ht (5) 808lc (5) 808lt (5) 808mc (5) 808mt (5) 808oh (5) 808sd (25) 909 (1) ab (12) ade (10) ades2 (9) ades3 (7) ades4 (6) alex (2) alphabet (26) amencutup (32) armora (7) arp (2) arpy (11) auto (11) baa (7) baa2 (7) bass (4) bass0 (3) bass1 (30) bass2 (5) bass3 (11) bassdm (24) bassfoo (3) battles (2) bd (24) bend (4) bev (2) bin (2) birds (10) birds3 (19) bleep (13) blip (2) blue (2) bottle (13) breaks125 (2) breaks152 (1) breaks157 (1) breaks165 (1) breath (1) bubble (8) can (14) casio (3) cb (1) cc (6) chin (4) circus (3) clak (2) click (4) clubkick (5) co (4) coins (1) control (2) cosmicg (15) cp (2) cr (6) crow (4) d (4) db (13) diphone (38) diphone2 (12) dist (16) dork2 (4) dorkbot (2) dr (42) dr2 (6) dr55 (4) dr_few (8) drum (6) drumtraks (13) e (8) east (9) electro1 (13) em2 (6) erk (1) f (1) feel (7) feelfx (8) fest (1) fire (1) flick (17) fm (17) foo (27) future (17) gab (10) gabba (4) gabbaloud (4) gabbalouder (4) glasstap (3) glitch (8) glitch2 (8) gretsch (24) gtr (3) h (7) hand (17) hardcore (12) hardkick (6) haw (6) hc (6) hh (13) hh27 (13) hit (6) hmm (1) ho (6) hoover (6) house (8) ht (16) if (5) ifdrums (3) incoming (8) industrial (32) insect (3) invaders (18) jazz (8) jungbass (20) jungle (13) juno (12) jvbass (13) kicklinn (1) koy (2) kurt (7) latibro (8) led (1) less (4) lighter (33) linnhats (6) lt (16) made (7) made2 (1) mash (2) mash2 (4) metal (10) miniyeah (4) monsterb (6) moog (7) mouth (15) mp3 (4) msg (9) mt (16) mute (28) newnotes (15) noise (1) noise2 (8) notes (15) numbers (9) oc (4) odx (15) off (1) outdoor (6) pad (3) padlong (1) pebbles (1) perc (6) peri (15) pluck (17) popkick (10) print (11) proc (2) procshort (8) psr (30) rave (8) rave2 (4) ravemono (2) realclaps (4) reverbkick (1) rm (2) rs (1) sax (22) sd (2) seawolf (3) sequential (8) sf (18) sheffield (1) short (5) sid (12) sine (6) sitar (8) sn (52) space (18) speakspell (12) speech (7) speechless (10) speedupdown (9) stab (23) stomp (10) subroc3d (11) sugar (2) sundance (6) tabla (26) tabla2 (46) tablex (3) tacscan (22) tech (13) techno (7) tink (5) tok (4) toys (13) trump (11) ul (10) ulgab (5) uxay (3) v (6) voodoo (5) wind (10) wobble (1) world (3) xmas (1) yeah (31)
*
* <a href="https://tidalcycles.org/docs/configuration/Audio%20Samples/default_library" target="_blank">more info</a>
*
* </details>
*
* @name s
* @param {string | Pattern} sound The sound / pattern of sounds to pick
* @example
@@ -28,20 +19,36 @@ const generic_params = [
*/
['s', 's', 'sound'],
/**
* The note or sample number to choose for a synth or sampleset
* Note names currently not working yet, but will hopefully soon. Just stick to numbers for now
* Selects the given index from the sample map.
* Numbers too high will wrap around.
* `n` can also be used to play midi numbers, but it is recommended to use `note` instead.
*
* @name n
* @param {string | number | Pattern} value note name, note number or sample number
* @param {number | Pattern} value sample index starting from 0
* @example
* s('superpiano').n("<0 1 2 3>").osc()
* @example
* s('superpiano').n("<c4 d4 e4 g4>").osc()
* @example
* n("0 1 2 3").s('east').osc()
* s("bd sd,hh*3").n("<0 1>")
*/
// also see https://github.com/tidalcycles/strudel/pull/63
['f', 'n', 'The note or sample number to choose for a synth or sampleset'],
['f', 'n', 'The sample number to choose for a synth or sampleset'],
/**
* Plays the given note name or midi number. A note name consists of
*
* - a letter (a-g or A-G)
* - optional accidentals (b or #)
* - optional octave number (0-9). Defaults to 3
*
* Examples of valid note names: `c`, `bb`, `Bb`, `f#`, `c3`, `A4`, `Eb2`, `c#5`
*
* You can also use midi numbers instead of note names, where 69 is mapped to A4 440Hz in 12EDO.
*
* @name note
* @example
* note("c a f e")
* @example
* note("c4 a4 f4 e4")
* @example
* note("60 69 65 64")
*/
['f', 'note', 'The note or pitch to play a sound or synth with'],
//['s', 'toArg', 'for internal sound routing'],
// ["f", "from", "for internal sound routing"),
@@ -386,6 +393,7 @@ const generic_params = [
*
* @name detune
* @param {number | Pattern} amount between 0 and 1
* @superdirtOnly
* @example
* n("0 3 7").s('superzow').octave(3).detune("<0 .25 .5 1 2>").osc()
*
@@ -398,6 +406,7 @@ const generic_params = [
* @param {number | Pattern} dry 0 = wet, 1 = dry
* @example
* n("[0,3,7](3,8)").s("superpiano").room(.7).dry("<0 .5 .75 1>").osc()
* @superdirtOnly
*
*/
[
@@ -456,6 +465,7 @@ const generic_params = [
* @param {number | Pattern} wet between 0 and 1
* @example
* n("0,4,7").s("supersquare").leslie("<0 .4 .6 1>").osc()
* @superdirtOnly
*
*/
['f', 'leslie', ''],
@@ -466,6 +476,7 @@ const generic_params = [
* @param {number | Pattern} rate 6.7 for fast, 0.7 for slow
* @example
* n("0,4,7").s("supersquare").leslie(1).lrate("<1 2 4 8>").osc()
* @superdirtOnly
*
*/
// TODO: the rate seems to "lag" (in the example, 1 will be fast)
@@ -477,6 +488,7 @@ const generic_params = [
* @param {number | Pattern} meters somewhere between 0 and 1
* @example
* n("0,4,7").s("supersquare").leslie(1).lrate(2).lsize("<.1 .5 1>").osc()
* @superdirtOnly
*
*/
['f', 'lsize', ''],
@@ -512,6 +524,7 @@ const generic_params = [
* @param {number | Pattern} octave octave number
* @example
* n("0,4,7").s('supersquare').octave("<3 4 5 6>").osc()
* @superDirtOnly
*/
['i', 'octave', ''],
['f', 'offset', ''], // TODO: what is this? not found in tidal doc
@@ -676,6 +689,7 @@ const generic_params = [
* @param {number | string | Pattern} unit see description above
* @example
* speed("1 2 .5 3").s("bd").unit("c").osc()
* @superdirtOnly
*
*/
[
@@ -692,6 +706,7 @@ const generic_params = [
* @param {number | Pattern} squiz Try passing multiples of 2 to it - 2, 4, 8 etc.
* @example
* squiz("2 4/2 6 [8 16]").s("bd").osc()
* @superdirtOnly
*
*/
['f', 'squiz', ''],
@@ -813,26 +828,26 @@ const generic_params = [
// TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13
const _name = (name, ...pats) => sequence(...pats).withValue((x) => ({ [name]: x }));
const _setter = (func, name) =>
function (...pats) {
const makeControl = function (name) {
const func = (...pats) => sequence(...pats).withValue((x) => ({ [name]: x }));
const setter = function (...pats) {
if (!pats.length) {
return this.fmap((value) => ({ [name]: value }));
}
return this.set(func(...pats));
};
Pattern.prototype[name] = setter;
registerControl(name, func);
return func;
};
generic_params.forEach(([type, name, description]) => {
controls[name] = (...pats) => _name(name, ...pats);
Pattern.prototype[name] = _setter(controls[name], name);
controls[name] = makeControl(name);
});
// create custom param
controls.createParam = (name) => {
const func = (...pats) => _name(name, ...pats);
Pattern.prototype[name] = _setter(func, name);
return (...pats) => _name(name, ...pats);
return makeControl(name);
};
controls.createParams = (...names) =>
+297 -69
View File
@@ -21,12 +21,148 @@ let stringParser;
// intended to use with mini to automatically interpret all strings as mini notation
export const setStringParser = (parser) => (stringParser = parser);
const alignments = ['in', 'out', 'mix', 'squeeze', 'squeezeout', 'trig', 'trigzero'];
const methodRegistry = [];
const getterRegistry = [];
const controlRegistry = [];
const controlSubscribers = [];
const composifiedRegistry = [];
//////////////////////////////////////////////////////////////////////
// Magic for supporting higher order composition of method chains
// Dresses the given (unary) function with methods for composition chaining, so e.g.
// `fast(2).iter(4)` composes to pattern functions into a new one.
function composify(func) {
if (!func.__composified) {
for (const [name, method] of methodRegistry) {
func[name] = method;
}
for (const [name, getter] of getterRegistry) {
Object.defineProperty(func, name, getter);
}
func.__composified = true;
composifiedRegistry.push(func);
} else {
console.log('Warning: attempt at composifying a function more than once');
}
return func;
}
export function registerMethod(name, addAlignments = false, addControls = false) {
if (addAlignments || addControls) {
// This method needs to make its 'this' object available to chained alignments and/or
// control parameters, so it has to be implemented as a getter
const getter = {
get: function () {
const func = this;
const wrapped = function (...args) {
const composed = (pat) => func(pat)[name](...args);
return composify(composed);
};
if (addAlignments) {
for (const alignment of alignments) {
wrapped[alignment] = function (...args) {
const composed = (pat) => func(pat)[name][alignment](...args);
return composify(composed);
};
for (const [controlname, controlfunc] of controlRegistry) {
wrapped[alignment][controlname] = function (...args) {
const composed = (pat) => func(pat)[name][alignment](controlfunc(...args));
return composify(composed);
};
}
}
}
if (addControls) {
for (const [controlname, controlfunc] of controlRegistry) {
wrapped[controlname] = function (...args) {
const composed = (pat) => func(pat)[name](controlfunc(...args));
return composify(composed);
};
}
}
return wrapped;
},
};
getterRegistry.push([name, getter]);
// Add to functions already 'composified'
for (const composified of composifiedRegistry) {
Object.defineProperty(composified, name, getter);
}
} else {
// No chained alignments/controls needed, so we can just add as a plain method,
// probably more efficient this way?
const method = function (...args) {
const func = this;
const composed = (pat) => func(pat)[name](...args);
return composify(composed);
};
methodRegistry.push([name, method]);
// Add to functions already 'composified'
for (const composified of composifiedRegistry) {
composified[name] = method;
}
}
}
export function registerControl(controlname, controlfunc) {
registerMethod(controlname);
controlRegistry.push([controlname, controlfunc]);
for (const subscriber of controlSubscribers) {
subscriber(controlname, controlfunc);
}
}
export function withControls(func) {
for (const [controlname, controlfunc] of controlRegistry) {
func(controlname, controlfunc);
}
controlSubscribers.push(func);
}
export function addToPrototype(name, func) {
Pattern.prototype[name] = func;
registerMethod(name);
}
export function curryPattern(func, arity = func.length) {
const fn = function curried(...args) {
if (args.length >= arity) {
return func.apply(this, args);
}
const partial = function (...args2) {
return curried.apply(this, args.concat(args2));
};
if (args.length == arity - 1) {
return composify(partial);
}
return partial;
};
if (arity == 1) {
composify(fn);
}
return fn;
}
//////////////////////////////////////////////////////////////////////
// The core Pattern class
/** @class Class representing a pattern. */
export class Pattern {
/**
* Create a pattern. As an end user, you will most likely not create a Pattern directly.
*
* @param {function} query - The function that maps a {@link State} to an array of {@link Hap}.
* @noAutocomplete
*/
constructor(query) {
this.query = query;
@@ -51,6 +187,7 @@ export class Pattern {
/**
* see {@link Pattern#withValue}
* @noAutocomplete
*/
fmap(func) {
return this.withValue(func);
@@ -62,6 +199,7 @@ export class Pattern {
* pattern of functions.
* @param {Function} whole_func
* @param {Function} func
* @noAutocomplete
* @returns Pattern
*/
appWhole(whole_func, pat_val) {
@@ -97,6 +235,7 @@ export class Pattern {
* are not the same but do intersect, the resulting hap has a timespan of the
* intersection. This applies to both the part and the whole timespan.
* @param {Pattern} pat_val
* @noAutocomplete
* @returns Pattern
*/
appBoth(pat_val) {
@@ -117,6 +256,7 @@ export class Pattern {
* are preserved from the pattern of functions (often referred to as the left
* hand or inner pattern).
* @param {Pattern} pat_val
* @noAutocomplete
* @returns Pattern
*/
appLeft(pat_val) {
@@ -147,6 +287,7 @@ export class Pattern {
* pattern of values, i.e. structure is preserved from the right hand/outer
* pattern.
* @param {Pattern} pat_val
* @noAutocomplete
* @returns Pattern
*/
appRight(pat_val) {
@@ -332,6 +473,7 @@ export class Pattern {
* const haps = pattern.queryArc(0, 1)
* console.log(haps)
* silence
* @noAutocomplete
*/
queryArc(begin, end) {
try {
@@ -347,6 +489,7 @@ export class Pattern {
* some calculations easier to express, as all haps are then constrained to
* happen within a cycle.
* @returns Pattern
* @noAutocomplete
*/
splitQueries() {
const pat = this;
@@ -361,6 +504,7 @@ export class Pattern {
* timespan before passing it to the original pattern.
* @param {Function} func the function to apply
* @returns Pattern
* @noAutocomplete
*/
withQuerySpan(func) {
return new Pattern((state) => this.query(state.withSpan(func)));
@@ -382,6 +526,7 @@ export class Pattern {
* begin and end time of the query timespan.
* @param {Function} func the function to apply
* @returns Pattern
* @noAutocomplete
*/
withQueryTime(func) {
return new Pattern((state) => this.query(state.withSpan((span) => span.withTime(func))));
@@ -393,6 +538,7 @@ export class Pattern {
* present, `whole` timespans).
* @param {Function} func
* @returns Pattern
* @noAutocomplete
*/
withHapSpan(func) {
return new Pattern((state) => this.query(state).map((hap) => hap.withSpan(func)));
@@ -403,6 +549,7 @@ export class Pattern {
* begin and end time of the hap timespans.
* @param {Function} func the function to apply
* @returns Pattern
* @noAutocomplete
*/
withHapTime(func) {
return this.withHapSpan((span) => span.withTime(func));
@@ -412,6 +559,7 @@ export class Pattern {
* Returns a new pattern with the given function applied to the list of haps returned by every query.
* @param {Function} func
* @returns Pattern
* @noAutocomplete
*/
withHaps(func) {
return new Pattern((state) => func(this.query(state)));
@@ -421,6 +569,7 @@ export class Pattern {
* As with {@link Pattern#withHaps}, but applies the function to every hap, rather than every list of haps.
* @param {Function} func
* @returns Pattern
* @noAutocomplete
*/
withHap(func) {
return this.withHaps((haps) => haps.map(func));
@@ -430,6 +579,7 @@ export class Pattern {
* Returns a new pattern with the context field set to every hap set to the given value.
* @param {*} context
* @returns Pattern
* @noAutocomplete
*/
setContext(context) {
return this.withHap((hap) => hap.setContext(context));
@@ -439,6 +589,7 @@ export class Pattern {
* Returns a new pattern with the given function applied to the context field of every hap.
* @param {Function} func
* @returns Pattern
* @noAutocomplete
*/
withContext(func) {
return this.withHap((hap) => hap.setContext(func(hap.context)));
@@ -447,6 +598,7 @@ export class Pattern {
/**
* Returns a new pattern with the context field of every hap set to an empty object.
* @returns Pattern
* @noAutocomplete
*/
stripContext() {
return this.withHap((hap) => hap.setContext({}));
@@ -458,6 +610,7 @@ export class Pattern {
* @param {Number} start
* @param {Number} end
* @returns Pattern
* @noAutocomplete
*/
withLocation(start, end) {
const location = {
@@ -500,6 +653,7 @@ export class Pattern {
* Returns a new Pattern, which only returns haps that meet the given test.
* @param {Function} hap_test - a function which returns false for haps to be removed from the pattern
* @returns Pattern
* @noAutocomplete
*/
filterHaps(hap_test) {
return new Pattern((state) => this.query(state).filter(hap_test));
@@ -510,6 +664,7 @@ export class Pattern {
* inside haps.
* @param {Function} value_test
* @returns Pattern
* @noAutocomplete
*/
filterValues(value_test) {
return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value)));
@@ -519,6 +674,7 @@ export class Pattern {
* Returns a new pattern, with haps containing undefined values removed from
* query results.
* @returns Pattern
* @noAutocomplete
*/
removeUndefineds() {
return this.filterValues((val) => val != undefined);
@@ -529,6 +685,7 @@ export class Pattern {
* with an onset is one with a `whole` timespan that begins at the same time
* as its `part` timespan.
* @returns Pattern
* @noAutocomplete
*/
onsetsOnly() {
// Returns a new pattern that will only return haps where the start
@@ -541,6 +698,7 @@ export class Pattern {
* Returns a new pattern, with 'continuous' haps (those without 'whole'
* timespans) removed from query results.
* @returns Pattern
* @noAutocomplete
*/
discreteOnly() {
// removes continuous haps that don't have a 'whole' timespan
@@ -550,6 +708,7 @@ export class Pattern {
/**
* Combines adjacent haps with the same value and whole. Only
* intended for use in tests.
* @noAutocomplete
*/
defragmentHaps() {
// remove continuous haps
@@ -604,6 +763,7 @@ export class Pattern {
* @param {Boolean} with_context - set to true, otherwise the context field
* will be stripped from the resulting haps.
* @returns [Hap]
* @noAutocomplete
*/
firstCycle(with_context = false) {
var self = this;
@@ -615,13 +775,17 @@ export class Pattern {
/**
* Accessor for a list of values returned by querying the first cycle.
* @noAutocomplete
*/
get firstCycleValues() {
return this.firstCycle().map((hap) => hap.value);
return this.sortHapsByPart()
.firstCycle()
.map((hap) => hap.value);
}
/**
* More human-readable version of the {@link Pattern#firstCycleValues} accessor.
* @noAutocomplete
*/
get showFirstCycle() {
return this.firstCycle().map(
@@ -633,6 +797,7 @@ export class Pattern {
* Returns a new pattern, which returns haps sorted in temporal order. Mainly
* of use when comparing two patterns for equality, in tests.
* @returns Pattern
* @noAutocomplete
*/
sortHapsByPart() {
return this.withHaps((haps) =>
@@ -665,7 +830,7 @@ export class Pattern {
const otherPat = reify(other);
return this.fmap((a) => otherPat.fmap((b) => func(a)(b))).squeezeJoin();
}
_opSqueezeOut(other, func) {
_opSqueezeout(other, func) {
const thisPat = this;
const otherPat = reify(other);
return otherPat.fmap((a) => thisPat.fmap((b) => func(b)(a))).squeezeJoin();
@@ -832,11 +997,11 @@ function groupHapsBy(eq, haps) {
const congruent = (a, b) => a.spanEquals(b);
// Pattern<Hap<T>> -> Pattern<Hap<T[]>>
// returned pattern contains arrays of congruent haps
Pattern.prototype.collect = function () {
addToPrototype('collect', function () {
return this.withHaps((haps) =>
groupHapsBy(congruent, haps).map((_haps) => new Hap(_haps[0].whole, _haps[0].part, _haps, {})),
);
};
});
/**
* Selects indices in in stacked notes.
@@ -844,12 +1009,12 @@ Pattern.prototype.collect = function () {
* note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>")
* .arpWith(haps => haps[2])
* */
Pattern.prototype.arpWith = function (func) {
addToPrototype('arpWith', function (func) {
return this.collect()
.fmap((v) => reify(func(v)))
.innerJoin()
.withHap((h) => new Hap(h.whole, h.part, h.value.value, h.combineContext(h.value)));
};
});
/**
* Selects indices in in stacked notes.
@@ -857,9 +1022,9 @@ Pattern.prototype.arpWith = function (func) {
* note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>")
* .arp("0 [0,2] 1 [0,2]").slow(2)
* */
Pattern.prototype.arp = function (pat) {
addToPrototype('arp', function (pat) {
return this.arpWith((haps) => pat.fmap((i) => haps[i % haps.length]));
};
});
//////////////////////////////////////////////////////////////////////
// compose matrix functions
@@ -957,15 +1122,15 @@ function _composeOp(a, b, func) {
func: [(a, b) => b(a)],
};
const hows = ['In', 'Out', 'Mix', 'Squeeze', 'SqueezeOut', 'Trig', 'Trigzero'];
const hows = alignments.map((x) => x.charAt(0).toUpperCase() + x.slice(1));
// generate methods to do what and how
for (const [what, [op, preprocess]] of Object.entries(composers)) {
// make plain version, e.g. pat._add(value) adds that plain value
// to all the values in pat
Pattern.prototype['_' + what] = function (value) {
addToPrototype('_' + what, function (value) {
return this.fmap((x) => op(x, value));
};
});
// make patternified monster version
Object.defineProperty(Pattern.prototype, what, {
@@ -979,7 +1144,7 @@ function _composeOp(a, b, func) {
// add methods to that function for each behaviour
for (const how of hows) {
wrapper[how.toLowerCase()] = function (...other) {
const howfunc = function (...other) {
var howpat = pat;
other = sequence(other);
if (preprocess) {
@@ -997,19 +1162,41 @@ function _composeOp(a, b, func) {
}
return result;
};
for (const [controlname, controlfunc] of controlRegistry) {
howfunc[controlname] = (...args) => howfunc(controlfunc(...args));
}
wrapper[how.toLowerCase()] = howfunc;
}
wrapper.squeezein = wrapper.squeeze;
for (const [controlname, controlfunc] of controlRegistry) {
wrapper[controlname] = (...args) => wrapper.in(controlfunc(...args));
}
return wrapper;
},
});
// Default op to 'set', e.g. pat.squeeze(pat2) = pat.set.squeeze(pat2)
for (const how of hows) {
Pattern.prototype[how.toLowerCase()] = function (...args) {
return this.set[how.toLowerCase()](args);
};
}
registerMethod(what, true, true);
}
// Default op to 'set', e.g. pat.squeeze(pat2) = pat.set.squeeze(pat2)
for (const howLower of alignments) {
// Using a 'get'ted function so that all the controls are added
Object.defineProperty(Pattern.prototype, howLower, {
get: function () {
const pat = this;
const howfunc = function (...args) {
return pat.set[howLower](args);
};
for (const [controlname, controlfunc] of controlRegistry) {
howfunc[controlname] = (...args) => howfunc(controlfunc(...args));
}
return howfunc;
},
});
registerMethod(howLower, false, true);
}
// binary composers
@@ -1021,36 +1208,36 @@ function _composeOp(a, b, func) {
* .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~")
* .slow(4)
*/
Pattern.prototype.struct = function (...args) {
addToPrototype('struct', function (...args) {
return this.keepif.out(...args);
};
Pattern.prototype.structAll = function (...args) {
});
addToPrototype('structAll', function (...args) {
return this.keep.out(...args);
};
});
/**
* Returns silence when mask is 0 or "~"
*
* @example
* note("c [eb,g] d [eb,g]").mask("<1 [0 1]>").slow(2)
*/
Pattern.prototype.mask = function (...args) {
addToPrototype('mask', function (...args) {
return this.keepif.in(...args);
};
Pattern.prototype.maskAll = function (...args) {
});
addToPrototype('maskAll', function (...args) {
return this.keep.in(...args);
};
});
/**
* Resets the pattern to the start of the cycle for each onset of the reset pattern.
*
* @example
* s("<bd lt> sd, hh*4").reset("<x@3 x(3,8)>")
*/
Pattern.prototype.reset = function (...args) {
addToPrototype('reset', function (...args) {
return this.keepif.trig(...args);
};
Pattern.prototype.resetAll = function (...args) {
});
addToPrototype('resetAll', function (...args) {
return this.keep.trig(...args);
};
});
/**
* Restarts the pattern for each onset of the restart pattern.
* While reset will only reset the current cycle, restart will start from cycle 0.
@@ -1058,12 +1245,12 @@ function _composeOp(a, b, func) {
* @example
* s("<bd lt> sd, hh*4").restart("<x@3 x(3,8)>")
*/
Pattern.prototype.restart = function (...args) {
addToPrototype('restart', function (...args) {
return this.keepif.trigzero(...args);
};
Pattern.prototype.restartAll = function (...args) {
});
addToPrototype('restartAll', function (...args) {
return this.keep.trigzero(...args);
};
});
})();
// aliases
@@ -1103,6 +1290,7 @@ export const silence = new Pattern(() => []);
* @returns {Pattern}
* @example
* pure('e4') // "e4"
* @noAutocomplete
*/
export function pure(value) {
function query(state) {
@@ -1307,42 +1495,75 @@ export function pm(...args) {
polymeter(...args);
}
export const mask = curry((a, b) => reify(b).mask(a));
export const struct = curry((a, b) => reify(b).struct(a));
export const superimpose = curry((a, b) => reify(b).superimpose(...a));
export const mask = curryPattern((a, b) => reify(b).mask(a));
export const struct = curryPattern((a, b) => reify(b).struct(a));
export const superimpose = curryPattern((a, b) => reify(b).superimpose(...a));
const methodToFunction = function (name, addAlignments = false) {
const func = curryPattern((a, b) => reify(b)[name](a));
withControls((controlname, controlfunc) => {
func[controlname] = function (...pats) {
return func(controlfunc(...pats));
};
});
if (addAlignments) {
for (const alignment of alignments) {
func[alignment] = curryPattern((a, b) => reify(b)[name][alignment](a));
withControls((controlname, controlfunc) => {
func[alignment][controlname] = function (...pats) {
return func[alignment](controlfunc(...pats));
};
});
}
}
return func;
};
// operators
export const set = curry((a, b) => reify(b).set(a));
export const keep = curry((a, b) => reify(b).keep(a));
export const keepif = curry((a, b) => reify(b).keepif(a));
export const add = curry((a, b) => reify(b).add(a));
export const sub = curry((a, b) => reify(b).sub(a));
export const mul = curry((a, b) => reify(b).mul(a));
export const div = curry((a, b) => reify(b).div(a));
export const mod = curry((a, b) => reify(b).mod(a));
export const pow = curry((a, b) => reify(b).pow(a));
export const band = curry((a, b) => reify(b).band(a));
export const bor = curry((a, b) => reify(b).bor(a));
export const bxor = curry((a, b) => reify(b).bxor(a));
export const blshift = curry((a, b) => reify(b).blshift(a));
export const brshift = curry((a, b) => reify(b).brshift(a));
export const lt = curry((a, b) => reify(b).lt(a));
export const gt = curry((a, b) => reify(b).gt(a));
export const lte = curry((a, b) => reify(b).lte(a));
export const gte = curry((a, b) => reify(b).gte(a));
export const eq = curry((a, b) => reify(b).eq(a));
export const eqt = curry((a, b) => reify(b).eqt(a));
export const ne = curry((a, b) => reify(b).ne(a));
export const net = curry((a, b) => reify(b).net(a));
export const and = curry((a, b) => reify(b).and(a));
export const or = curry((a, b) => reify(b).or(a));
export const func = curry((a, b) => reify(b).func(a));
export const set = methodToFunction('set', true);
export const keep = methodToFunction('keep', true);
export const keepif = methodToFunction('keepif', true);
export const add = methodToFunction('add', true);
export const sub = methodToFunction('sub', true);
export const mul = methodToFunction('mul', true);
export const div = methodToFunction('div', true);
export const mod = methodToFunction('mod', true);
export const pow = methodToFunction('pow', true);
export const band = methodToFunction('band', true);
export const bor = methodToFunction('bor', true);
export const bxor = methodToFunction('bxor', true);
export const blshift = methodToFunction('blshift', true);
export const brshift = methodToFunction('brshift', true);
export const lt = methodToFunction('lt', true);
export const gt = methodToFunction('gt', true);
export const lte = methodToFunction('lte', true);
export const gte = methodToFunction('gte', true);
export const eq = methodToFunction('eq', true);
export const eqt = methodToFunction('eqt', true);
export const ne = methodToFunction('ne', true);
export const net = methodToFunction('net', true);
export const and = methodToFunction('and', true);
export const or = methodToFunction('or', true);
export const func = methodToFunction('func', true);
// alignments
// export const in = methodToFunction('in'); // reserved word :(
export const out = methodToFunction('out');
export const mix = methodToFunction('mix');
export const squeeze = methodToFunction('squeeze');
export const squeezeout = methodToFunction('squeezeout');
export const trig = methodToFunction('trig');
export const trigzero = methodToFunction('trigzero');
/**
* Registers a new pattern method. The method is added to the Pattern class + the standalone function is returned from register.
*
* @param {string} name name of the function
* @param {function} func function with 1 or more params, where last is the current pattern
* @noAutocomplete
*
*/
export function register(name, func) {
@@ -1354,9 +1575,10 @@ export function register(name, func) {
return result;
}
const arity = func.length;
var pfunc; // the patternified function
pfunc = function (...args) {
registerMethod(name);
const pfunc = function (...args) {
args = args.map(reify);
const pat = args[args.length - 1];
if (arity === 1) {
@@ -1372,8 +1594,12 @@ export function register(name, func) {
.map((_, i) => args[i] ?? undefined);
return func(...args, pat);
};
mapFn = curry(mapFn, null, arity - 1);
return right.reduce((acc, p) => acc.appLeft(p), left.fmap(mapFn)).innerJoin();
mapFn = curryPattern(mapFn, arity - 1);
const app = (acc, p) => acc.appLeft(p);
const start = left.fmap(mapFn);
return right.reduce(app, start).innerJoin();
};
Pattern.prototype[name] = function (...args) {
@@ -1398,7 +1624,7 @@ export function register(name, func) {
// toplevel functions get curried as well as patternified
// because pfunc uses spread args, we need to state the arity explicitly!
return curry(pfunc, null, arity);
return curryPattern(pfunc, arity);
}
//////////////////////////////////////////////////////////////////////
@@ -1448,15 +1674,17 @@ export const ceil = register('ceil', function (pat) {
* Assumes a numerical pattern, containing unipolar values in the range 0 ..
* 1. Returns a new pattern with values scaled to the bipolar range -1 .. 1
* @returns Pattern
* @noAutocomplete
*/
export const toBipolar = register('toBipolar', function (pat) {
return pat.fmap((x) => x * 2 - 1);
});
/**
* Assumes a numerical pattern, containing bipolar values in the range -1 ..
* 1. Returns a new pattern with values scaled to the unipolar range 0 .. 1
* Assumes a numerical pattern, containing bipolar values in the range -1 .. 1
* Returns a new pattern with values scaled to the unipolar range 0 .. 1
* @returns Pattern
* @noAutocomplete
*/
export const fromBipolar = register('fromBipolar', function (pat) {
return pat.fmap((x) => (x + 1) / 2);
+25 -3
View File
@@ -45,6 +45,8 @@ import {
rev,
time,
run,
hitch,
set,
} from '../index.mjs';
import { steady } from '../signal.mjs';
@@ -204,7 +206,7 @@ describe('Pattern', () => {
),
);
});
it('can SqueezeOut() structure', () => {
it('can squeezeout() structure', () => {
sameFirst(
sequence(1, [2, 3]).add.squeezeout(10, 20, 30),
sequence([11, [12, 13]], [21, [22, 23]], [31, [32, 33]]),
@@ -252,7 +254,7 @@ describe('Pattern', () => {
),
);
});
it('can SqueezeOut() structure', () => {
it('can squeezeout() structure', () => {
sameFirst(sequence(1, [2, 3]).keep.squeezeout(10, 20, 30), sequence([1, [2, 3]], [1, [2, 3]], [1, [2, 3]]));
});
});
@@ -294,7 +296,7 @@ describe('Pattern', () => {
),
);
});
it('can SqueezeOut() structure', () => {
it('can squeezeout() structure', () => {
sameFirst(sequence(1, [2, 3]).keepif.squeezeout(true, true, false), sequence([1, [2, 3]], [1, [2, 3]], silence));
});
});
@@ -929,6 +931,14 @@ describe('Pattern', () => {
});
});
describe('alignments', () => {
it('Can combine controls', () => {
sameFirst(s('bd').set.in.n(3), s('bd').n(3));
sameFirst(s('bd').set.squeeze.n(3, 4), sequence(s('bd').n(3), s('bd').n(4)));
});
it('Can combine functions with alignmed controls', () => {
sameFirst(s('bd').apply(fast(2).set(n(3))), s('bd').fast(2).set.in.n(3));
sameFirst(s('bd').apply(fast(2).set.in.n(3)), s('bd').fast(2).set.in.n(3));
});
it('Can squeeze arguments', () => {
expect(sequence(1, 2).add.squeeze(4, 5).firstCycle()).toStrictEqual(sequence(5, 6, 6, 7).firstCycle());
});
@@ -959,4 +969,16 @@ describe('Pattern', () => {
sameFirst(s('a', 'b').hurry(2), s('a', 'b').fast(2).speed(2));
});
});
describe('composable functions', () => {
it('Can compose functions', () => {
sameFirst(sequence(3, 4).fast(2).rev().fast(2), fast(2).rev().fast(2)(sequence(3, 4)));
});
it('Can compose by method chaining operators with controls', () => {
sameFirst(s('bd').apply(set.n(3).fast(2)), s('bd').set.n(3).fast(2));
});
it('Can compose by method chaining operators and alignments with controls', () => {
sameFirst(s('bd').apply(set.in.n(3).fast(2)), s('bd').set.n(3).fast(2));
// sameFirst(s('bd').apply(set.squeeze.n(3).fast(2)), s('bd').set.squeeze.n(3).fast(2));
});
});
});
+3 -1
View File
@@ -58,6 +58,7 @@ export const valueToMidi = (value, fallbackValue) => {
/**
* @deprecated does not appear to be referenced or invoked anywhere in the codebase
* @noAutocomplete
*/
export const getFreq = (noteOrMidi) => {
if (typeof noteOrMidi === 'number') {
@@ -68,6 +69,7 @@ export const getFreq = (noteOrMidi) => {
/**
* @deprecated does not appear to be referenced or invoked anywhere in the codebase
* @noAutocomplete
*/
export const midi2note = (n) => {
const oct = Math.floor(n / 12) - 1;
@@ -137,7 +139,7 @@ export const removeUndefineds = (xs) => xs.filter((x) => x != undefined);
export const flatten = (arr) => [].concat(...arr);
export const id = (a) => a;
export const constant = (a, b) => a;
export const constant = curry((a, b) => a);
export const listRange = (min, max) => Array.from({ length: max - min + 1 }, (_, i) => i + min);
+2 -1
View File
@@ -30,4 +30,5 @@ note(`[[e5 [b4 c5] d5 [c5 b4]]
</strudel-repl>
```
Note that the Code is placed inside HTML comments to prevent the browser from treating it as HTML.
- Note that the Code is placed inside HTML comments to prevent the browser from treating it as HTML.
- [Play with this example on stackblitz](https://stackblitz.com/edit/js-75cvww?file=index.html)
+1
View File
@@ -39,6 +39,7 @@ let startedAt = -1;
/**
*
* Sends each hap as an OSC message, which can be picked up by SuperCollider or any other OSC-enabled software.
* For more info, read [MIDI & OSC in the docs](https://strudel.tidalcycles.org/learn/input-output)
*
* @name osc
* @memberof Pattern
+1
View File
@@ -32,6 +32,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@codemirror/autocomplete": "^6.4.0",
"@codemirror/lang-javascript": "^6.1.1",
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.7.3",
@@ -0,0 +1,77 @@
import { createRoot } from 'react-dom/client';
import jsdoc from '../../../../doc.json';
const getDocLabel = (doc) => doc.name || doc.longname;
const getInnerText = (html) => {
var div = document.createElement('div');
div.innerHTML = html;
return div.textContent || div.innerText || '';
};
export function Autocomplete({ doc }) {
return (
<div className="prose dark:prose-invert max-h-[400px] overflow-auto">
<h3 className="pt-0 mt-0">{getDocLabel(doc)}</h3>
<div dangerouslySetInnerHTML={{ __html: doc.description }} />
<ul>
{doc.params?.map(({ name, type, description }, i) => (
<li key={i}>
{name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)}</> : ''}
</li>
))}
</ul>
<div>
{doc.examples?.map((example, i) => (
<div key={i}>
<pre
className="cursor-pointer"
onMouseDown={(e) => {
console.log('ola!');
navigator.clipboard.writeText(example);
e.stopPropagation();
}}
>
{example}
</pre>
</div>
))}
</div>
</div>
);
}
const jsdocCompletions = jsdoc.docs
.filter(
(doc) =>
getDocLabel(doc) &&
!getDocLabel(doc).startsWith('_') &&
!['package'].includes(doc.kind) &&
!['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)),
)
// https://codemirror.net/docs/ref/#autocomplete.Completion
.map((doc) /*: Completion */ => ({
label: getDocLabel(doc),
// detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
info: () => {
const node = document.createElement('div');
// if Autocomplete is non-interactive, it could also be rendered at build time..
// .. using renderToStaticMarkup
createRoot(node).render(<Autocomplete doc={doc} />);
return node;
},
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
}));
export const strudelAutocomplete = (context /* : CompletionContext */) => {
let word = context.matchBefore(/\w*/);
if (word.from == word.to && !context.explicit) return null;
return {
from: word.from,
options: jsdocCompletions,
/* options: [
{ label: 'match', type: 'keyword' },
{ label: 'hello', type: 'variable', info: '(World)' },
{ label: 'magic', type: 'text', apply: '⠁⭒*.✩.*⭒⠁', detail: 'macro' },
], */
};
};
+23 -4
View File
@@ -6,6 +6,8 @@ import { javascript } from '@codemirror/lang-javascript';
import strudelTheme from '../themes/strudel-theme';
import './style.css';
import { useCallback } from 'react';
import { autocompletion } from '@codemirror/autocomplete';
import { strudelAutocomplete } from './Autocomplete';
export const setFlash = StateEffect.define();
const flashField = StateField.define({
@@ -49,11 +51,12 @@ const highlightField = StateField.define({
try {
for (let e of tr.effects) {
if (e.is(setHighlights)) {
const { haps } = e.value;
const marks =
e.value
haps
.map((hap) =>
(hap.context.locations || []).map(({ start, end }) => {
const color = hap.context.color || '#FFCA28';
const color = hap.context.color || e.value.color || '#FFCA28';
let from = tr.newDoc.line(start.line).from + start.column;
let to = tr.newDoc.line(end.line).from + end.column;
const l = tr.newDoc.length;
@@ -79,9 +82,24 @@ const highlightField = StateField.define({
provide: (f) => EditorView.decorations.from(f),
});
const extensions = [javascript(), strudelTheme, highlightField, flashField];
const extensions = [
javascript(),
highlightField,
flashField,
// javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }),
// autocompletion({ override: [strudelAutocomplete] }),
autocompletion({ override: [] }), // wait for https://github.com/uiwjs/react-codemirror/pull/458
];
export default function CodeMirror({ value, onChange, onViewChanged, onSelectionChange, options, editorDidMount }) {
export default function CodeMirror({
value,
onChange,
onViewChanged,
onSelectionChange,
theme,
options,
editorDidMount,
}) {
const handleOnChange = useCallback(
(value) => {
onChange?.(value);
@@ -106,6 +124,7 @@ export default function CodeMirror({ value, onChange, onViewChanged, onSelection
<>
<_CodeMirror
value={value}
theme={theme || strudelTheme}
onChange={handleOnChange}
onCreateEditor={handleOnCreateEditor}
onUpdate={handleOnUpdate}
+35 -12
View File
@@ -10,10 +10,21 @@ import { Icon } from './Icon';
import styles from './MiniRepl.module.css';
import './style.css';
import { logger } from '@strudel.cycles/core';
import useEvent from '../hooks/useEvent.mjs';
import useKeydown from '../hooks/useKeydown.mjs';
const getTime = () => getAudioContext().currentTime;
export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, drawTime, punchcard, canvasHeight = 200 }) {
export function MiniRepl({
tune,
hideOutsideView = false,
enableKeyboard,
drawTime,
punchcard,
canvasHeight = 200,
theme,
highlightColor,
}) {
drawTime = drawTime || (punchcard ? [0, 4] : undefined);
const evalOnMount = !!drawTime;
const drawContext = useCallback(
@@ -61,8 +72,30 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, drawTi
pattern,
active: started && !activeCode?.includes('strudel disable-highlighting'),
getTime: () => scheduler.now(),
color: highlightColor,
});
// keyboard shortcuts
useKeydown(
useCallback(
async (e) => {
if (view?.hasFocus) {
if (e.ctrlKey || e.altKey) {
if (e.code === 'Enter') {
e.preventDefault();
flash(view);
await activateCode();
} else if (e.code === 'Period') {
stop();
e.preventDefault();
}
}
}
},
[activateCode, stop, view],
),
);
// set active pattern on ctrl+enter
useLayoutEffect(() => {
if (enableKeyboard) {
@@ -111,7 +144,7 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, drawTi
{error && <div className={styles.error}>{error.message}</div>}
</div>
<div className={styles.body}>
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} />}
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} theme={theme} />}
</div>
{drawTime && (
<canvas
@@ -140,13 +173,3 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, drawTi
function useLogger(onTrigger) {
useEvent(logger.key, onTrigger);
}
// TODO: dedupe
function useEvent(name, onTrigger, useCapture = false) {
useEffect(() => {
document.addEventListener(name, onTrigger, useCapture);
return () => {
document.removeEventListener(name, onTrigger, useCapture);
};
}, [onTrigger]);
}
@@ -1,9 +1,9 @@
.container {
@apply rounded-md overflow-hidden bg-[#222222];
@apply overflow-hidden;
}
.header {
@apply flex justify-between bg-slate-700 border-t border-slate-500;
@apply flex justify-between bg-lineHighlight border-t border-l border-r border-lineHighlight rounded-t-md overflow-hidden;
}
.buttons {
@@ -11,11 +11,11 @@
}
.button {
@apply cursor-pointer w-16 flex items-center justify-center p-1 bg-slate-700 border-r border-slate-500 text-white hover:bg-slate-600;
@apply cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground hover:bg-background;
}
.buttonDisabled {
@apply w-16 flex items-center justify-center p-1 bg-slate-600 text-slate-400 cursor-not-allowed;
@apply w-16 flex items-center justify-center p-1 opacity-50 cursor-not-allowed border-r border-lineHighlight;
}
.error {
+3 -3
View File
@@ -5,10 +5,10 @@
font-size: 18px;
}
.cm-theme-light {
.cm-theme {
width: 100%;
}
.cm-line > * {
background: #00000095;
.cm-theme-light {
width: 100%;
}
+12
View File
@@ -0,0 +1,12 @@
import { useEffect } from 'react';
function useEvent(name, onTrigger, useCapture = false) {
useEffect(() => {
document.addEventListener(name, onTrigger, useCapture);
return () => {
document.removeEventListener(name, onTrigger, useCapture);
};
}, [onTrigger]);
}
export default useEvent;
+5 -5
View File
@@ -1,7 +1,7 @@
import { useEffect, useRef } from 'react';
import { setHighlights } from '../components/CodeMirror6';
function useHighlighting({ view, pattern, active, getTime }) {
function useHighlighting({ view, pattern, active, getTime, color }) {
const highlights = useRef([]);
const lastEnd = useRef(0);
useEffect(() => {
@@ -19,9 +19,9 @@ function useHighlighting({ view, pattern, active, getTime }) {
highlights.current = highlights.current.filter((hap) => hap.whole.end > audioTime); // keep only highlights that are still active
const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset());
highlights.current = highlights.current.concat(haps); // add potential new onsets
view.dispatch({ effects: setHighlights.of(highlights.current) }); // highlight all still active + new active haps
view.dispatch({ effects: setHighlights.of({ haps: highlights.current, color }) }); // highlight all still active + new active haps
} catch (err) {
view.dispatch({ effects: setHighlights.of([]) });
view.dispatch({ effects: setHighlights.of({ haps: [] }) });
}
frame = requestAnimationFrame(updateHighlights);
});
@@ -30,10 +30,10 @@ function useHighlighting({ view, pattern, active, getTime }) {
};
} else {
highlights.current = [];
view.dispatch({ effects: setHighlights.of([]) });
view.dispatch({ effects: setHighlights.of({ haps: [] }) });
}
}
}, [pattern, active, view]);
}, [pattern, active, view, color]);
}
export default useHighlighting;
+6 -4
View File
@@ -1,9 +1,11 @@
// import 'tailwindcss/tailwind.css';
export { default as CodeMirror, flash } from './components/CodeMirror6';
export * from './components/MiniRepl';
export { default as useHighlighting } from './hooks/useHighlighting';
export { default as CodeMirror, flash } from './components/CodeMirror6'; // !SSR
export * from './components/MiniRepl'; // !SSR
export { default as useHighlighting } from './hooks/useHighlighting'; // !SSR
export { default as useStrudel } from './hooks/useStrudel'; // !SSR
export { default as usePostMessage } from './hooks/usePostMessage';
export { default as useStrudel } from './hooks/useStrudel';
export { default as useKeydown } from './hooks/useKeydown';
export { default as useEvent } from './hooks/useEvent';
export { default as strudelTheme } from './themes/strudel-theme';
export { default as cx } from './cx';
-11
View File
@@ -1,11 +0,0 @@
import { describe, it, expect } from 'vitest';
import { Note } from '../tonleiter.mjs';
describe('tonleiter', () => {
it('Should tokenize notes', () => {
expect(Note.tokenize('c')).toEqual(['c', '']);
expect(Note.tokenize('C')).toEqual(['C', '']);
expect(Note.tokenize('c#')).toEqual(['c', '#']);
expect(Note.tokenize('Bb')).toEqual(['B', 'b']);
});
});
+6 -8
View File
@@ -123,17 +123,18 @@ export const scaleTranspose = register('scaleTranspose', function (offset /* : n
/**
* Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like {@link Pattern#scaleTranspose}.
*
* The scale name has the form "TO? N" wher
*
* - T = Tonic
* - O = Octave (optional, defaults to 3)
* - N = Name of scale, available names can be found [here](https://github.com/tonaljs/tonal/blob/main/packages/scale-type/data.ts).
* A scale consists of a root note (e.g. `c4`, `c`, `f#`, `bb4`) followed by a [scale type](https://github.com/tonaljs/tonal/blob/main/packages/scale-type/data.ts).
* The root note defaults to octave 3, if no octave number is given.
* Note that you currently cannot pattern `scale` with the mini notation, because the scale name includes a space.
* This will be improved in the future. Until then, use a sequence function like `cat` or `seq`.
*
* @memberof Pattern
* @name scale
* @param {string} scale Name of scale
* @returns Pattern
* @example
* "0 2 4 6 4 2".scale('C2 major').note()
* @example
* "0 2 4 6 4 2"
* .scale(seq('C2 major', 'C2 minor').slow(2))
* .note()
@@ -148,9 +149,6 @@ export const scale = register('scale', function (scale /* : string */, pat) {
let [tonic, scaleName] = Scale.tokenize(scale);
const { pc, oct = 3 } = Note.get(tonic);
note = scaleOffset(pc + ' ' + scaleName, asNumber, pc + oct);
/* const scaleWIthOctave = `${pc + oct} ${scaleName}`;
const degree = Scale.degrees(scaleWIthOctave);
note = degree(asNumber + (asNumber >= 0 ? 1 : 0)); */
}
return hap.withValue(() => (isObject ? { ...hap.value, note } : note)).setContext({ ...hap.context, scale });
});
-71
View File
@@ -1,71 +0,0 @@
const steps = [1, 0, 2, 0, 3, 4, 0, 5, 0, 6, 0, 7];
const notes = ['C', '', 'D', '', 'E', 'F', '', 'G', '', 'A', '', 'B'];
const noteLetters = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
const accidentalOffset = (accidentals) => {
return accidentals.split('#').length - accidentals.split('b').length;
};
const accidentalString = (offset) => {
if (offset < 0) {
return 'b'.repeat(-offset);
}
if (offset > 0) {
return '#'.repeat(offset);
}
return '';
};
export const Step = {
tokenize(step) {
const [accidentals, stepNumber] = step.match(/^([#b]*)([1-9]+)$/).slice(1);
return [accidentals, parseInt(stepNumber)];
},
accidentals(step) {
return accidentalOffset(Step.tokenize(step)[0]);
},
};
Step.tokenize('#11');
Step.tokenize('b13');
Step.tokenize('bb6');
Step.accidentals('3');
Step.accidentals('b3');
export const Note = {
// TODO: support octave numbers
tokenize(note) {
return [note[0], note.slice(1)];
},
accidentals(note) {
return accidentalOffset(this.tokenize(note)[1]);
},
};
Note.tokenize('C##');
Note.accidentals('C#');
Note.accidentals('C##');
Note.accidentals('Eb');
Note.accidentals('Bbb');
// TODO: support octave numbers
function transpose(note, step) {
// example: E, 3
const stepNumber = Step.tokenize(step)[1]; // 3
const noteLetter = Note.tokenize(note)[0]; // E
const noteIndex = noteLetters.indexOf(noteLetter); // 2 "E is C+2"
const targetNote = noteLetters[(noteIndex + stepNumber - 1) % 8]; // G "G is a third above E"
const rootIndex = notes.indexOf(noteLetter); // 4 "E is 4 semitones above C"
const targetIndex = notes.indexOf(targetNote); // 7 "G is 7 semitones above C"
const indexOffset = targetIndex - rootIndex; // 3 (E to G is normally a 3 semitones)
const stepIndex = steps.indexOf(stepNumber); // 4 ("3" is normally 4 semitones)
const offsetAccidentals = accidentalString(Step.accidentals(step) + Note.accidentals(note) + stepIndex - indexOffset); // "we need to add a # to to the G to make it a major third from E"
return [targetNote, offsetAccidentals].join('');
}
transpose('F#', '3');
transpose('C', '3');
transpose('D', '3');
transpose('E', '3');
transpose('Eb', '3');
transpose('Ebb', '3');
+3 -1
View File
@@ -111,7 +111,9 @@ let sampleCache = { current: undefined };
/**
* Loads a collection of samples to use with `s`
*
* @example
* samples('github:tidalcycles/Dirt-Samples/master');
* s("[bd ~]*2, [~ hh]*2, ~ sd")
* @example
* samples({
* bd: '808bd/BD0000.WAV',
+1
View File
@@ -59,6 +59,7 @@ export function loadWebDirt(config) {
* @returns Pattern
* @example
* s("bd*2 hh sd hh").n("<0 1>").webdirt()
* @noAutocomplete
*/
Pattern.prototype.webdirt = function () {
// create a WebDirt object and initialize Web Audio context
+280 -8
View File
@@ -159,6 +159,7 @@ importers:
packages/react:
specifiers:
'@codemirror/autocomplete': ^6.4.0
'@codemirror/lang-javascript': ^6.1.1
'@codemirror/state': ^6.2.0
'@codemirror/view': ^6.7.3
@@ -179,6 +180,7 @@ importers:
tailwindcss: ^3.0.24
vite: ^3.2.2
dependencies:
'@codemirror/autocomplete': 6.4.0_a4vbhepr4qhxm5cldqd4jpyase
'@codemirror/lang-javascript': 6.1.2
'@codemirror/state': 6.2.0
'@codemirror/view': 6.7.3
@@ -187,7 +189,7 @@ importers:
'@strudel.cycles/transpiler': link:../transpiler
'@strudel.cycles/webaudio': link:../webaudio
'@uiw/codemirror-themes': 4.19.7_a4vbhepr4qhxm5cldqd4jpyase
'@uiw/react-codemirror': 4.19.7_b6o5qp6ml4k7skggixohr5abde
'@uiw/react-codemirror': 4.19.7_r3x7zzmc35ug7i3c2vv4bf5iey
react-hook-inview: 4.5.0_sfoxds7t5ydpegc3knd667wn6m
devDependencies:
'@types/react': 17.0.53
@@ -373,6 +375,7 @@ importers:
'@types/node': ^18.0.0
'@types/react': ^18.0.26
'@types/react-dom': ^18.0.9
'@uiw/codemirror-themes-all': ^4.19.8
'@vite-pwa/astro': ^0.0.1
astro: ^1.7.2
canvas: ^2.11.0
@@ -384,6 +387,7 @@ importers:
react-dom: ^18.2.0
rehype-autolink-headings: ^6.1.1
rehype-slug: ^5.0.1
rehype-urls: ^1.1.1
remark-toc: ^8.0.1
tailwindcss: ^3.2.4
vite-plugin-pwa: ^0.14.1
@@ -415,6 +419,7 @@ importers:
'@types/node': 18.11.18
'@types/react': 18.0.27
'@types/react-dom': 18.0.10
'@uiw/codemirror-themes-all': 4.19.8
astro: 1.9.2_@types+node@18.11.18
canvas: 2.11.0
fraction.js: 4.2.0
@@ -424,6 +429,7 @@ importers:
react-dom: 18.2.0_react@18.2.0
rehype-autolink-headings: 6.1.1
rehype-slug: 5.1.0
rehype-urls: 1.1.1
remark-toc: 8.0.1
tailwindcss: 3.2.4
devDependencies:
@@ -3979,9 +3985,10 @@ packages:
eslint-visitor-keys: 3.3.0
dev: false
/@uiw/codemirror-extensions-basic-setup/4.19.7_a4vbhepr4qhxm5cldqd4jpyase:
/@uiw/codemirror-extensions-basic-setup/4.19.7_cgfc5aojxuwjajwhkrgidrzxoa:
resolution: {integrity: sha512-pk0JTFJAZOGxouBB1pdBtb59qKibO9DW4hER4VSFGBGW3TBDeXUwL/gKujhRpySHG2MtG09cgt4a3XOFIWwXTw==}
peerDependencies:
'@codemirror/autocomplete': '>=6.0.0'
'@codemirror/state': '>=6.0.0'
'@codemirror/view': '>=6.0.0'
dependencies:
@@ -3994,6 +4001,173 @@ packages:
'@codemirror/view': 6.7.3
dev: false
/@uiw/codemirror-theme-abcdef/4.19.8:
resolution: {integrity: sha512-sR7srfMJRGTsAFQYEs+8IogUxUHDsMqNqZ4waIA6rNIvOH9Q1NYFEqTpqf4AeivkNeXm3N1LI+1nQAKhWzWpOA==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-androidstudio/4.19.8:
resolution: {integrity: sha512-xhvEPZ1NvihIJVx7MA1Z8A8NwaTbM6mtBsfwM3mKAmzW6K6JcIJftcKe5z5wSPZk4J3S66vq8RYEMOBygSOdEQ==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-atomone/4.19.8:
resolution: {integrity: sha512-enZR8RPiKohyuEU4DOWA7A/xLs/+NsuvZAuGmg5PU43CQITzK1Wqri43/WxkGGsEIztejX439s1QuYxUwCCeUg==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-aura/4.19.8:
resolution: {integrity: sha512-DLoi2XEPNBD6Cxsp3v/nYw7jJ7sDEXET9+C43KZnk1dQ9H5e0HSpZhOkw1xmaCGzoXn9q7HInPa7GgUEMv8NPA==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-bbedit/4.19.8:
resolution: {integrity: sha512-1vaVaVhMP2IB7H77W9yvPRhVr+8HGBWPDfFEVWAc7tfeU0xC2MFby05gO/7T6K2JWyj2lXnVw/i+oFNeDdcekQ==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-bespin/4.19.8:
resolution: {integrity: sha512-JyzmgCloEWhduH5GVYzHQ3vPc3zC0YVRiu7lOjgdOVRhr49M6KmwTytmbmqXbaqmSZKFUJqiHjz8VswqcNC4dw==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-darcula/4.19.8:
resolution: {integrity: sha512-fPKyzMqCtBfez2ubC/77AP0BfE+XOio+sy8oRYfX3HqRQEivnnEddQJnTZVz0kKSLYSryQHbOMw/qwgInPvBCA==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-dracula/4.19.8:
resolution: {integrity: sha512-YHQJUDnYKrew/YkEPQG3SQFcnKr6MzVbIXxmdKuxlCTrySNFNMK19dKrzY3fwc+Lgpp5r26QLLocTZyCvu1xeA==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-duotone/4.19.8:
resolution: {integrity: sha512-SJkjyKxU2Nazv6uEctvRdz62/ZQ8rBP4xtXrjF/zcjOdWmx2ecUAGn/j5Euw5V55Dr74yIQFs0L+GB5UTHCHDA==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-eclipse/4.19.8:
resolution: {integrity: sha512-6XXzYFC809e5MWT/NNTqoZ0N9H6P8mXwIEGDfigh33VU1TJPPpO1LCWdf59VcXx/xIY0ctDp7rU7hTy3f3tHkw==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-github/4.19.8:
resolution: {integrity: sha512-RUAzHGkG2G2PXTxsH4KbKVCKqPs/+H6xvhCT3Q4YiPj1W2aGSfPwZZ8cLLCm9dYaIh/+H6L+AcoiUtvADjyKsQ==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-gruvbox-dark/4.19.8:
resolution: {integrity: sha512-EQepnqhfcu7HY2uutigzflZ9OFzDcF9eq4xF53UylD+WZBdwq2qarZMQgBfOXZZKYI3XcTny064fa7Xxklhs1g==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-material/4.19.8:
resolution: {integrity: sha512-dm7FtZkJSZ4AV+9vXxKUDnXLTHaqWnsmn9HOb6AmLiHmHcLFLDgeosSBnA80fwXBhd6QswJI0cEnKHAvcC/vFg==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-noctis-lilac/4.19.8:
resolution: {integrity: sha512-vPH8KOiRgcBK1V7/BN04n8K03cfR4CEmOGZUMKpcLlSZwWRe0+JPk6S2qtjbJ072Fsn1ZrcLRYrqtaAJQNOVnw==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-nord/4.19.8:
resolution: {integrity: sha512-qBqKJI9fa35rFefXqaiw3FCXWSpD+65+Weji3g/AgB0m0+MHG+3rkC+rLZ4PE2QpkcMz6wHj89EMwOW9W1i3cA==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-okaidia/4.19.8:
resolution: {integrity: sha512-gxEThgbbfIxKjk7UzD0D8gw5bVpgxDct3e6r9/WegZw7tTvTHMnz9wutGbJp2cYSwdgLHoQJZzZBi6jKhExZSg==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-solarized/4.19.8:
resolution: {integrity: sha512-xHp2i6zWBMw2+kZX0GUYi7pCL3ZL1qWSO8OaleHCkpY3jn+lfLSJOAYYkajvNyu3tPsiKQuAgpCQ6AYFWOr6BQ==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-sublime/4.19.8:
resolution: {integrity: sha512-tapkCty8lwpRw2CAY5bPiK8S2ptdTL4d1X/IkCVyHqbC0B8728iQJu3E4Rrka7yggFx4QGUpyZB9UTIFYURtsw==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-tokyo-night-day/4.19.8:
resolution: {integrity: sha512-HDKp2KDjluubpe+7M31SohWUFCW7wGQIvoD60xr86OkPZDdqsCVUFmna6rqfttV6S2OsgVNJPBzZvOF398Eapg==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-tokyo-night-storm/4.19.8:
resolution: {integrity: sha512-Kp8HIf1H+pgbRx/ZhRsqqN5qfyx3SVVfjnUPGltsdBymqh8QGdr7LC8Tp0P5+Yjb6CqF6Dgx7RnQDj1XZ94rbA==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-tokyo-night/4.19.8:
resolution: {integrity: sha512-LrfB+EgZqBNlMF6Zqk8RrlsfVzKe6Ple7ckvREV6kSuwxncF5FlhzVL3cWgxNfNQ7TJ5ySVSvw4fWOcxIcc1LQ==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-vscode/4.19.8:
resolution: {integrity: sha512-UdUuJfNjG2y88taP5gsEA9AQlhRTURnl+9kl+LOJkQtXavrqp3J8gUAHRhEWxN7k8q8YimsSVcQ4yw0FKAgXQQ==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-xcode/4.19.8:
resolution: {integrity: sha512-VYCRhStYujUNGIkiaZZ1o5OO2Z2edsKkYCCYSy0rwb4G17vT6sd4YDnmJQuF0gzqQkTIdDxSb6aCM9ISkPABlg==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-themes-all/4.19.8:
resolution: {integrity: sha512-0kAWabe46Vkz+gwNfaXvn/0adeUSCTAuZC0d7Nu8ebcr1TjT1fDLFNZIXlwidn40suiXq8FDfZ9euArx3RPg0Q==}
dependencies:
'@uiw/codemirror-theme-abcdef': 4.19.8
'@uiw/codemirror-theme-androidstudio': 4.19.8
'@uiw/codemirror-theme-atomone': 4.19.8
'@uiw/codemirror-theme-aura': 4.19.8
'@uiw/codemirror-theme-bbedit': 4.19.8
'@uiw/codemirror-theme-bespin': 4.19.8
'@uiw/codemirror-theme-darcula': 4.19.8
'@uiw/codemirror-theme-dracula': 4.19.8
'@uiw/codemirror-theme-duotone': 4.19.8
'@uiw/codemirror-theme-eclipse': 4.19.8
'@uiw/codemirror-theme-github': 4.19.8
'@uiw/codemirror-theme-gruvbox-dark': 4.19.8
'@uiw/codemirror-theme-material': 4.19.8
'@uiw/codemirror-theme-noctis-lilac': 4.19.8
'@uiw/codemirror-theme-nord': 4.19.8
'@uiw/codemirror-theme-okaidia': 4.19.8
'@uiw/codemirror-theme-solarized': 4.19.8
'@uiw/codemirror-theme-sublime': 4.19.8
'@uiw/codemirror-theme-tokyo-night': 4.19.8
'@uiw/codemirror-theme-tokyo-night-day': 4.19.8
'@uiw/codemirror-theme-tokyo-night-storm': 4.19.8
'@uiw/codemirror-theme-vscode': 4.19.8
'@uiw/codemirror-theme-xcode': 4.19.8
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-themes/4.19.7_a4vbhepr4qhxm5cldqd4jpyase:
resolution: {integrity: sha512-M/42RkPI60ItlssmNuEoZO2MQvlY6fRmdX7XRUAhKjxczZoaq8xS6HIvv1whGf2zGsTrwdVTPCm6ls0l17dvPA==}
peerDependencies:
@@ -4005,7 +4179,15 @@ packages:
'@codemirror/view': 6.7.3
dev: false
/@uiw/react-codemirror/4.19.7_b6o5qp6ml4k7skggixohr5abde:
/@uiw/codemirror-themes/4.19.8:
resolution: {integrity: sha512-k+0molX6YuQZ7vYymS5DEl3NcHxVE4VP4J0nB7RBnzLrhDwT2K8R5ie7J2eH+4bpppIK1ZZdZ6Mie7U48dH5dQ==}
dependencies:
'@codemirror/language': 6.4.0
'@codemirror/state': 6.2.0
'@codemirror/view': 6.7.3
dev: false
/@uiw/react-codemirror/4.19.7_r3x7zzmc35ug7i3c2vv4bf5iey:
resolution: {integrity: sha512-IHvpYWVSdiaHX0Fk6oY6YyAJigDnyvSpWKNUTRzsMNxB+8/wqZ8lior4TprXH0zyLxW5F1+bTyifFFTeg+X3Sw==}
peerDependencies:
'@codemirror/state': '>=6.0.0'
@@ -4018,10 +4200,12 @@ packages:
'@codemirror/state': 6.2.0
'@codemirror/theme-one-dark': 6.1.0
'@codemirror/view': 6.7.3
'@uiw/codemirror-extensions-basic-setup': 4.19.7_a4vbhepr4qhxm5cldqd4jpyase
'@uiw/codemirror-extensions-basic-setup': 4.19.7_cgfc5aojxuwjajwhkrgidrzxoa
codemirror: 6.0.1
react: 17.0.2
react-dom: 17.0.2_react@17.0.2
transitivePeerDependencies:
- '@codemirror/autocomplete'
dev: false
/@vite-pwa/astro/0.0.1:
@@ -7250,6 +7434,10 @@ packages:
web-namespaces: 2.0.1
dev: false
/hast-util-has-property/1.0.4:
resolution: {integrity: sha512-ghHup2voGfgFoHMGnaLHOjbYFACKrRh9KFttdCzMCbFoBMJXiNi2+XTrPP8+q6cDJM/RSqlCfVWrjp1H201rZg==}
dev: false
/hast-util-has-property/2.0.1:
resolution: {integrity: sha512-X2+RwZIMTMKpXUzlotatPzWj8bspCymtXH3cfG3iQKV+wPF53Vgaqxi/eLqGck0wKq1kS9nvoB1wchbCPEL8sg==}
dev: false
@@ -7593,6 +7781,10 @@ packages:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
dev: true
/is-arrayish/0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
dev: false
/is-bigint/1.0.4:
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
dependencies:
@@ -10221,6 +10413,17 @@ packages:
- supports-color
dev: true
/postcss-import/14.1.0:
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
engines: {node: '>=10.0.0'}
peerDependencies:
postcss: ^8.0.0
dependencies:
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.1
dev: false
/postcss-import/14.1.0_postcss@8.4.21:
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
engines: {node: '>=10.0.0'}
@@ -10231,6 +10434,16 @@ packages:
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.1
dev: true
/postcss-js/4.0.0:
resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
engines: {node: ^12 || ^14 || >= 16}
peerDependencies:
postcss: ^8.3.3
dependencies:
camelcase-css: 2.0.1
dev: false
/postcss-js/4.0.0_postcss@8.4.21:
resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
@@ -10240,6 +10453,23 @@ packages:
dependencies:
camelcase-css: 2.0.1
postcss: 8.4.21
dev: true
/postcss-load-config/3.1.4:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
engines: {node: '>= 10'}
peerDependencies:
postcss: '>=8.0.9'
ts-node: '>=9.0.0'
peerDependenciesMeta:
postcss:
optional: true
ts-node:
optional: true
dependencies:
lilconfig: 2.0.6
yaml: 1.10.2
dev: false
/postcss-load-config/3.1.4_postcss@8.4.21:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
@@ -10257,6 +10487,15 @@ packages:
postcss: 8.4.21
yaml: 1.10.2
/postcss-nested/6.0.0:
resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
engines: {node: '>=12.0'}
peerDependencies:
postcss: ^8.2.14
dependencies:
postcss-selector-parser: 6.0.11
dev: false
/postcss-nested/6.0.0_postcss@8.4.21:
resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
engines: {node: '>=12.0'}
@@ -10265,6 +10504,7 @@ packages:
dependencies:
postcss: 8.4.21
postcss-selector-parser: 6.0.11
dev: true
/postcss-selector-parser/6.0.10:
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
@@ -10880,6 +11120,14 @@ packages:
unified: 10.1.2
dev: false
/rehype-urls/1.1.1:
resolution: {integrity: sha512-ct9Kb/nAL6oe/O5fDc0xjiqm8Z9xgXdorOdDhZAWx7awucyiuYXU7Dax+23Gu24nnGwtdaCW6zslKAYzlEW1lw==}
dependencies:
hast-util-has-property: 1.0.4
stdopt: 2.2.0
unist-util-visit: 1.4.1
dev: false
/rehype/12.0.1:
resolution: {integrity: sha512-ey6kAqwLM3X6QnMDILJthGvG1m1ULROS9NT4uG9IDCuv08SFyLlreSuvOa//DgEvbXx62DS6elGVqusWhRUbgw==}
dependencies:
@@ -11609,6 +11857,12 @@ packages:
tslib: 2.5.0
dev: false
/stdopt/2.2.0:
resolution: {integrity: sha512-D/p41NgXOkcj1SeGhfXOwv9z1K6EV3sjAUY5aeepVbgEHv7DpKWLTjhjScyzMWAQCAgUQys1mjH0eArm4cjRGw==}
dependencies:
is-arrayish: 0.3.2
dev: false
/stream-connect/1.0.2:
resolution: {integrity: sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==}
engines: {node: '>=0.10.0'}
@@ -11873,6 +12127,8 @@ packages:
resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==}
engines: {node: '>=12.13.0'}
hasBin: true
peerDependencies:
postcss: ^8.0.9
dependencies:
arg: 5.0.2
chokidar: 3.5.3
@@ -11889,10 +12145,10 @@ packages:
object-hash: 3.0.0
picocolors: 1.0.0
postcss: 8.4.21
postcss-import: 14.1.0_postcss@8.4.21
postcss-js: 4.0.0_postcss@8.4.21
postcss-load-config: 3.1.4_postcss@8.4.21
postcss-nested: 6.0.0_postcss@8.4.21
postcss-import: 14.1.0
postcss-js: 4.0.0
postcss-load-config: 3.1.4
postcss-nested: 6.0.0
postcss-selector-parser: 6.0.11
postcss-value-parser: 4.2.0
quick-lru: 5.1.1
@@ -12475,6 +12731,10 @@ packages:
resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==}
dev: false
/unist-util-is/3.0.0:
resolution: {integrity: sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==}
dev: false
/unist-util-is/5.2.0:
resolution: {integrity: sha512-Glt17jWwZeyqrFqOK0pF1Ded5U3yzJnFr8CG1GMjCWTp9zDo2p+cmD6pWbZU8AgM5WU3IzRv6+rBwhzsGh6hBQ==}
dev: false
@@ -12523,6 +12783,12 @@ packages:
'@types/unist': 2.0.6
dev: false
/unist-util-visit-parents/2.1.2:
resolution: {integrity: sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==}
dependencies:
unist-util-is: 3.0.0
dev: false
/unist-util-visit-parents/5.1.3:
resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==}
dependencies:
@@ -12530,6 +12796,12 @@ packages:
unist-util-is: 5.2.0
dev: false
/unist-util-visit/1.4.1:
resolution: {integrity: sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==}
dependencies:
unist-util-visit-parents: 2.1.2
dev: false
/unist-util-visit/4.1.2:
resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==}
dependencies:
+137 -34
View File
@@ -2506,40 +2506,26 @@ exports[`runs examples > example "mul" example index 0 1`] = `
exports[`runs examples > example "n" example index 0 1`] = `
[
"[ 0/1 → 1/1 | s:superpiano n:0 ]",
"[ 1/12/1 | s:superpiano n:1 ]",
"[ 2/1 → 3/1 | s:superpiano n:2 ]",
"[ 3/1 → 4/1 | s:superpiano n:3 ]",
]
`;
exports[`runs examples > example "n" example index 1 1`] = `
[
"[ 0/11/1 | s:superpiano n:c4 ]",
"[ 1/1 → 2/1 | s:superpiano n:d4 ]",
"[ 2/1 → 3/1 | s:superpiano n:e4 ]",
"[ 3/1 → 4/1 | s:superpiano n:g4 ]",
]
`;
exports[`runs examples > example "n" example index 2 1`] = `
[
"[ 0/1 → 1/4 | n:0 s:east ]",
"[ 1/4 → 1/2 | n:1 s:east ]",
"[ 1/2 → 3/4 | n:2 s:east ]",
"[ 3/4 → 1/1 | n:3 s:east ]",
"[ 1/1 → 5/4 | n:0 s:east ]",
"[ 5/4 → 3/2 | n:1 s:east ]",
"[ 3/2 → 7/4 | n:2 s:east ]",
"[ 7/4 → 2/1 | n:3 s:east ]",
"[ 2/1 → 9/4 | n:0 s:east ]",
"[ 9/4 → 5/2 | n:1 s:east ]",
"[ 5/2 → 11/4 | n:2 s:east ]",
"[ 11/4 → 3/1 | n:3 s:east ]",
"[ 3/1 → 13/4 | n:0 s:east ]",
"[ 13/4 → 7/2 | n:1 s:east ]",
"[ 7/2 → 15/4 | n:2 s:east ]",
"[ 15/4 → 4/1 | n:3 s:east ]",
"[ 0/1 → 1/2 | s:bd n:0 ]",
"[ 1/21/1 | s:sd n:0 ]",
"[ 0/1 → 1/3 | s:hh n:0 ]",
"[ 1/3 → 2/3 | s:hh n:0 ]",
"[ 2/3 → 1/1 | s:hh n:0 ]",
"[ 1/1 → 3/2 | s:bd n:1 ]",
"[ 3/2 → 2/1 | s:sd n:1 ]",
"[ 1/1 → 4/3 | s:hh n:1 ]",
"[ 4/3 → 5/3 | s:hh n:1 ]",
"[ 5/32/1 | s:hh n:1 ]",
"[ 2/1 → 5/2 | s:bd n:0 ]",
"[ 5/2 → 3/1 | s:sd n:0 ]",
"[ 2/1 → 7/3 | s:hh n:0 ]",
"[ 7/3 → 8/3 | s:hh n:0 ]",
"[ 8/3 → 3/1 | s:hh n:0 ]",
"[ 3/1 → 7/2 | s:bd n:1 ]",
"[ 7/2 → 4/1 | s:sd n:1 ]",
"[ 3/1 → 10/3 | s:hh n:1 ]",
"[ 10/3 → 11/3 | s:hh n:1 ]",
"[ 11/3 → 4/1 | s:hh n:1 ]",
]
`;
@@ -2580,6 +2566,69 @@ exports[`runs examples > example "never" example index 0 1`] = `
]
`;
exports[`runs examples > example "note" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c ]",
"[ 1/4 → 1/2 | note:a ]",
"[ 1/2 → 3/4 | note:f ]",
"[ 3/4 → 1/1 | note:e ]",
"[ 1/1 → 5/4 | note:c ]",
"[ 5/4 → 3/2 | note:a ]",
"[ 3/2 → 7/4 | note:f ]",
"[ 7/4 → 2/1 | note:e ]",
"[ 2/1 → 9/4 | note:c ]",
"[ 9/4 → 5/2 | note:a ]",
"[ 5/2 → 11/4 | note:f ]",
"[ 11/4 → 3/1 | note:e ]",
"[ 3/1 → 13/4 | note:c ]",
"[ 13/4 → 7/2 | note:a ]",
"[ 7/2 → 15/4 | note:f ]",
"[ 15/4 → 4/1 | note:e ]",
]
`;
exports[`runs examples > example "note" example index 1 1`] = `
[
"[ 0/1 → 1/4 | note:c4 ]",
"[ 1/4 → 1/2 | note:a4 ]",
"[ 1/2 → 3/4 | note:f4 ]",
"[ 3/4 → 1/1 | note:e4 ]",
"[ 1/1 → 5/4 | note:c4 ]",
"[ 5/4 → 3/2 | note:a4 ]",
"[ 3/2 → 7/4 | note:f4 ]",
"[ 7/4 → 2/1 | note:e4 ]",
"[ 2/1 → 9/4 | note:c4 ]",
"[ 9/4 → 5/2 | note:a4 ]",
"[ 5/2 → 11/4 | note:f4 ]",
"[ 11/4 → 3/1 | note:e4 ]",
"[ 3/1 → 13/4 | note:c4 ]",
"[ 13/4 → 7/2 | note:a4 ]",
"[ 7/2 → 15/4 | note:f4 ]",
"[ 15/4 → 4/1 | note:e4 ]",
]
`;
exports[`runs examples > example "note" example index 2 1`] = `
[
"[ 0/1 → 1/4 | note:60 ]",
"[ 1/4 → 1/2 | note:69 ]",
"[ 1/2 → 3/4 | note:65 ]",
"[ 3/4 → 1/1 | note:64 ]",
"[ 1/1 → 5/4 | note:60 ]",
"[ 5/4 → 3/2 | note:69 ]",
"[ 3/2 → 7/4 | note:65 ]",
"[ 7/4 → 2/1 | note:64 ]",
"[ 2/1 → 9/4 | note:60 ]",
"[ 9/4 → 5/2 | note:69 ]",
"[ 5/2 → 11/4 | note:65 ]",
"[ 11/4 → 3/1 | note:64 ]",
"[ 3/1 → 13/4 | note:60 ]",
"[ 13/4 → 7/2 | note:69 ]",
"[ 7/2 → 15/4 | note:65 ]",
"[ 15/4 → 4/1 | note:64 ]",
]
`;
exports[`runs examples > example "octave" example index 0 1`] = `
[
"[ 0/1 → 1/1 | n:0 s:supersquare octave:3 ]",
@@ -3280,6 +3329,31 @@ exports[`runs examples > example "samples" example index 0 1`] = `
]
`;
exports[`runs examples > example "samples" example index 1 1`] = `
[
"[ 0/1 → 1/4 | s:bd ]",
"[ 1/2 → 3/4 | s:bd ]",
"[ 1/4 → 1/2 | s:hh ]",
"[ 3/4 → 1/1 | s:hh ]",
"[ 1/2 → 1/1 | s:sd ]",
"[ 1/1 → 5/4 | s:bd ]",
"[ 3/2 → 7/4 | s:bd ]",
"[ 5/4 → 3/2 | s:hh ]",
"[ 7/4 → 2/1 | s:hh ]",
"[ 3/2 → 2/1 | s:sd ]",
"[ 2/1 → 9/4 | s:bd ]",
"[ 5/2 → 11/4 | s:bd ]",
"[ 9/4 → 5/2 | s:hh ]",
"[ 11/4 → 3/1 | s:hh ]",
"[ 5/2 → 3/1 | s:sd ]",
"[ 3/1 → 13/4 | s:bd ]",
"[ 7/2 → 15/4 | s:bd ]",
"[ 13/4 → 7/2 | s:hh ]",
"[ 15/4 → 4/1 | s:hh ]",
"[ 7/2 → 4/1 | s:sd ]",
]
`;
exports[`runs examples > example "saw" example index 0 1`] = `
[
"[ 0/1 → 1/32 | note:c3 ]",
@@ -3323,6 +3397,35 @@ exports[`runs examples > example "saw" example index 1 1`] = `
`;
exports[`runs examples > example "scale" example index 0 1`] = `
[
"[ 0/1 → 1/6 | note:C2 ]",
"[ 1/6 → 1/3 | note:E2 ]",
"[ 1/3 → 1/2 | note:G2 ]",
"[ 1/2 → 2/3 | note:B2 ]",
"[ 2/3 → 5/6 | note:G2 ]",
"[ 5/6 → 1/1 | note:E2 ]",
"[ 1/1 → 7/6 | note:C2 ]",
"[ 7/6 → 4/3 | note:E2 ]",
"[ 4/3 → 3/2 | note:G2 ]",
"[ 3/2 → 5/3 | note:B2 ]",
"[ 5/3 → 11/6 | note:G2 ]",
"[ 11/6 → 2/1 | note:E2 ]",
"[ 2/1 → 13/6 | note:C2 ]",
"[ 13/6 → 7/3 | note:E2 ]",
"[ 7/3 → 5/2 | note:G2 ]",
"[ 5/2 → 8/3 | note:B2 ]",
"[ 8/3 → 17/6 | note:G2 ]",
"[ 17/6 → 3/1 | note:E2 ]",
"[ 3/1 → 19/6 | note:C2 ]",
"[ 19/6 → 10/3 | note:E2 ]",
"[ 10/3 → 7/2 | note:G2 ]",
"[ 7/2 → 11/3 | note:B2 ]",
"[ 11/3 → 23/6 | note:G2 ]",
"[ 23/6 → 4/1 | note:E2 ]",
]
`;
exports[`runs examples > example "scale" example index 1 1`] = `
[
"[ 0/1 → 1/6 | note:C2 ]",
"[ 1/6 → 1/3 | note:E2 ]",
+27 -14
View File
@@ -1,24 +1,47 @@
import { defineConfig } from 'astro/config';
import preact from '@astrojs/preact';
import react from '@astrojs/react';
import mdx from '@astrojs/mdx';
import remarkToc from 'remark-toc';
import rehypeSlug from 'rehype-slug';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import rehypeUrls from 'rehype-urls';
import tailwind from '@astrojs/tailwind';
import AstroPWA from '@vite-pwa/astro';
// import { visualizer } from 'rollup-plugin-visualizer';
const site = `https://strudel.tidalcycles.org`; // root url without a path
const base = '/'; // base path of the strudel site
// this rehype plugin converts relative anchor links to absolute ones
// it wokrs by prepending the absolute page path to anchor links
// example: #gain -> /learn/effects/#gain
// this is necessary when using a base href like <base href={base} />
// in this setup, relative anchor links will always link to base, instead of the current page
function absoluteAnchors() {
return (tree, file) => {
const chunks = file.history[0].split('/src/pages/'); // file.history[0] is the file path
const path = chunks[chunks.length - 1].slice(0, -4); // only path inside src/pages, without .mdx
return rehypeUrls((url) => {
if (!url.href.startsWith('#')) {
return;
}
const baseWithSlash = base.endsWith('/') ? base : base + '/';
const absoluteUrl = baseWithSlash + path + url.href;
// console.log(url.href + ' -> ', absoluteUrl);
return absoluteUrl;
})(tree);
};
}
const options = {
// See https://mdxjs.com/advanced/plugins
remarkPlugins: [
remarkToc,
// E.g. `remark-frontmatter`
],
rehypePlugins: [rehypeSlug, rehypeAutolinkHeadings],
rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'append' }], absoluteAnchors],
};
// https://astro.build/config
@@ -97,8 +120,8 @@ export default defineConfig({
},
}),
],
site: `https://strudel.tidalcycles.org`,
base: '/',
site,
base,
vite: {
ssr: {
// Example: Force a broken package to skip SSR processing, if needed
@@ -106,13 +129,3 @@ export default defineConfig({
},
},
});
/*
build: {
outDir: '../out',
sourcemap: true,
rollupOptions: {
plugins: [visualizer({ template: 'treemap' })],
},
},
*/
+2
View File
@@ -38,6 +38,7 @@
"@types/node": "^18.0.0",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@uiw/codemirror-themes-all": "^4.19.8",
"astro": "^1.7.2",
"canvas": "^2.11.0",
"fraction.js": "^4.2.0",
@@ -47,6 +48,7 @@
"react-dom": "^18.2.0",
"rehype-autolink-headings": "^6.1.1",
"rehype-slug": "^5.0.1",
"rehype-urls": "^1.1.1",
"remark-toc": "^8.0.1",
"tailwindcss": "^3.2.4"
},
+326
View File
@@ -0,0 +1,326 @@
<svg height="100%" viewBox="0.00 0.00 538.13 507.20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet">
<defs><pattern id="warning" width="12" height="12" patternUnits="userSpaceOnUse" patternTransform="rotate(45 50 50)">
<line class="line0" stroke-width="6px" x1="3" x2="3" y2="12"></line>
<line class="line1" stroke-width="6px" x1="9" x2="9" y2="12"></line>
</pattern></defs><g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 503.2)">
<!-- @strudel.cycles/core@0.6.8 -->
<g id="node1" class="node module-_strudel_cycles_core maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/core@0.6.8">
<title>@strudel.cycles/core@0.6.8</title>
<path fill="none" stroke="black" d="M174.8,-37.3C174.8,-37.3 56.9,-37.3 56.9,-37.3 53.66,-37.3 50.43,-34.07 50.43,-30.83 50.43,-30.83 50.43,-24.37 50.43,-24.37 50.43,-21.13 53.66,-17.9 56.9,-17.9 56.9,-17.9 174.8,-17.9 174.8,-17.9 178.03,-17.9 181.26,-21.13 181.26,-24.37 181.26,-24.37 181.26,-30.83 181.26,-30.83 181.26,-34.07 178.03,-37.3 174.8,-37.3"></path>
<text text-anchor="middle" x="115.85" y="-24.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/core@0.6.8</text>
</g>
<!-- fraction.js@4.2.0 -->
<g id="node21" class="node module-fraction_js maintainer-infusion license-mit" data-module="fraction.js@4.2.0">
<title>fraction.js@4.2.0</title>
<path fill="none" stroke="black" d="M343.81,-19.3C343.81,-19.3 273.9,-19.3 273.9,-19.3 270.67,-19.3 267.44,-16.07 267.44,-12.83 267.44,-12.83 267.44,-6.37 267.44,-6.37 267.44,-3.13 270.67,0.1 273.9,0.1 273.9,0.1 343.81,0.1 343.81,0.1 347.05,0.1 350.28,-3.13 350.28,-6.37 350.28,-6.37 350.28,-12.83 350.28,-12.83 350.28,-16.07 347.05,-19.3 343.81,-19.3"></path>
<text text-anchor="middle" x="308.86" y="-6.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">fraction.js@4.2.0</text>
</g>
<!-- @strudel.cycles/core@0.6.8&#45;&gt;fraction.js@4.2.0 -->
<!-- @strudel.cycles/csound@0.6.2 -->
<g id="node2" class="node module-_strudel_cycles_csound maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/csound@0.6.2">
<title>@strudel.cycles/csound@0.6.2</title>
<path fill="none" stroke="black" d="M180.53,-296.3C180.53,-296.3 51.16,-296.3 51.16,-296.3 47.93,-296.3 44.7,-293.07 44.7,-289.83 44.7,-289.83 44.7,-283.37 44.7,-283.37 44.7,-280.13 47.93,-276.9 51.16,-276.9 51.16,-276.9 180.53,-276.9 180.53,-276.9 183.77,-276.9 187,-280.13 187,-283.37 187,-283.37 187,-289.83 187,-289.83 187,-293.07 183.77,-296.3 180.53,-296.3"></path>
<text text-anchor="middle" x="115.85" y="-283.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/csound@0.6.2</text>
</g>
<!-- @strudel.cycles/csound@0.6.2&#45;&gt;@strudel.cycles/core@0.6.8 -->
<!-- @strudel.cycles/webaudio@0.6.1 -->
<g id="node12" class="node module-_strudel_cycles_webaudio maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/webaudio@0.6.1">
<title>@strudel.cycles/webaudio@0.6.1</title>
<path fill="none" stroke="black" d="M186.63,-259.3C186.63,-259.3 45.06,-259.3 45.06,-259.3 41.83,-259.3 38.59,-256.07 38.59,-252.83 38.59,-252.83 38.59,-246.37 38.59,-246.37 38.59,-243.13 41.83,-239.9 45.06,-239.9 45.06,-239.9 186.63,-239.9 186.63,-239.9 189.87,-239.9 193.1,-243.13 193.1,-246.37 193.1,-246.37 193.1,-252.83 193.1,-252.83 193.1,-256.07 189.87,-259.3 186.63,-259.3"></path>
<text text-anchor="middle" x="115.85" y="-246.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/webaudio@0.6.1</text>
</g>
<!-- @strudel.cycles/csound@0.6.2&#45;&gt;@strudel.cycles/webaudio@0.6.1 -->
<!-- @csound/browser@6.18.5 -->
<g id="node14" class="node module-_csound_browser collapsed maintainer-skyne maintainer-kunstmusik maintainer-eddyc maintainer-hlolli license-lgpl_2_1" data-module="@csound/browser@6.18.5">
<title>@csound/browser@6.18.5</title>
<path fill="none" stroke="black" d="M364.3,-257.3C364.3,-257.3 253.41,-257.3 253.41,-257.3 250.18,-257.3 246.95,-254.07 246.95,-250.83 246.95,-250.83 246.95,-244.37 246.95,-244.37 246.95,-241.13 250.18,-237.9 253.41,-237.9 253.41,-237.9 364.3,-237.9 364.3,-237.9 367.54,-237.9 370.77,-241.13 370.77,-244.37 370.77,-244.37 370.77,-250.83 370.77,-250.83 370.77,-254.07 367.54,-257.3 364.3,-257.3"></path>
<text text-anchor="middle" x="308.86" y="-244.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@csound/browser@6.18.5</text>
</g>
<!-- @strudel.cycles/csound@0.6.2&#45;&gt;@csound/browser@6.18.5 -->
<!-- @strudel.cycles/embed@0.2.0 -->
<g id="node3" class="node module-_strudel_cycles_embed maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/embed@0.2.0">
<title>@strudel.cycles/embed@0.2.0</title>
<path fill="none" stroke="black" d="M179.69,-462.3C179.69,-462.3 52.01,-462.3 52.01,-462.3 48.77,-462.3 45.54,-459.07 45.54,-455.83 45.54,-455.83 45.54,-449.37 45.54,-449.37 45.54,-446.13 48.77,-442.9 52.01,-442.9 52.01,-442.9 179.69,-442.9 179.69,-442.9 182.92,-442.9 186.16,-446.13 186.16,-449.37 186.16,-449.37 186.16,-455.83 186.16,-455.83 186.16,-459.07 182.92,-462.3 179.69,-462.3"></path>
<text text-anchor="middle" x="115.85" y="-449.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/embed@0.2.0</text>
</g>
<!-- @strudel.cycles/midi@0.6.0 -->
<g id="node4" class="node module-_strudel_cycles_midi maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/midi@0.6.0">
<title>@strudel.cycles/midi@0.6.0</title>
<path fill="none" stroke="black" d="M175.04,-370.3C175.04,-370.3 56.66,-370.3 56.66,-370.3 53.42,-370.3 50.19,-367.07 50.19,-363.83 50.19,-363.83 50.19,-357.37 50.19,-357.37 50.19,-354.13 53.42,-350.9 56.66,-350.9 56.66,-350.9 175.04,-350.9 175.04,-350.9 178.27,-350.9 181.5,-354.13 181.5,-357.37 181.5,-357.37 181.5,-363.83 181.5,-363.83 181.5,-367.07 178.27,-370.3 175.04,-370.3"></path>
<text text-anchor="middle" x="115.85" y="-357.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/midi@0.6.0</text>
</g>
<!-- @strudel.cycles/midi@0.6.0&#45;&gt;@strudel.cycles/core@0.6.8 -->
<!-- @strudel.cycles/webaudio@0.6.0 -->
<g id="node15" class="node module-_strudel_cycles_webaudio maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/webaudio@0.6.0">
<title>@strudel.cycles/webaudio@0.6.0</title>
<path fill="none" stroke="black" d="M379.64,-370.3C379.64,-370.3 238.07,-370.3 238.07,-370.3 234.84,-370.3 231.6,-367.07 231.6,-363.83 231.6,-363.83 231.6,-357.37 231.6,-357.37 231.6,-354.13 234.84,-350.9 238.07,-350.9 238.07,-350.9 379.64,-350.9 379.64,-350.9 382.88,-350.9 386.11,-354.13 386.11,-357.37 386.11,-357.37 386.11,-363.83 386.11,-363.83 386.11,-367.07 382.88,-370.3 379.64,-370.3"></path>
<text text-anchor="middle" x="308.86" y="-357.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/webaudio@0.6.0</text>
</g>
<!-- @strudel.cycles/midi@0.6.0&#45;&gt;@strudel.cycles/webaudio@0.6.0 -->
<!-- webmidi@3.1.4 -->
<g id="node25" class="node module-webmidi collapsed maintainer-djipco license-apache_2_0" data-module="webmidi@3.1.4">
<title>webmidi@3.1.4</title>
<path fill="none" stroke="black" d="M340.52,-407.3C340.52,-407.3 277.2,-407.3 277.2,-407.3 273.97,-407.3 270.73,-404.07 270.73,-400.83 270.73,-400.83 270.73,-394.37 270.73,-394.37 270.73,-391.13 273.97,-387.9 277.2,-387.9 277.2,-387.9 340.52,-387.9 340.52,-387.9 343.75,-387.9 346.98,-391.13 346.98,-394.37 346.98,-394.37 346.98,-400.83 346.98,-400.83 346.98,-404.07 343.75,-407.3 340.52,-407.3"></path>
<text text-anchor="middle" x="308.86" y="-394.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">webmidi@3.1.4</text>
</g>
<!-- @strudel.cycles/midi@0.6.0&#45;&gt;webmidi@3.1.4 -->
<!-- @strudel.cycles/mini@0.6.0 -->
<g id="node5" class="node module-_strudel_cycles_mini maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/mini@0.6.0">
<title>@strudel.cycles/mini@0.6.0</title>
<path fill="none" stroke="black" d="M175.04,-111.3C175.04,-111.3 56.66,-111.3 56.66,-111.3 53.42,-111.3 50.19,-108.07 50.19,-104.83 50.19,-104.83 50.19,-98.37 50.19,-98.37 50.19,-95.13 53.42,-91.9 56.66,-91.9 56.66,-91.9 175.04,-91.9 175.04,-91.9 178.27,-91.9 181.5,-95.13 181.5,-98.37 181.5,-98.37 181.5,-104.83 181.5,-104.83 181.5,-108.07 178.27,-111.3 175.04,-111.3"></path>
<text text-anchor="middle" x="115.85" y="-98.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/mini@0.6.0</text>
</g>
<!-- @strudel.cycles/mini@0.6.0&#45;&gt;@strudel.cycles/core@0.6.8 -->
<!-- @strudel.cycles/osc@0.6.0 -->
<g id="node6" class="node module-_strudel_cycles_osc maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/osc@0.6.0">
<title>@strudel.cycles/osc@0.6.0</title>
<path fill="none" stroke="black" d="M172.53,-222.3C172.53,-222.3 59.16,-222.3 59.16,-222.3 55.93,-222.3 52.7,-219.07 52.7,-215.83 52.7,-215.83 52.7,-209.37 52.7,-209.37 52.7,-206.13 55.93,-202.9 59.16,-202.9 59.16,-202.9 172.53,-202.9 172.53,-202.9 175.76,-202.9 179,-206.13 179,-209.37 179,-209.37 179,-215.83 179,-215.83 179,-219.07 175.76,-222.3 172.53,-222.3"></path>
<text text-anchor="middle" x="115.85" y="-209.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/osc@0.6.0</text>
</g>
<!-- @strudel.cycles/osc@0.6.0&#45;&gt;@strudel.cycles/core@0.6.8 -->
<!-- osc&#45;js@2.4.0 -->
<g id="node22" class="node module-osc_js collapsed maintainer-andreasdz license-mit" data-module="osc-js@2.4.0">
<title>osc-js@2.4.0</title>
<path fill="none" stroke="black" d="M334.18,-220.3C334.18,-220.3 283.53,-220.3 283.53,-220.3 280.3,-220.3 277.06,-217.07 277.06,-213.83 277.06,-213.83 277.06,-207.37 277.06,-207.37 277.06,-204.13 280.3,-200.9 283.53,-200.9 283.53,-200.9 334.18,-200.9 334.18,-200.9 337.42,-200.9 340.65,-204.13 340.65,-207.37 340.65,-207.37 340.65,-213.83 340.65,-213.83 340.65,-217.07 337.42,-220.3 334.18,-220.3"></path>
<text text-anchor="middle" x="308.86" y="-207.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">osc-js@2.4.0</text>
</g>
<!-- @strudel.cycles/osc@0.6.0&#45;&gt;osc&#45;js@2.4.0 -->
<!-- @strudel.cycles/react@0.6.0 -->
<g id="node7" class="node module-_strudel_cycles_react collapsed maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/react@0.6.0">
<title>@strudel.cycles/react@0.6.0</title>
<path fill="none" stroke="black" d="M175.73,-499.3C175.73,-499.3 55.96,-499.3 55.96,-499.3 52.73,-499.3 49.49,-496.07 49.49,-492.83 49.49,-492.83 49.49,-486.37 49.49,-486.37 49.49,-483.13 52.73,-479.9 55.96,-479.9 55.96,-479.9 175.73,-479.9 175.73,-479.9 178.97,-479.9 182.2,-483.13 182.2,-486.37 182.2,-486.37 182.2,-492.83 182.2,-492.83 182.2,-496.07 178.97,-499.3 175.73,-499.3"></path>
<text text-anchor="middle" x="115.85" y="-486.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/react@0.6.0</text>
</g>
<!-- @strudel.cycles/serial@0.6.0 -->
<g id="node8" class="node module-_strudel_cycles_serial maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/serial@0.6.0">
<title>@strudel.cycles/serial@0.6.0</title>
<path fill="none" stroke="black" d="M177.19,-148.3C177.19,-148.3 54.5,-148.3 54.5,-148.3 51.27,-148.3 48.04,-145.07 48.04,-141.83 48.04,-141.83 48.04,-135.37 48.04,-135.37 48.04,-132.13 51.27,-128.9 54.5,-128.9 54.5,-128.9 177.19,-128.9 177.19,-128.9 180.42,-128.9 183.66,-132.13 183.66,-135.37 183.66,-135.37 183.66,-141.83 183.66,-141.83 183.66,-145.07 180.42,-148.3 177.19,-148.3"></path>
<text text-anchor="middle" x="115.85" y="-135.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/serial@0.6.0</text>
</g>
<!-- @strudel.cycles/serial@0.6.0&#45;&gt;@strudel.cycles/core@0.6.8 -->
<!-- @strudel.cycles/soundfonts@0.6.0 -->
<g id="node9" class="node module-_strudel_cycles_soundfonts maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/soundfonts@0.6.0">
<title>@strudel.cycles/soundfonts@0.6.0</title>
<path fill="none" stroke="black" d="M189.15,-333.3C189.15,-333.3 42.55,-333.3 42.55,-333.3 39.31,-333.3 36.08,-330.07 36.08,-326.83 36.08,-326.83 36.08,-320.37 36.08,-320.37 36.08,-317.13 39.31,-313.9 42.55,-313.9 42.55,-313.9 189.15,-313.9 189.15,-313.9 192.38,-313.9 195.62,-317.13 195.62,-320.37 195.62,-320.37 195.62,-326.83 195.62,-326.83 195.62,-330.07 192.38,-333.3 189.15,-333.3"></path>
<text text-anchor="middle" x="115.85" y="-320.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/soundfonts@0.6.0</text>
</g>
<!-- @strudel.cycles/soundfonts@0.6.0&#45;&gt;@strudel.cycles/core@0.6.8 -->
<!-- @strudel.cycles/soundfonts@0.6.0&#45;&gt;@strudel.cycles/webaudio@0.6.0 -->
<!-- sfumato@0.1.2 -->
<g id="node23" class="node module-sfumato maintainer-felixroos license-isc" data-module="sfumato@0.1.2">
<title>sfumato@0.1.2</title>
<path fill="none" stroke="black" d="M338.96,-333.3C338.96,-333.3 278.76,-333.3 278.76,-333.3 275.52,-333.3 272.29,-330.07 272.29,-326.83 272.29,-326.83 272.29,-320.37 272.29,-320.37 272.29,-317.13 275.52,-313.9 278.76,-313.9 278.76,-313.9 338.96,-313.9 338.96,-313.9 342.19,-313.9 345.43,-317.13 345.43,-320.37 345.43,-320.37 345.43,-326.83 345.43,-326.83 345.43,-330.07 342.19,-333.3 338.96,-333.3"></path>
<text text-anchor="middle" x="308.86" y="-320.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">sfumato@0.1.2</text>
</g>
<!-- @strudel.cycles/soundfonts@0.6.0&#45;&gt;sfumato@0.1.2 -->
<!-- soundfont2@0.4.0 -->
<g id="node24" class="node module-soundfont2 maintainer-mrten license-mit" data-module="soundfont2@0.4.0">
<title>soundfont2@0.4.0</title>
<path fill="none" stroke="black" d="M513.22,-314.3C513.22,-314.3 438.89,-314.3 438.89,-314.3 435.66,-314.3 432.42,-311.07 432.42,-307.83 432.42,-307.83 432.42,-301.37 432.42,-301.37 432.42,-298.13 435.66,-294.9 438.89,-294.9 438.89,-294.9 513.22,-294.9 513.22,-294.9 516.45,-294.9 519.69,-298.13 519.69,-301.37 519.69,-301.37 519.69,-307.83 519.69,-307.83 519.69,-311.07 516.45,-314.3 513.22,-314.3"></path>
<text text-anchor="middle" x="476.06" y="-301.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">soundfont2@0.4.0</text>
</g>
<!-- @strudel.cycles/soundfonts@0.6.0&#45;&gt;soundfont2@0.4.0 -->
<!-- @strudel.cycles/tonal@0.6.0 -->
<g id="node10" class="node module-_strudel_cycles_tonal maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/tonal@0.6.0">
<title>@strudel.cycles/tonal@0.6.0</title>
<path fill="none" stroke="black" d="M176.36,-425.3C176.36,-425.3 55.33,-425.3 55.33,-425.3 52.1,-425.3 48.86,-422.07 48.86,-418.83 48.86,-418.83 48.86,-412.37 48.86,-412.37 48.86,-409.13 52.1,-405.9 55.33,-405.9 55.33,-405.9 176.36,-405.9 176.36,-405.9 179.6,-405.9 182.83,-409.13 182.83,-412.37 182.83,-412.37 182.83,-418.83 182.83,-418.83 182.83,-422.07 179.6,-425.3 176.36,-425.3"></path>
<text text-anchor="middle" x="115.85" y="-412.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/tonal@0.6.0</text>
</g>
<!-- @strudel.cycles/tonal@0.6.0&#45;&gt;@strudel.cycles/core@0.6.8 -->
<!-- @tonaljs/tonal@4.10.0 -->
<g id="node16" class="node module-_tonaljs_tonal collapsed maintainer-danigb license-mit" data-module="@tonaljs/tonal@4.10.0">
<title>@tonaljs/tonal@4.10.0</title>
<path fill="none" stroke="black" d="M523.73,-463.3C523.73,-463.3 428.38,-463.3 428.38,-463.3 425.15,-463.3 421.91,-460.07 421.91,-456.83 421.91,-456.83 421.91,-450.37 421.91,-450.37 421.91,-447.13 425.15,-443.9 428.38,-443.9 428.38,-443.9 523.73,-443.9 523.73,-443.9 526.96,-443.9 530.2,-447.13 530.2,-450.37 530.2,-450.37 530.2,-456.83 530.2,-456.83 530.2,-460.07 526.96,-463.3 523.73,-463.3"></path>
<text text-anchor="middle" x="476.06" y="-450.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@tonaljs/tonal@4.10.0</text>
</g>
<!-- @strudel.cycles/tonal@0.6.0&#45;&gt;@tonaljs/tonal@4.10.0 -->
<!-- chord&#45;voicings@0.0.1 -->
<g id="node18" class="node module-chord_voicings maintainer-felixroos license-isc" data-module="chord-voicings@0.0.1">
<title>chord-voicings@0.0.1</title>
<path fill="none" stroke="black" d="M354.5,-444.3C354.5,-444.3 263.21,-444.3 263.21,-444.3 259.98,-444.3 256.74,-441.07 256.74,-437.83 256.74,-437.83 256.74,-431.37 256.74,-431.37 256.74,-428.13 259.98,-424.9 263.21,-424.9 263.21,-424.9 354.5,-424.9 354.5,-424.9 357.74,-424.9 360.97,-428.13 360.97,-431.37 360.97,-431.37 360.97,-437.83 360.97,-437.83 360.97,-441.07 357.74,-444.3 354.5,-444.3"></path>
<text text-anchor="middle" x="308.86" y="-431.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">chord-voicings@0.0.1</text>
</g>
<!-- @strudel.cycles/tonal@0.6.0&#45;&gt;chord&#45;voicings@0.0.1 -->
<!-- @strudel.cycles/tonal@0.6.0&#45;&gt;webmidi@3.1.4 -->
<!-- @strudel.cycles/transpiler@0.6.0 -->
<g id="node11" class="node module-_strudel_cycles_transpiler maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/transpiler@0.6.0">
<title>@strudel.cycles/transpiler@0.6.0</title>
<path fill="none" stroke="black" d="M185.91,-74.3C185.91,-74.3 45.79,-74.3 45.79,-74.3 42.55,-74.3 39.32,-71.07 39.32,-67.83 39.32,-67.83 39.32,-61.37 39.32,-61.37 39.32,-58.13 42.55,-54.9 45.79,-54.9 45.79,-54.9 185.91,-54.9 185.91,-54.9 189.14,-54.9 192.38,-58.13 192.38,-61.37 192.38,-61.37 192.38,-67.83 192.38,-67.83 192.38,-71.07 189.14,-74.3 185.91,-74.3"></path>
<text text-anchor="middle" x="115.85" y="-61.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/transpiler@0.6.0</text>
</g>
<!-- @strudel.cycles/transpiler@0.6.0&#45;&gt;@strudel.cycles/core@0.6.8 -->
<!-- acorn@8.8.2 -->
<g id="node17" class="node module-acorn collapsed maintainer-marijn maintainer-adrianheine maintainer-rreverser license-mit" data-module="acorn@8.8.2">
<title>acorn@8.8.2</title>
<path fill="none" stroke="black" d="M333.45,-130.3C333.45,-130.3 284.27,-130.3 284.27,-130.3 281.03,-130.3 277.8,-127.07 277.8,-123.83 277.8,-123.83 277.8,-117.37 277.8,-117.37 277.8,-114.13 281.03,-110.9 284.27,-110.9 284.27,-110.9 333.45,-110.9 333.45,-110.9 336.68,-110.9 339.92,-114.13 339.92,-117.37 339.92,-117.37 339.92,-123.83 339.92,-123.83 339.92,-127.07 336.68,-130.3 333.45,-130.3"></path>
<text text-anchor="middle" x="308.86" y="-117.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">acorn@8.8.2</text>
</g>
<!-- @strudel.cycles/transpiler@0.6.0&#45;&gt;acorn@8.8.2 -->
<!-- escodegen@2.0.0 -->
<g id="node19" class="node module-escodegen collapsed maintainer-constellation maintainer-michaelficarra license-bsd_2_clause" data-module="escodegen@2.0.0">
<title>escodegen@2.0.0</title>
<path fill="none" stroke="black" d="M344.33,-93.3C344.33,-93.3 273.39,-93.3 273.39,-93.3 270.15,-93.3 266.92,-90.07 266.92,-86.83 266.92,-86.83 266.92,-80.37 266.92,-80.37 266.92,-77.13 270.15,-73.9 273.39,-73.9 273.39,-73.9 344.33,-73.9 344.33,-73.9 347.56,-73.9 350.8,-77.13 350.8,-80.37 350.8,-80.37 350.8,-86.83 350.8,-86.83 350.8,-90.07 347.56,-93.3 344.33,-93.3"></path>
<text text-anchor="middle" x="308.86" y="-80.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">escodegen@2.0.0</text>
</g>
<!-- @strudel.cycles/transpiler@0.6.0&#45;&gt;escodegen@2.0.0 -->
<!-- estree&#45;walker@3.0.3 -->
<g id="node20" class="node module-estree_walker collapsed maintainer-rich_harris license-mit" data-module="estree-walker@3.0.3">
<title>estree-walker@3.0.3</title>
<path fill="none" stroke="black" d="M351.26,-56.3C351.26,-56.3 266.46,-56.3 266.46,-56.3 263.22,-56.3 259.99,-53.07 259.99,-49.83 259.99,-49.83 259.99,-43.37 259.99,-43.37 259.99,-40.13 263.22,-36.9 266.46,-36.9 266.46,-36.9 351.26,-36.9 351.26,-36.9 354.49,-36.9 357.73,-40.13 357.73,-43.37 357.73,-43.37 357.73,-49.83 357.73,-49.83 357.73,-53.07 354.49,-56.3 351.26,-56.3"></path>
<text text-anchor="middle" x="308.86" y="-43.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">estree-walker@3.0.3</text>
</g>
<!-- @strudel.cycles/transpiler@0.6.0&#45;&gt;estree&#45;walker@3.0.3 -->
<!-- @strudel.cycles/webaudio@0.6.1&#45;&gt;@strudel.cycles/core@0.6.8 -->
<!-- @strudel.cycles/xen@0.6.0 -->
<g id="node13" class="node module-_strudel_cycles_xen maintainer-yaxupaxo maintainer-felixroos license-agpl_3_0_or_later" data-module="@strudel.cycles/xen@0.6.0">
<title>@strudel.cycles/xen@0.6.0</title>
<path fill="none" stroke="black" d="M173.25,-185.3C173.25,-185.3 58.44,-185.3 58.44,-185.3 55.21,-185.3 51.98,-182.07 51.98,-178.83 51.98,-178.83 51.98,-172.37 51.98,-172.37 51.98,-169.13 55.21,-165.9 58.44,-165.9 58.44,-165.9 173.25,-165.9 173.25,-165.9 176.48,-165.9 179.72,-169.13 179.72,-172.37 179.72,-172.37 179.72,-178.83 179.72,-178.83 179.72,-182.07 176.48,-185.3 173.25,-185.3"></path>
<text text-anchor="middle" x="115.85" y="-172.3" font-family="Roboto Condensed, sans-serif" font-size="11.00">@strudel.cycles/xen@0.6.0</text>
</g>
<!-- @strudel.cycles/xen@0.6.0&#45;&gt;@strudel.cycles/core@0.6.8 -->
<!-- @strudel.cycles/webaudio@0.6.0&#45;&gt;@strudel.cycles/core@0.6.8 -->
<!-- chord&#45;voicings@0.0.1&#45;&gt;@tonaljs/tonal@4.10.0 -->
<g id="edge27" class="edge">
<title>chord-voicings@0.0.1-&gt;@tonaljs/tonal@4.10.0</title>
<path fill="none" stroke="black" d="M361.22,-440.51C377.14,-442.34 394.88,-444.38 411.53,-446.29"></path>
<polygon fill="black" stroke="black" points="411.39,-449.8 421.73,-447.47 412.19,-442.85 411.39,-449.8"></polygon>
</g>
<!-- sfumato@0.1.2&#45;&gt;soundfont2@0.4.0 -->
<g id="edge28" class="edge">
<title>sfumato@0.1.2-&gt;soundfont2@0.4.0</title>
<path fill="none" stroke="black" d="M345.7,-319.48C368.06,-316.91 397.22,-313.55 422.19,-310.68"></path>
<polygon fill="black" stroke="black" points="422.72,-314.14 432.25,-309.52 421.92,-307.19 422.72,-314.14"></polygon>
</g>
<g id="edge1" class="edge">
<title>@strudel.cycles/core@0.6.8-&gt;fraction.js@4.2.0</title>
<path fill="none" stroke="black" d="M181.25,-21.53C206.16,-19.18 234.23,-16.54 257.6,-14.34"></path>
<polygon fill="black" stroke="black" points="258.02,-17.81 267.64,-13.39 257.36,-10.84 258.02,-17.81"></polygon>
</g><g id="edge3" class="edge">
<title>@strudel.cycles/csound@0.6.2-&gt;@strudel.cycles/core@0.6.8</title>
<path fill="none" stroke="black" d="M44.23,-280.41C33.55,-276.09 24.08,-269.5 17.96,-259.6 -5.99,-220.85 -5.99,-93.35 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
</g><g id="edge4" class="edge">
<title>@strudel.cycles/csound@0.6.2-&gt;@strudel.cycles/webaudio@0.6.1</title>
<path fill="none" stroke="black" d="M115.85,-276.84C115.85,-274.39 115.85,-271.94 115.85,-269.49"></path>
<polygon fill="black" stroke="black" points="119.35,-269.43 115.85,-259.43 112.35,-269.43 119.35,-269.43"></polygon>
</g><g id="edge6" class="edge">
<title>@strudel.cycles/midi@0.6.0-&gt;@strudel.cycles/core@0.6.8</title>
<path fill="none" stroke="black" d="M50.17,-356.56C37.1,-352.37 25.19,-345.3 17.96,-333.6 1.66,-307.23 1.66,-80.97 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
</g><g id="edge7" class="edge">
<title>@strudel.cycles/midi@0.6.0-&gt;@strudel.cycles/webaudio@0.6.0</title>
<path fill="none" stroke="black" d="M181.51,-360.6C194.37,-360.6 208.05,-360.6 221.49,-360.6"></path>
<polygon fill="black" stroke="black" points="221.71,-364.1 231.7,-360.6 221.7,-357.1 221.71,-364.1"></polygon>
</g><g id="edge5" class="edge">
<title>@strudel.cycles/midi@0.6.0-&gt;webmidi@3.1.4</title>
<path fill="none" stroke="black" d="M166.53,-370.22C195.48,-375.83 231.78,-382.86 260.34,-388.4"></path>
<polygon fill="black" stroke="black" points="260.01,-391.9 270.49,-390.36 261.34,-385.02 260.01,-391.9"></polygon>
</g><g id="edge8" class="edge">
<title>@strudel.cycles/mini@0.6.0-&gt;@strudel.cycles/core@0.6.8</title>
<path fill="none" stroke="black" d="M50.17,-97.56C37.1,-93.37 25.19,-86.3 17.96,-74.6 7.78,-58.12 21.54,-47.3 41.67,-40.25"></path>
<polygon fill="black" stroke="black" points="42.78,-43.57 51.3,-37.27 40.71,-36.88 42.78,-43.57"></polygon>
</g><g id="edge10" class="edge">
<title>@strudel.cycles/osc@0.6.0-&gt;@strudel.cycles/core@0.6.8</title>
<path fill="none" stroke="black" d="M52.7,-209.33C38.65,-205.26 25.65,-198.05 17.96,-185.6 2.66,-160.84 2.66,-79.36 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
</g><g id="edge9" class="edge">
<title>@strudel.cycles/osc@0.6.0-&gt;osc-js@2.4.0</title>
<path fill="none" stroke="black" d="M179.13,-211.95C207.93,-211.65 241.33,-211.3 266.9,-211.03"></path>
<polygon fill="black" stroke="black" points="267.02,-214.53 276.99,-210.92 266.95,-207.53 267.02,-214.53"></polygon>
</g><g id="edge11" class="edge">
<title>@strudel.cycles/serial@0.6.0-&gt;@strudel.cycles/core@0.6.8</title>
<path fill="none" stroke="black" d="M48.17,-133.89C35.89,-129.63 24.82,-122.7 17.96,-111.6 4.64,-90.05 4.64,-76.15 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
</g><g id="edge14" class="edge">
<title>@strudel.cycles/soundfonts@0.6.0-&gt;@strudel.cycles/core@0.6.8</title>
<path fill="none" stroke="black" d="M36.71,-313.85C29.15,-309.7 22.6,-304.1 17.96,-296.6 3.83,-273.73 3.83,-77.47 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
</g><g id="edge15" class="edge">
<title>@strudel.cycles/soundfonts@0.6.0-&gt;@strudel.cycles/webaudio@0.6.0</title>
<path fill="none" stroke="black" d="M166.53,-333.22C191.53,-338.07 222.01,-343.97 248.26,-349.05"></path>
<polygon fill="black" stroke="black" points="247.6,-352.49 258.09,-350.96 248.94,-345.62 247.6,-352.49"></polygon>
</g><g id="edge12" class="edge">
<title>@strudel.cycles/soundfonts@0.6.0-&gt;sfumato@0.1.2</title>
<path fill="none" stroke="black" d="M195.64,-323.6C218.22,-323.6 242.08,-323.6 261.97,-323.6"></path>
<polygon fill="black" stroke="black" points="261.98,-327.1 271.98,-323.6 261.98,-320.1 261.98,-327.1"></polygon>
</g><g id="edge13" class="edge">
<title>@strudel.cycles/soundfonts@0.6.0-&gt;soundfont2@0.4.0</title>
<path fill="none" stroke="black" d="M164.14,-313.99C184.8,-310.25 209.37,-306.44 231.73,-304.6 297.18,-299.22 372.86,-300.38 422.21,-302.11"></path>
<polygon fill="black" stroke="black" points="422.21,-305.61 432.33,-302.49 422.47,-298.62 422.21,-305.61"></polygon>
</g><g id="edge19" class="edge">
<title>@strudel.cycles/tonal@0.6.0-&gt;@strudel.cycles/core@0.6.8</title>
<path fill="none" stroke="black" d="M68.74,-405.99C49.45,-399.45 29.07,-388.58 17.96,-370.6 -0.5,-340.73 -0.5,-84.47 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
</g><g id="edge16" class="edge">
<title>@strudel.cycles/tonal@0.6.0-&gt;@tonaljs/tonal@4.10.0</title>
<path fill="none" stroke="black" d="M168.82,-425.25C177.92,-427.57 187.2,-430.33 195.73,-433.6 212.82,-440.15 214.01,-449.01 231.73,-453.6 291.41,-469.07 362.13,-466.9 411.9,-462.17"></path>
<polygon fill="black" stroke="black" points="412.28,-465.65 421.88,-461.17 411.58,-458.69 412.28,-465.65"></polygon>
</g><g id="edge17" class="edge">
<title>@strudel.cycles/tonal@0.6.0-&gt;chord-voicings@0.0.1</title>
<path fill="none" stroke="black" d="M182.85,-422.17C203.51,-424.22 226.22,-426.48 246.55,-428.5"></path>
<polygon fill="black" stroke="black" points="246.33,-432 256.62,-429.5 247.02,-425.03 246.33,-432"></polygon>
</g><g id="edge18" class="edge">
<title>@strudel.cycles/tonal@0.6.0-&gt;webmidi@3.1.4</title>
<path fill="none" stroke="black" d="M182.85,-409.38C208.4,-406.97 237.07,-404.27 260.51,-402.06"></path>
<polygon fill="black" stroke="black" points="260.91,-405.54 270.54,-401.12 260.26,-398.57 260.91,-405.54"></polygon>
</g><g id="edge23" class="edge">
<title>@strudel.cycles/transpiler@0.6.0-&gt;@strudel.cycles/core@0.6.8</title>
<path fill="none" stroke="black" d="M115.85,-54.84C115.85,-52.39 115.85,-49.94 115.85,-47.49"></path>
<polygon fill="black" stroke="black" points="119.35,-47.43 115.85,-37.43 112.35,-47.43 119.35,-47.43"></polygon>
</g><g id="edge20" class="edge">
<title>@strudel.cycles/transpiler@0.6.0-&gt;acorn@8.8.2</title>
<path fill="none" stroke="black" d="M168.82,-74.25C177.92,-76.57 187.2,-79.33 195.73,-82.6 212.82,-89.15 214.66,-95.99 231.73,-102.6 243.09,-107 255.84,-110.53 267.61,-113.25"></path>
<polygon fill="black" stroke="black" points="267.12,-116.73 277.64,-115.44 268.61,-109.89 267.12,-116.73"></polygon>
</g><g id="edge21" class="edge">
<title>@strudel.cycles/transpiler@0.6.0-&gt;escodegen@2.0.0</title>
<path fill="none" stroke="black" d="M192.61,-72.14C214.17,-74.28 237.12,-76.56 256.83,-78.52"></path>
<polygon fill="black" stroke="black" points="256.51,-82.01 266.81,-79.52 257.21,-75.04 256.51,-82.01"></polygon>
</g><g id="edge22" class="edge">
<title>@strudel.cycles/transpiler@0.6.0-&gt;estree-walker@3.0.3</title>
<path fill="none" stroke="black" d="M192.61,-57.46C211.68,-55.66 231.85,-53.76 249.89,-52.06"></path>
<polygon fill="black" stroke="black" points="250.33,-55.54 259.96,-51.11 249.68,-48.57 250.33,-55.54"></polygon>
</g><g id="edge24" class="edge">
<title>@strudel.cycles/webaudio@0.6.1-&gt;@strudel.cycles/core@0.6.8</title>
<path fill="none" stroke="black" d="M38.54,-240.82C30.22,-236.58 22.97,-230.7 17.96,-222.6 -1.66,-190.84 -1.66,-86.36 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
</g><g id="edge25" class="edge">
<title>@strudel.cycles/xen@0.6.0-&gt;@strudel.cycles/core@0.6.8</title>
<path fill="none" stroke="black" d="M51.94,-172.1C38.18,-168 25.52,-160.82 17.96,-148.6 -4,-113.06 -4,-90.14 17.96,-54.6 23.42,-45.77 31.54,-39.58 40.82,-35.27"></path>
<polygon fill="black" stroke="black" points="42.35,-38.43 50.42,-31.56 39.83,-31.9 42.35,-38.43"></polygon>
</g><g id="edge26" class="edge">
<title>@strudel.cycles/webaudio@0.6.0-&gt;@strudel.cycles/core@0.6.8</title>
<path fill="none" stroke="black" d="M241.66,-350.81C237.99,-348.55 234.63,-345.84 231.73,-342.6 143.1,-243.48 284.75,-144.37 195.73,-45.6 193.96,-43.64 192.03,-41.87 189.96,-40.27"></path>
<polygon fill="black" stroke="black" points="191.74,-37.25 181.4,-34.93 188.03,-43.19 191.74,-37.25"></polygon>
</g><g id="edge2" class="edge">
<title>@strudel.cycles/csound@0.6.2-&gt;@csound/browser@6.18.5</title>
<path fill="none" stroke="black" d="M164.06,-276.96C190.24,-271.62 222.97,-264.93 250.6,-259.29"></path>
<polygon fill="black" stroke="black" points="251.43,-262.69 260.52,-257.26 250.02,-255.84 251.43,-262.69"></polygon>
</g></g>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 KiB

+54
View File
@@ -1,9 +1,12 @@
---
import { pwaInfo } from 'virtual:pwa-info';
import '../styles/index.css';
import { settings } from '../repl/themes.mjs';
const { BASE_URL } = import.meta.env;
const base = BASE_URL;
const { strudelTheme } = settings;
---
<!-- Global Metadata -->
@@ -27,4 +30,55 @@ const base = BASE_URL;
<script src="./make-scrollable-code-focusable.js" is:inline></script>
<script src="/src/pwa.ts"></script>
<!-- this does not work for some reason: -->
<!-- <style is:global define:vars={strudelTheme}></style> -->
<!-- the following variables are just a fallback to make sure everything is readable without JS -->
<style is:global>
:root {
--background: #222;
--lineBackground: #22222250;
--foreground: #fff;
--caret: #ffcc00;
--selection: rgba(128, 203, 196, 0.5);
--selectionMatch: #036dd626;
--lineHighlight: #00000050;
--gutterBackground: transparent;
--gutterForeground: #8a919966;
}
</style>
{pwaInfo && <Fragment set:html={pwaInfo.webManifest.linkTag} />}
<script define:vars={{ settings, strudelTheme }} is:inline>
const themeStyle = document.createElement('style');
themeStyle.id = 'strudel-theme';
document.head.append(themeStyle);
function getTheme(name) {
if (!settings[name]) {
console.warn('theme', name, 'has no settings');
}
return {
name,
settings: settings[name] || settings.strudelTheme,
};
}
function setTheme(name) {
const { settings } = getTheme(name);
// set css variables
themeStyle.innerHTML = `:root {
${Object.entries(settings)
// important to override fallback
.map(([key, value]) => `--${key}: ${value} !important;`)
.join('\n')}
}`;
// tailwind dark mode
if (settings.light) {
document.documentElement.classList.remove('dark');
} else {
document.documentElement.classList.add('dark');
}
// persist theme name
localStorage.setItem('strudel-theme', name || 'strudelTheme');
}
setTheme(localStorage.getItem('strudel-theme'));
document.addEventListener('strudel-theme', (e) => setTheme(e.detail));
</script>
+2 -2
View File
@@ -19,13 +19,13 @@ const langCode = 'en'; // getLanguageFromURL(currentPage);
const sidebar = SIDEBAR[langCode];
---
<nav class="flex justify-between py-2 px-4 items-center h-14 max-h-14 bg-[#161616]" title="Top Navigation">
<nav class="flex justify-between py-2 px-4 items-center h-14 max-h-14 bg-lineHighlight text-foreground" title="Top Navigation">
<!-- <div class="menu-toggle">
<SidebarToggle client:idle />
</div> -->
<div class="flex overflow-visible items-center grow" style="overflow:visible">
<a href="/" class="flex items-center text-2xl space-x-2">
<h1 class="text-white font-bold flex space-x-2 items-baseline text-xl">
<h1 class="font-bold flex space-x-2 items-baseline text-xl">
<span>🌀</span>
<div class="flex space-x-1 items-baseline">
<span class="">strudel</span>
@@ -14,7 +14,7 @@ const langCode = 'en'; // getLanguageFromURL(currentPage);
const sidebar = SIDEBAR[langCode];
---
<nav aria-labelledby="grid-left" class="max-h-full overflow-auto pb-20">
<nav aria-labelledby="grid-left" class="max-h-full overflow-auto pb-20 text-foreground">
<ul>
{
Object.entries(sidebar).map(([header, children]) => (
@@ -27,8 +27,8 @@ const sidebar = SIDEBAR[langCode];
return (
<li class="">
<a
class={`pl-4 py-0.5 w-full hover:bg-header block${
currentPageMatch === child.link ? ' bg-header' : ''
class={`pl-4 py-0.5 w-full hover:bg-lineHighlight block${
currentPageMatch === child.link ? ' bg-lineHighlight' : ''
}`}
href={url}
aria-current={currentPageMatch === child.link ? 'page' : false}
@@ -22,7 +22,7 @@ const currentPage = Astro.url.pathname;
<span>On this Page:</span>
<TableOfContents client:media="(max-width: 50em)" headings={headings} currentPage={currentPage} />
</nav> -->
<div class="prose prose-invert max-w-full pb-8">
<div class="prose dark:prose-invert max-w-full pb-8">
<slot />
</div>
</section>
@@ -9,10 +9,12 @@ type Props = {
};
const { headings, githubEditUrl } = Astro.props as Props;
const currentPage = Astro.url.pathname;
let currentPage = Astro.url.pathname;
// remove slash before #
currentPage = currentPage.endsWith('/') ? currentPage.slice(0, -1) : currentPage;
---
<nav aria-labelledby="grid-right" class="w-64">
<nav aria-labelledby="grid-right" class="w-64 text-foreground">
<TableOfContents client:media="(min-width: 50em)" headings={headings} currentPage={currentPage} />
<MoreMenu editHref={githubEditUrl} />
</nav>
@@ -78,9 +78,9 @@ const TableOfContents: FunctionalComponent<{ headings: MarkdownHeading[]; curren
<a
href={`${currentPage}#${heading.slug}`}
onClick={onLinkClick}
className={`py-0.5 block cursor-pointer w-full border-l-4 border-header hover:bg-header ${
className={`py-0.5 block cursor-pointer w-full border-l-4 border-lineHighlight hover:bg-lineHighlight ${
['pl-4', 'pl-9', 'pl-12'][heading.depth - minDepth]
} ${currentID === heading.slug ? 'bg-header' : ''}`.trim()}
} ${currentID === heading.slug ? 'bg-lineHighlight' : ''}`.trim()}
>
{unescape(heading.text)}
</a>
+3 -2
View File
@@ -65,16 +65,17 @@ export const SIDEBAR: Sidebar = {
{ text: 'Tonal Modifiers', link: 'learn/tonal' },
],
More: [
{ text: 'MIDI & OSC', link: 'learn/input-output' },
{ text: 'Offline', link: 'learn/pwa' },
{ text: 'Patterns', link: 'technical-manual/patterns' },
{ text: 'Pattern Alignment', link: 'technical-manual/alignment' },
{ text: 'MIDI & OSC', link: 'learn/input-output' },
{ text: 'Strudel vs Tidal', link: 'learn/strudel-vs-tidal' },
],
Development: [
{ text: 'REPL', link: 'technical-manual/repl' },
{ text: 'Packages', link: 'technical-manual/packages' },
{ text: 'Docs', link: 'technical-manual/docs' },
{ text: 'Testing', link: 'technical-manual/testing' },
{ text: 'Packages', link: 'technical-manual/packages' },
// { text: 'Internals', link: 'technical-manual/internals' },
],
},
+9
View File
@@ -0,0 +1,9 @@
.cm-activeLine {
background-color: transparent !important;
}
.cm-theme {
background-color: var(--background);
border: 1px solid var(--lineHighlight);
padding: 2px;
}
+14 -1
View File
@@ -2,6 +2,10 @@ import { evalScope, controls } from '@strudel.cycles/core';
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import { useEffect, useState } from 'react';
import { prebake } from '../repl/prebake';
import { themes, settings } from '../repl/themes.mjs';
import './MiniRepl.css';
const theme = localStorage.getItem('strudel-theme') || 'strudelTheme';
let modules;
if (typeof window !== 'undefined') {
@@ -32,9 +36,18 @@ export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) {
.then(([res]) => setRepl(() => res.MiniRepl))
.catch((err) => console.error(err));
}, []);
// const { settings } = useTheme();
return Repl ? (
<div className="mb-4">
<Repl tune={tune} hideOutsideView={true} drawTime={drawTime} punchcard={punchcard} canvasHeight={canvasHeight} />
<Repl
tune={tune}
hideOutsideView={true}
drawTime={drawTime}
punchcard={punchcard}
canvasHeight={canvasHeight}
theme={themes[theme]}
highlightColor={settings[theme]?.foreground}
/>
</div>
) : (
<pre>{tune}</pre>
+4 -2
View File
@@ -23,7 +23,7 @@ export default function MobileNav({ sidebar }) {
<div className="space-y-1 px-4 py-4 bg-[#161616]">
<a
href=".."
className="py-2 flex cursor-pointer items-center space-x-1 hover:bg-bg hover:px-2 rounded-md"
className="py-2 flex cursor-pointer items-center space-x-1 hover:bg-background hover:px-2 rounded-md"
>
<span>go to REPL</span>
</a>
@@ -36,7 +36,9 @@ export default function MobileNav({ sidebar }) {
as="a"
href={`/${item.link}`}
className={classNames(
item.current ? 'bg-bg text-white' : 'text-gray-300 hover:bg-bg hover:text-white',
item.current
? 'bg-background text-white'
: 'text-gray-300 hover:bg-lineHighlight hover:text-white',
'block px-3 py-2 rounded-md text-base font-medium',
)}
aria-current={item.current ? 'page' : undefined}
+25 -19
View File
@@ -10,27 +10,33 @@
height: 70px;
visibility: hidden;
pointer-events: none;
position: relative;
}
.prose h1:hover .icon-link,
.prose h2:hover .icon-link,
.prose h3:hover .icon-link,
.prose h4:hover .icon-link,
.prose h5:hover .icon-link,
.prose h6:hover .icon-link,
.icon.icon-link:hover {
.icon.icon-link {
visibility: hidden;
}
.prose h1:hover .icon.icon-link,
.prose h2:hover .icon.icon-link,
.prose h3:hover .icon.icon-link,
.prose h4:hover .icon.icon-link,
.prose h5:hover .icon.icon-link,
.prose h6:hover .icon.icon-link {
visibility: visible;
}
.icon.icon-link {
background-image: url(./link.svg);
background-repeat: no-repeat;
width: 16px;
height: 16px;
display: block;
position: absolute;
visibility: hidden;
margin-left: -20px;
width: 20px;
margin-top: 8px;
.prose h1 > a,
.prose h2 > a,
.prose h3 > a,
.prose h4 > a,
.prose h5 > a,
.prose h6 > a {
text-decoration: none !important;
}
.icon.icon-link::after {
content: '#';
margin-left: 8px;
font-size: 0.9em;
opacity: 50%;
}
+2 -2
View File
@@ -22,7 +22,7 @@ const currentFile = `src/pages${currentPage.replace(/\/$/, '')}.mdx`;
const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
---
<html dir={frontmatter.dir ?? 'ltr'} lang={frontmatter.lang ?? 'en'} class="initial">
<html dir={frontmatter.dir ?? 'ltr'} lang={frontmatter.lang ?? 'en'} class="initial dark">
<head>
<HeadCommon />
<HeadSEO frontmatter={frontmatter} canonicalUrl={canonicalURL} />
@@ -31,7 +31,7 @@ const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
</title>
</head>
<body class="h-screen text-gray-50">
<body class="h-screen text-gray-50 bg-background">
<div class="w-full h-full space-y-4 flex flex-col">
<header class="max-w-full fixed top-0 w-full z-[100]">
<Header currentPage={currentPage} />
+3 -1
View File
@@ -1,5 +1,5 @@
---
title: Introduction
title: JavaScript API
layout: ../../layouts/MainLayout.astro
---
@@ -71,3 +71,5 @@ You can freely mix JS patterns, mini patterns and values! For example, this patt
While mini notation is almost always shorter, it only has a handful of modifiers: \* / ! @.
When using JS patterns, there is a lot more you can do.
What [Pattern Constructors](/learn/factories) does Strudel offer?
@@ -160,3 +160,5 @@ x(sine.range(0, 200)).y(cosine.range(0, 200));
/>
Note that these params will not do anything until you give them meaning in your custom output!
From modifying parameters we transition to the concept of [Signals](/learn/signals).
+2 -2
View File
@@ -3,12 +3,12 @@ import HeadCommon from '../components/HeadCommon.astro';
import { Repl } from '../repl/Repl.jsx';
---
<html lang="en">
<html lang="en" class="dark">
<head>
<HeadCommon />
<title>Strudel REPL</title>
</head>
<body>
<body class="bg-background">
<Repl client:only="react" />
</body>
</html>
+2 -1
View File
@@ -1,6 +1,5 @@
---
title: Accumulation Modifiers
description: Strudel Tutorial - Coding syntax
layout: ../../layouts/MainLayout.astro
---
@@ -32,3 +31,5 @@ import { JsDoc } from '../../docs/JsDoc';
## stut
<JsDoc client:idle name="stut" h={0} />
There are also [Tonal Modifiers](/learn/tonal).
+1 -2
View File
@@ -1,6 +1,5 @@
---
title: Coding syntax
description: Strudel Tutorial - Coding syntax
layout: ../../layouts/MainLayout.astro
---
@@ -16,7 +15,7 @@ Let's look at this simple example again. What do we notice?
<MiniRepl client:idle tune={`freq("220 275 330 440").s("sine")`} />
- We have a word `freq` which is followed by some brackets `()` with some words/letters/numbers inside, surrounded by quotes `"a3 c#4 e4 a4"`.
- Then we have a dot `.` followed by another similar piece of code `s("sawtooth")`.
- Then we have a dot `.` followed by another similar piece of code `s("sine")`.
- We can also see these texts are _highlighted_ using colours: word `freq` is purple, the brackets `()` are grey, and the content inside the `""` are green.
What happens if we try to 'break' this pattern in different ways?
@@ -59,3 +59,5 @@ import { JsDoc } from '../../docs/JsDoc';
## invert
<JsDoc client:idle name="invert" h={0} />
After Conditional Modifiers, let's see what [Accumulation Modifiers](/learn/accumulation) have to offer.
+2
View File
@@ -150,3 +150,5 @@ In the future, the integration could be improved by passing all patterned contro
This could work by a unique [channel](https://kunstmusik.github.io/icsc2022-csound-web/tutorial2-interacting-with-csound/#step-4---writing-continuous-data-channels)
for each value. Channels could be read [like this](https://github.com/csound/csound/blob/master/Android/CsoundForAndroid/CsoundAndroidExamples/src/main/res/raw/multitouch_xy.csd).
Also, it might make sense to have a standard library of csound instruments for strudel's effects.
Now, let's dive into the [Functional JavaScript API](/functions/intro)
+2 -1
View File
@@ -1,6 +1,5 @@
---
title: Audio effects
description: Strudel Tutorial - Audio effects
layout: ../../layouts/MainLayout.astro
---
@@ -147,3 +146,5 @@ global effects use the same chain for all events of the same orbit:
## roomsize
<JsDoc client:idle name="roomsize" h={0} />
Next, we'll look at strudel's support for [Csound](/learn/csound).
+2 -1
View File
@@ -1,6 +1,5 @@
---
title: Pattern Constructors
description: Strudel Tutorial
layout: ../../layouts/MainLayout.astro
---
@@ -65,3 +64,5 @@ As a chained function:
## run
<JsDoc client:idle name="run" h={0} punchcard />
After Pattern Constructors, let's see what [Time Modifiers](/learn/time-modifiers) are available.
@@ -1,6 +1,5 @@
---
title: Getting Started
description: Strudel Tutorial - Getting Started
layout: ../../layouts/MainLayout.astro
---
+3 -2
View File
@@ -1,6 +1,5 @@
---
title: Other Outptuts
description: Strudel Tutorial
title: MIDI & OSC
layout: ../../layouts/MainLayout.astro
---
@@ -70,3 +69,5 @@ The following functions can be used with [SuperDirt](https://github.com/musikinf
Please refer to [Tidal Docs](https://tidalcycles.org/) for more info.
<br />
But can we use Strudel [offline](/learn/pwa)?
+4 -2
View File
@@ -58,7 +58,7 @@ Try adding or removing notes and notice how the tempo changes!
<MiniRepl client:idle tune={`note("c d e f g a b")`} punchcard />
Note that the overall duration of time does not change, and instead each note length descreases.
Note that the overall duration of time does not change, and instead each note length decreases.
This is a key idea, as it illustrates the 'Cycle' in TidalCycles!
Each space-separated note in this sequence is an _event_.
@@ -103,7 +103,7 @@ Contrary to division, a sequence can be sped up by multiplying it by a number us
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]*2")`} punchcard />
The multiplication by two here means that the sequence will play twice a cycle.
The multiplication by two here means that the sequence will play twice per cycle.
As with divisions, multiplications can be decimal (`*2.75`):
@@ -235,3 +235,5 @@ Starting with this one `n`, can you make a _pattern string_ that uses every sing
<MiniRepl client:idle tune={`n("60")`} />
<br />
Next: How do [Samples](/learn/samples) play into this?
-1
View File
@@ -1,6 +1,5 @@
---
title: Notes
description: Strudel Tutorial - Notes
layout: ../../layouts/MainLayout.astro
---
+85
View File
@@ -0,0 +1,85 @@
---
title: Offline
layout: ../../layouts/MainLayout.astro
---
# Using Strudel Offline
You can use Strudel even without a network! When you first visit the [Strudel REPL](strudel.tidalcycles.org/),
your browser will download the whole web app including documentation.
When the download is finished (&lt;1MB), you can visit the website even when offline,
getting the downloaded website instead of the online one.
When the site gets updated, your browser will download that update on the next online visit.
When an update is available, the site will refresh after the download is finished.
This works because Strudel is implemented as progessive web app (using [Vite PWA](https://vite-pwa-org.netlify.app/)).
## Samples
While the browser will download the app itself, samples are only downloaded when you're actively using them.
So to make sure a specific set of samples is available when offline, just use them.
Also, only samples from these domains will be cached for offline use:
- `https://raw.githubusercontent.com/*` for samples uploaded to github
- `https://freesound.org/*` / `https://cdn.freesound.org/*` for freesound
- `https://shabda.ndre.gr/.*` for shabda
## Inspecting / Clearing Cache
You can view all cached files in your browser.
### Firefox
- Open the Developer Tools (`Tools > Web Developer > Web Developer Tools`)
- go to `Storage` tab and expand `Cache Storage > https://strudel.tidalcycles.org`.
- or go to the `Application` tab and view the latest updates in `Service Workers`
### Chromium based Browsers
- Open Developer Tools (`Right Click > Inspect`)
- go to the `Application` tab
- view downloaded files under `Cache > Cache Storage`
- view the latest updates in `Service Workers`
## Strudel Standalone App
You can also install Strudel as a standalone app on most devices.
A standalone app has its own desktop / homescreen icon and launches in a separate window,
without the browser ui.
<figure>
<img src="./pwa/strudel-macos.png" alt="Strudel on MacOS" />
<figcaption>Strudel on MacOS</figcaption>
</figure>
### Desktop
With a chromium based browser:
1. go to the [Strudel REPL](strudel.tidalcycles.org/).
2. on the right of the adress bar, click `install Strudel REPL`
3. the REPL should now run as a standalone chromium app
Without a chromium based browser, you can use [nativefier](https://github.com/nativefier/nativefier) to generate a desktop app:
1. make sure you have NodeJS installed
2. run `npx nativefier strudel.tidalcycles.org`
<figure>
<img src="./pwa/strudel-linux.png" alt="Strudel on Linux" />
<figcaption>Strudel on Linux</figcaption>
</figure>
### iOS
1. open to the [Strudel REPL](strudel.tidalcycles.org/) in safari
2. press the share icon and tab `Add to homescreen`
3. You should now have a strudel app icon that opens the repl in full screen
### Android
1. open to the [Strudel REPL](strudel.tidalcycles.org/)
2. Tab the install button at the bottom
Ok, what are [Patterns](/technical-manual/patterns) all about?
+3 -2
View File
@@ -1,6 +1,5 @@
---
title: Notes
description: Strudel Tutorial - Notes
title: Samples
layout: ../../layouts/MainLayout.astro
---
@@ -323,3 +322,5 @@ Sampler effects are functions that can be used to change the behaviour of sample
### speed
<JsDoc client:idle name="speed" h={0} />
After samples, let's see what [Synths](/learn/synths) afford us.
+3 -2
View File
@@ -1,6 +1,5 @@
---
title: What is Strudel?
description: Strudel Tutorial
title: Signals
layout: ../../layouts/MainLayout.astro
---
@@ -107,3 +106,5 @@ These methods add random behavior to your Patterns.
## always
<JsDoc client:idle name="Pattern.always" h={0} />
Next up: [Conditional Modifiers](/learn/conditional-modifiers)
-1
View File
@@ -1,6 +1,5 @@
---
title: Sounds
description: Strudel Tutorial - Sounds
layout: ../../layouts/MainLayout.astro
---
@@ -144,3 +144,5 @@ You can get the same tempo as tidal with:
```
note("c a f e").fast(.5625);
```
Next up: the [REPL](/technical-manual/repl)
+3 -2
View File
@@ -1,6 +1,5 @@
---
title: Synths
description: Strudel Tutorial - Synths
layout: ../../layouts/MainLayout.astro
---
@@ -10,7 +9,7 @@ import { JsDoc } from '../../docs/JsDoc';
# Synths
For now, [samples](/learn/samples) are the main way to play with Strudel.
In future, more powerful synthesis capabilities will be added.
In the future, more powerful synthesis capabilities will be added.
If in the meantime you want to dive deeper into audio synthesis with Tidal, you will need to [install SuperCollider and SuperDirt](https://tidalcycles.org/docs/).
## Playing synths with `s`
@@ -28,3 +27,5 @@ The power of patterns allows us to sequence any _param_ independently:
Now we not only pattern the notes, but the sound as well!
`sawtooth` `square` and `triangle` are the basic waveforms available in `s`.
Next up: [Audio Effects](/learn/effects)...
@@ -105,3 +105,5 @@ Some of these have equivalent operators in the Mini Notation:
## ribbon
<JsDoc client:idle name="ribbon" h={0} />
Apart from modifying time, there are ways to [Control Parameters](functions/value-modifiers).
+2
View File
@@ -70,3 +70,5 @@ Together with layer, struct and voicings, this can be used to create a basic bac
x => x.rootNotes(2).note().s('sawtooth').cutoff(800)
)`}
/>
So far, we've stayed within the browser. [MIDI and OSC](/learn/input-output) are ways to break out of it.
@@ -45,3 +45,5 @@ This makes way for other ways to align the pattern, and several are already defi
- `trigzero` is similar to `trig`, but the pattern is 'triggered' from its very first cycle, rather than from the current cycle. `trig` and `trigzero` therefore only give different results where the leftmost pattern differs from one cycle to the next.
We will save going deeper into the background, design and practicalities of these alignment functions for future publications. However in the next section, we take them as a case study for looking at the different design affordances offered by Haskell to Tidal, and JavaScript to Strudel.
Ok, so how do Strudel and Tidal [compare](/learn/strudel-vs-tidal)?
@@ -74,3 +74,5 @@ Documentation is written with [jsdoc](https://jsdoc.app/) comments. Example:
- To regenerate the `doc.json` file manually, run `npm run jsdoc-json`
- The file is used by the `JsDoc` component to find the documentation by name
- Also, it is used for the `examples.test.mjs` snapshot test
How does Strudel do its [Testing](/technical-manual/testing)?
+39 -53
View File
@@ -5,75 +5,61 @@ layout: ../../layouts/MainLayout.astro
import { MiniRepl } from '../../docs/MiniRepl';
## Strudel Packages
# Strudel Packages
The [strudel repo](github.com/tidalcycles/strudel) is organized into packages, using [npm workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces).
Publishing packages is done with [lerna](https://lerna.js.org/).
The [strudel repo](github.com/tidalcycles/strudel) is organized as a monorepo, containing multiple npm packages.
The purpose of the multiple packages is to
There are different packages for different purposes. They..
- organize the codebase into more modular, encapsulated pieces
- be able to opt out of certain functionalities
- keep the dependencies of the core packages small
- split up the code into smaller chunks
- can be selectively used to implement some sort of time based system
## Overview
[See the latest published packages on npm](https://www.npmjs.com/search?q=%40strudel.cycles).
Here is an overview of all the packages:
### Important bits
### Essential Packages
- The [root package.json](https://github.com/tidalcycles/strudel/blob/main/package.json) specifies `packages/*` as `workspaces`
- Each folder in `packages` comes with its own `package.json`, defining a package name of the format `@strudel.cycles/<name>`
- Running `npm i` from the root folder will symlink all packages to the `node_modules` folder, e.g. `node_modules/@strudel.cycles/core` symlinks `packages/core`
- These symlinks allow importing the packages with their package name, instead of a relative path, e.g. `import { seq } from '@strudel.cycles/core'`, instead of `import { seq } from '../core/`.
This works because the [bare module import](https://vitejs.dev/guide/features.html#npm-dependency-resolving-and-pre-bundling) `@strudel.cycles/core` is resolved to `node_modules/@strudel.cycles/core`.
In a project that installs the published packages from npm, these imports will still work, whereas relative ones might not.
- When a strudel package is importing from another strudel package, the package that is imported from should be listed in the `dependencies` field of the `package.json`.
For example, [@strudel.cycles/mini lists `@strudel.cycles/core` as a dependency](https://github.com/tidalcycles/strudel/blob/main/packages/mini/package.json).
- In development, files in any package can be changed and saved to instantly update the dev server via [hot module replacement](https://vitejs.dev/guide/features.html#hot-module-replacement)
- To publish packages, `npx lerna publish` will check which packages were changed since the last publish and publish only those.
The version numbers in the dependencies of each packages will be updated automatically to the latest version.
These package are the most essential. You might want to use all of those if you're using strudel in your project:
### Building & Publishing
- [core](https://github.com/tidalcycles/strudel/tree/main/packages/core#strudelcyclescore): tidal pattern engine with core primitives
- [mini](https://github.com/tidalcycles/strudel/tree/main/packages/mini#strudelcyclesmini): mini notation parser + core bindings
- [transpiler](https://github.com/tidalcycles/strudel/tree/main/packages/transpiler#strudelcyclestranspiler): user code transpiler. syntax sugar + highlighting
Currently, all packages are only published as ESM with vite flavour.
To build standardized ESM and CJS files, a `vite.config.js` like that is needed:
### Language Extensions
```js
import { defineConfig } from 'vite';
import { dependencies } from './package.json';
import { resolve } from 'path';
These packages extend the pattern language by specific functions
// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
build: {
lib: {
entry: resolve(__dirname, 'index.mjs'),
formats: ['es', 'cjs'],
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
},
rollupOptions: {
external: [...Object.keys(dependencies)],
},
target: 'esnext',
},
});
```
- [tonal](https://github.com/tidalcycles/strudel/tree/main/packages/tonal): tonal functions for scales and chords
- [xen](https://github.com/tidalcycles/strudel/tree/main/packages/xen): microtonal / xenharmonic functions
This will build `index.mjs` (ESM) and `index.js` (CJS) to the dist folder.
### Outputs
### What's the main file?
These packages provide bindings for different ways to output strudel patterns:
Currently, each package uses the unbundled `index.mjs` as its main file, which must change for the published version.
There are 2 ways to handle this:
- [webaudio](https://github.com/tidalcycles/strudel/tree/main/packages/webaudio#strudelcycleswebaudio): the default webaudio output
- [osc](https://github.com/tidalcycles/strudel/tree/main/packages/osc#strudelcyclesosc): bindings to communicate via OSC
- [midi](https://github.com/tidalcycles/strudel/tree/main/packages/midi#strudelcyclesmidi): webmidi bindings
- [csound](https://github.com/tidalcycles/strudel/tree/main/packages/csound#strudelcyclescsound): csound bindings
- [soundfonts](https://github.com/tidalcycles/strudel/tree/main/packages/serial#strudelcyclessoundfonts): Soundfont support
- [serial](https://github.com/tidalcycles/strudel/tree/main/packages/serial#strudelcyclesserial): webserial bindings
1. `main` = `dist/index.js`, `module` = `dist/index.mjs`. The built files are used. This means that changing a source file won't take effect in the dev server without a rebuild.
2. Use different `package.json` files for dev vs publish. So the unbuilt `index.mjs` could be used in dev, while the built files can be used when publishing.
### Others
Option 1 could be done with [workspace watching](https://lerna.js.org/docs/features/workspace-watching), although it might make the dev server less snappy..
Option 2 can be done by [publishing just the dist folder and copying over the `package.json` file](https://stackoverflow.com/questions/37862712/how-to-publish-contents-only-of-a-specific-folder).
Sadly, [this does not fit into how lerna works](https://github.com/lerna/lerna/issues/91).
- [embed](https://github.com/tidalcycles/strudel/tree/main/packages/embed#strudelcyclesembed): embeddable REPL web component
- [react](https://github.com/tidalcycles/strudel/tree/main/packages/react#strudelcyclesreact): react hooks and components for strudel
https://github.com/changesets/changesets
### No Longer Maintained
https://turbo.build/repo/docs/handbook/publishing-packages/versioning-and-publishing
- [eval](https://github.com/tidalcycles/strudel/tree/main/packages/eval): old code transpiler
- [tone](https://github.com/tidalcycles/strudel/tree/main/packages/tone#strudelcyclestone): bindings for Tone.js instruments and effects
- [webdirt](https://github.com/tidalcycles/strudel/tree/main/packages/webdirt): webdirt bindings, replaced by webaudio package
### Tools
- [pnpm workspaces](https://pnpm.io/)
- Publishing packages is done with [lerna](https://lerna.js.org/).
## Usage Examples
https://pnpm.io/workspaces
@@ -37,3 +37,5 @@ Each event has a value, a begin time and an end time, where time is represented
Note that the query function is not just a way to access a pattern, but true to the principles of functional programming, is the pattern itself. This means that in theory there is no way to change a pattern, it is opaque as a pure function. In practice though, Strudel and Tidal are all about transforming patterns, so how is this done? The answer is, by replacing the pattern with a new one, that calls the old one. This new one is only able to manipulate the query before passing it to the old pattern, and manipulate the results from it before returning them to caller. But, this is enough to support all the temporal and structural manipulations provided by Strudel (and Tidal's) extensive library of functions.
The above examples do not represent how Strudel is used in practice. In the live coding editor, the user only has to type in the pattern itself, the querying will be handled by the scheduler. The scheduler will repeatedly query the pattern for events, which are then scheduled as sound synthesis or other event triggers.
Can we [align](/technical-manual/alignment) patterns?
+3 -1
View File
@@ -9,7 +9,7 @@ import { MiniRepl } from '../../docs/MiniRepl';
{/* The [REPL](https://strudel.tidalcycles.org/) is the place where all packages come together to form a live coding system. It can also be seen as a reference implementation for users of the library. */}
While Strudel can be used as a library in any JavaScript codebase, its main, reference user interface is the Strudel REPL^[REPL stands for read, evaluate, print/play, loop. It is friendly jargon for an interactive programming interface from computing heritage, usually for a commandline interface but also applied to live coding editors.], which is a browser-based live coding environment. This live code editor is dedicated to manipulating Strudel patterns while they play. The REPL features built-in visual feedback, highlighting which elements in the patterned (mini-notation) sequences are influencing the event that is currently being played. This feedback is designed to support both learning and live use of Strudel.
While Strudel can be used as a library in any JavaScript codebase, its main, reference user interface is the Strudel REPL (REPL stands for read, evaluate, print/play, loop. It is friendly jargon for an interactive programming interface from computing heritage, usually for a commandline interface but also applied to live coding editors.), which is a browser-based live coding environment. This live code editor is dedicated to manipulating Strudel patterns while they play. The REPL features built-in visual feedback, highlighting which elements in the patterned (mini-notation) sequences are influencing the event that is currently being played. This feedback is designed to support both learning and live use of Strudel.
Besides a UI for playback control and meta information, the main part of the REPL interface is the code editor powered by CodeMirror. In it, the user can edit and evaluate pattern code live, using one of the available synthesis outputs to create music and/or sound art. The control flow of the REPL follows 3 basic steps:
@@ -186,3 +186,5 @@ function onTrigger(hap, deadline, duration) {
```
The above example will create an `OscillatorNode` for each event, where the frequency is controlled by the `note` param. In essence, this is how the WebAudio API output of Strudel works, only with many more parameters to control synths, samples and effects.
I want to help, how do I contribute to the [Docs](/technical-manual/docs)?
+44 -13
View File
@@ -1,15 +1,17 @@
import XMarkIcon from '@heroicons/react/20/solid/XMarkIcon';
import { logger } from '@strudel.cycles/core';
import { cx } from '@strudel.cycles/react';
import { useEvent, cx } from '@strudel.cycles/react';
// import { cx } from '@strudel.cycles/react';
import { nanoid } from 'nanoid';
import React, { useContext, useCallback, useLayoutEffect, useRef, useState } from 'react';
import { useEvent, loadedSamples, ReplContext } from './Repl';
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
import { loadedSamples } from './Repl';
import { Reference } from './Reference';
import { themes, themeColors } from './themes.mjs';
export function Footer({ context }) {
// const [activeFooter, setActiveFooter] = useState('console');
// const { activeFooter, setActiveFooter, isZen } = useContext?.(ReplContext);
const { activeFooter, setActiveFooter, isZen } = context;
const { activeFooter, setActiveFooter, isZen, theme, setTheme } = context;
const footerContent = useRef();
const [log, setLog] = useState([]);
@@ -54,8 +56,8 @@ export function Footer({ context }) {
<div
onClick={() => setActiveFooter(name)}
className={cx(
'h-8 px-2 text-white cursor-pointer hover:text-tertiary flex items-center space-x-1 border-b',
activeFooter === name ? 'border-white hover:border-tertiary' : 'border-transparent',
'h-8 px-2 text-foreground cursor-pointer hover:text-tertiary flex items-center space-x-1 border-b',
activeFooter === name ? 'border-foreground hover:border-tertiary' : 'border-transparent',
)}
>
{label || name}
@@ -67,16 +69,17 @@ export function Footer({ context }) {
return null;
}
return (
<footer className="bg-footer z-[20]">
<footer className="bg-lineHighlight z-[20]">
<div className="flex justify-between px-2">
<div className={cx('flex select-none', activeFooter && 'pb-2')}>
<div className={cx('flex select-none max-w-full overflow-auto', activeFooter && 'pb-2')}>
<FooterTab name="intro" label="welcome" />
<FooterTab name="samples" />
<FooterTab name="console" />
<FooterTab name="reference" />
<FooterTab name="theme" />
</div>
{activeFooter !== '' && (
<button onClick={() => setActiveFooter('')} className="text-white" aria-label="Close Panel">
<button onClick={() => setActiveFooter('')} className="text-foreground" aria-label="Close Panel">
<XMarkIcon className="w-5 h-5" />
</button>
)}
@@ -87,7 +90,7 @@ export function Footer({ context }) {
ref={footerContent}
>
{activeFooter === 'intro' && (
<div className="prose prose-invert max-w-[600px] pt-2 font-sans pb-8 px-4">
<div className="prose dark:prose-invert max-w-[600px] pt-2 font-sans pb-8 px-4">
<h3>
<span className={cx('animate-spin inline-block select-none')}>🌀</span> welcome
</h3>
@@ -132,7 +135,7 @@ export function Footer({ context }) {
</div>
)}
{activeFooter === 'console' && (
<div className="break-all px-4">
<div className="break-all px-4 dark:text-white text-stone-900">
{log.map((l, i) => {
const message = linkify(l.message);
return (
@@ -148,8 +151,8 @@ export function Footer({ context }) {
</div>
)}
{activeFooter === 'samples' && (
<div className="break-normal w-full px-4">
<span className="text-white">{loadedSamples.length} banks loaded:</span>
<div className="break-normal w-full px-4 dark:text-white text-stone-900">
<span>{loadedSamples.length} banks loaded:</span>
{loadedSamples.map(([name, samples]) => (
<span key={name} className="cursor-pointer hover:text-tertiary" onClick={() => {}}>
{' '}
@@ -165,6 +168,34 @@ export function Footer({ context }) {
</div>
)}
{activeFooter === 'reference' && <Reference />}
{activeFooter === 'theme' && (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-2 p-2">
{Object.entries(themes).map(([k, t]) => (
<div
key={k}
className={cx(
'border-2 border-transparent cursor-pointer p-4 bg-background bg-opacity-25 rounded-md',
theme === k ? '!border-foreground' : '',
)}
onClick={() => {
setTheme(k);
document.dispatchEvent(
new CustomEvent('strudel-theme', {
detail: k,
}),
);
}}
>
<div className="mb-2 w-full text-center text-foreground">{k}</div>
<div className="flex justify-stretch overflow-hidden rounded-md">
{themeColors(t).map((c, i) => (
<div key={i} className="grow h-6" style={{ background: c }} />
))}
</div>
</div>
))}
</div>
)}
</div>
)}
</footer>
+3 -4
View File
@@ -32,7 +32,7 @@ export function Header({ context }) {
id="header"
className={cx(
'py-1 flex-none w-full text-black justify-between z-[100] text-lg select-none sticky top-0',
!isZen && !isEmbedded && 'bg-header',
!isZen && !isEmbedded && 'bg-lineHighlight',
isEmbedded ? 'flex' : 'md:flex',
)}
>
@@ -48,8 +48,7 @@ export function Header({ context }) {
}}
className={cx(
isEmbedded ? 'text-l cursor-pointer' : 'text-xl',
// 'bg-clip-text bg-gradient-to-r from-primary to-secondary text-transparent font-bold',
'text-white font-bold flex space-x-2 items-center',
'text-foreground font-bold flex space-x-2 items-center',
)}
>
<div
@@ -66,7 +65,7 @@ export function Header({ context }) {
</h1>
</div>
{!isZen && (
<div className="flex max-w-full overflow-auto text-white ">
<div className="flex max-w-full overflow-auto text-foreground">
<button
onClick={handleTogglePlay}
title={started ? 'stop' : 'play'}
+3 -3
View File
@@ -5,16 +5,16 @@ const visibleFunctions = jsdocJson.docs
export function Reference() {
return (
<div className="flex h-full w-full pt-2">
<div className="flex h-full w-full pt-2 text-foreground">
<div className="w-64 flex-none h-full overflow-y-auto overflow-x-hidden pr-4">
{visibleFunctions.map((entry, i) => (
<a key={i} className="cursor-pointer block hover:bg-linegray py-1 px-4" href={`#doc-${i}`}>
<a key={i} className="cursor-pointer block hover:bg-lineHighlight py-1 px-4" href={`#doc-${i}`}>
{entry.name} {/* <span className="text-gray-600">{entry.meta.filename}</span> */}
</a>
))}
</div>
<div className="break-normal w-full h-full overflow-auto pl-4 flex relative">
<div className="prose prose-invert">
<div className="prose dark:prose-invert">
<h2>API Reference</h2>
<p>
This is the long list functions you can use! Remember that you don't need to remember all of those and that
+8 -5
View File
@@ -10,11 +10,14 @@
opacity: 0.5;
}
.cm-gutters {
display: none !important;
}
.cm-content {
#code .cm-content {
padding-top: 12px !important;
padding-left: 8px !important;
}
#code .cm-scroller {
padding-bottom: 50vh;
}
#code .cm-line > * {
background: var(--lineBackground);
}
+13 -14
View File
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import { cleanupDraw, cleanupUi, controls, evalScope, getDrawContext, logger } from '@strudel.cycles/core';
import { CodeMirror, cx, flash, useHighlighting, useStrudel } from '@strudel.cycles/react';
import { CodeMirror, cx, flash, useHighlighting, useStrudel, useKeydown } from '@strudel.cycles/react';
import {
getAudioContext,
getLoadedSamples,
@@ -22,6 +22,10 @@ import { Header } from './Header';
import { prebake } from './prebake.mjs';
import * as tunes from './tunes.mjs';
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
import { themes } from './themes.mjs';
import useTheme from '../useTheme';
const initialTheme = localStorage.getItem('strudel-theme') || 'strudelTheme';
initAudioOnFirstClick();
@@ -109,6 +113,7 @@ export const ReplContext = createContext(null);
export function Repl({ embedded = false }) {
const isEmbedded = embedded || window.location !== window.parent.location;
const [view, setView] = useState(); // codemirror view
const [theme, setTheme] = useState(initialTheme);
const [lastShared, setLastShared] = useState();
const [activeFooter, setActiveFooter] = useState('');
const [isZen, setIsZen] = useState(false);
@@ -167,12 +172,15 @@ export function Repl({ embedded = false }) {
),
);
const { settings } = useTheme();
// highlighting
useHighlighting({
view,
pattern,
active: started && !activeCode?.includes('strudel disable-highlighting'),
getTime: () => scheduler.now(),
color: settings?.foreground,
});
//
@@ -255,6 +263,8 @@ export function Repl({ embedded = false }) {
handleShare,
isZen,
setIsZen,
theme,
setTheme,
};
return (
// bg-gradient-to-t from-blue-900 to-slate-900
@@ -269,6 +279,7 @@ export function Repl({ embedded = false }) {
<Header context={context} />
<section className="grow flex text-gray-100 relative overflow-auto cursor-text pb-0" id="code">
<CodeMirror
theme={themes[theme] || themes.strudelTheme}
value={code}
onChange={handleChangeCode}
onViewChanged={setView}
@@ -276,7 +287,7 @@ export function Repl({ embedded = false }) {
/>
</section>
{error && (
<div className="text-red-500 p-4 bg-lineblack animate-pulse">{error.message || 'Unknown Error :-/'}</div>
<div className="text-red-500 p-4 bg-lineHighlight animate-pulse">{error.message || 'Unknown Error :-/'}</div>
)}
{isEmbedded && !started && (
<button
@@ -292,15 +303,3 @@ export function Repl({ embedded = false }) {
</ReplContext.Provider>
);
}
export function useEvent(name, onTrigger, useCapture = false) {
useEffect(() => {
document.addEventListener(name, onTrigger, useCapture);
return () => {
document.removeEventListener(name, onTrigger, useCapture);
};
}, [onTrigger]);
}
function useKeydown(onTrigger) {
useEvent('keydown', onTrigger, true);
}
+457
View File
@@ -0,0 +1,457 @@
import {
abcdef,
androidstudio,
atomone,
aura,
bespin,
darcula,
dracula,
duotoneDark,
eclipse,
githubDark,
gruvboxDark,
materialDark,
nord,
okaidia,
solarizedDark,
sublime,
tokyoNight,
tokyoNightStorm,
vscodeDark,
xcodeDark,
bbedit,
duotoneLight,
githubLight,
gruvboxLight,
materialLight,
noctisLilac,
solarizedLight,
tokyoNightDay,
xcodeLight,
} from '@uiw/codemirror-themes-all';
import strudelTheme from '@strudel.cycles/react/src/themes/strudel-theme';
export const themes = {
strudelTheme,
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: {
background: '#222',
lineBackground: '#22222250',
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',
},
abcdef: {
background: '#0f0f0f',
lineBackground: '#0f0f0f50',
foreground: '#defdef',
caret: '#00FF00',
selection: '#515151',
selectionMatch: '#515151',
gutterBackground: '#555',
gutterForeground: '#FFFFFF',
lineHighlight: '#314151',
},
androidstudio: {
background: '#282b2e',
lineBackground: '#282b2e50',
foreground: '#a9b7c6',
caret: '#00FF00',
selection: '#343739',
selectionMatch: '#343739',
lineHighlight: '#343739',
},
atomone: {
background: '#272C35',
lineBackground: '#272C3550',
foreground: '#9d9b97',
caret: '#797977',
selection: '#ffffff30',
selectionMatch: '#2B323D',
gutterBackground: '#272C35',
gutterForeground: '#465063',
gutterBorder: 'transparent',
lineHighlight: '#2B323D',
},
aura: {
background: '#21202e',
lineBackground: '#21202e50',
foreground: '#edecee',
caret: '#a277ff',
selection: '#3d375e7f',
selectionMatch: '#3d375e7f',
gutterBackground: '#21202e',
gutterForeground: '#edecee',
gutterBorder: 'transparent',
lineHighlight: '#a394f033',
},
bbedit: {
light: true,
background: '#FFFFFF',
lineBackground: '#FFFFFF50',
foreground: '#000000',
caret: '#FBAC52',
selection: '#FFD420',
selectionMatch: '#FFD420',
gutterBackground: '#f5f5f5',
gutterForeground: '#4D4D4C',
gutterBorder: 'transparent',
lineHighlight: '#00000012',
},
bespin: {
background: '#28211c',
lineBackground: '#28211c50',
foreground: '#9d9b97',
caret: '#797977',
selection: '#36312e',
selectionMatch: '#4f382b',
gutterBackground: '#28211c',
gutterForeground: '#666666',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
darcula: {
background: '#2B2B2B',
lineBackground: '#2B2B2B50',
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: '#282a3650',
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: '#faf8f550',
foreground: '#b29762',
caret: '#93abdc',
selection: '#e3dcce',
selectionMatch: '#e3dcce',
gutterBackground: '#faf8f5',
gutterForeground: '#cdc4b1',
gutterBorder: 'transparent',
lineHighlight: '#EFEFEF',
},
duotoneDark: {
background: '#2a2734',
lineBackground: '#2a273450',
foreground: '#6c6783',
caret: '#ffad5c',
selection: 'rgba(255, 255, 255, 0.1)',
gutterBackground: '#2a2734',
gutterForeground: '#545167',
lineHighlight: '#36334280',
},
eclipse: {
light: true,
background: '#fff',
lineBackground: '#ffffff50',
foreground: '#000',
caret: '#FFFFFF',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
gutterBackground: '#f7f7f7',
gutterForeground: '#999',
lineHighlight: '#e8f2ff',
gutterBorder: 'transparent',
},
githubLight: {
light: true,
background: '#fff',
lineBackground: '#ffffff50',
foreground: '#24292e',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#6e7781',
},
githubDark: {
background: '#0d1117',
lineBackground: '#0d111750',
foreground: '#c9d1d9',
caret: '#c9d1d9',
selection: '#003d73',
selectionMatch: '#003d73',
lineHighlight: '#36334280',
},
gruvboxDark: {
background: '#282828',
lineBackground: '#28282850',
foreground: '#ebdbb2',
caret: '#ebdbb2',
selection: '#bdae93',
selectionMatch: '#bdae93',
lineHighlight: '#3c3836',
gutterBackground: '#282828',
gutterForeground: '#7c6f64',
},
gruvboxLight: {
light: true,
background: '#fbf1c7',
lineBackground: '#fbf1c750',
foreground: '#3c3836',
caret: '#af3a03',
selection: '#ebdbb2',
selectionMatch: '#bdae93',
lineHighlight: '#ebdbb2',
gutterBackground: '#ebdbb2',
gutterForeground: '#665c54',
gutterBorder: 'transparent',
},
materialDark: {
background: '#2e3235',
lineBackground: '#2e323550',
foreground: '#bdbdbd',
caret: '#a0a4ae',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
gutterBackground: '#2e3235',
gutterForeground: '#999',
gutterActiveForeground: '#4f5b66',
lineHighlight: '#545b61',
},
materialLight: {
light: true,
background: '#FAFAFA',
lineBackground: '#FAFAFA50',
foreground: '#90A4AE',
caret: '#272727',
selection: '#80CBC440',
selectionMatch: '#FAFAFA',
gutterBackground: '#FAFAFA',
gutterForeground: '#90A4AE',
gutterBorder: 'transparent',
lineHighlight: '#CCD7DA50',
},
noctisLilac: {
light: true,
background: '#f2f1f8',
lineBackground: '#f2f1f850',
foreground: '#0c006b',
caret: '#5c49e9',
selection: '#d5d1f2',
selectionMatch: '#d5d1f2',
gutterBackground: '#f2f1f8',
gutterForeground: '#0c006b70',
lineHighlight: '#e1def3',
},
nord: {
background: '#2e3440',
lineBackground: '#2e344050',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#3b4252',
selectionMatch: '#e5e9f0',
gutterBackground: '#2e3440',
gutterForeground: '#4c566a',
gutterActiveForeground: '#d8dee9',
lineHighlight: '#4c566a',
},
okaidia: {
background: '#272822',
lineBackground: '#27282250',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#49483E',
selectionMatch: '#49483E',
gutterBackground: '#272822',
gutterForeground: '#FFFFFF70',
lineHighlight: '#00000059',
},
solarizedLight: {
light: true,
background: '#fdf6e3',
lineBackground: '#fdf6e350',
foreground: '#657b83',
caret: '#586e75',
selection: '#dfd9c8',
selectionMatch: '#dfd9c8',
gutterBackground: '#00000010',
gutterForeground: '#657b83',
lineHighlight: '#dfd9c8',
},
solarizedDark: {
background: '#002b36',
lineBackground: '#002b3650',
foreground: '#93a1a1',
caret: '#839496',
selection: '#173541',
selectionMatch: '#aafe661a',
gutterBackground: '#00252f',
gutterForeground: '#839496',
lineHighlight: '#173541',
},
sublime: {
background: '#303841',
lineBackground: '#30384150',
foreground: '#FFFFFF',
caret: '#FBAC52',
selection: '#4C5964',
selectionMatch: '#3A546E',
gutterBackground: '#303841',
gutterForeground: '#FFFFFF70',
lineHighlight: '#00000059',
},
tokyoNightDay: {
light: true,
background: '#e1e2e7',
lineBackground: '#e1e2e750',
foreground: '#3760bf',
caret: '#3760bf',
selection: '#99a7df',
selectionMatch: '#99a7df',
gutterBackground: '#e1e2e7',
gutterForeground: '#3760bf',
gutterBorder: 'transparent',
lineHighlight: '#5f5faf11',
},
tokyoNightStorm: {
background: '#24283b',
lineBackground: '#24283b50',
foreground: '#7982a9',
caret: '#c0caf5',
selection: '#6f7bb630',
selectionMatch: '#1f2335',
gutterBackground: '#24283b',
gutterForeground: '#7982a9',
gutterBorder: 'transparent',
lineHighlight: '#292e42',
},
tokyoNight: {
background: '#1a1b26',
lineBackground: '#1a1b2650',
foreground: '#787c99',
caret: '#c0caf5',
selection: '#515c7e40',
selectionMatch: '#16161e',
gutterBackground: '#1a1b26',
gutterForeground: '#787c99',
gutterBorder: 'transparent',
lineHighlight: '#1e202e',
},
vscodeDark: {
background: '#1e1e1e',
lineBackground: '#1e1e1e50',
foreground: '#9cdcfe',
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',
},
xcodeLight: {
light: true,
background: '#fff',
lineBackground: '#ffffff50',
foreground: '#3D3D3D',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#AFAFAF',
lineHighlight: '#EDF4FF',
},
xcodeDark: {
background: '#292A30',
lineBackground: '#292A3050',
foreground: '#CECFD0',
caret: '#fff',
selection: '#727377',
selectionMatch: '#727377',
lineHighlight: '#2F3239',
},
};
function getColors(str) {
const colorRegex = /#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})/g;
const colors = [];
let match;
while ((match = colorRegex.exec(str)) !== null) {
const color = match[0];
if (!colors.includes(color)) {
colors.push(color);
}
}
return colors;
}
// TODO: remove
export function themeColors(theme) {
return getColors(stringifySafe(theme));
}
function getCircularReplacer() {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
}
function stringifySafe(json) {
return JSON.stringify(json, getCircularReplacer());
}
-4
View File
@@ -1,7 +1,3 @@
body {
background-color: #222;
}
.cm-gutters {
display: none !important;
}
+27
View File
@@ -0,0 +1,27 @@
import { useState } from 'react';
import { settings } from './repl/themes.mjs';
import { useEffect } from 'react';
function useTheme() {
const [theme, setTheme] = useState(localStorage.getItem('strudel-theme'));
useEvent('strudel-theme', (e) => setTheme(e.detail));
const themeSettings = settings[theme || 'strudelTheme'];
return {
theme,
setTheme,
settings: themeSettings,
isDark: !themeSettings.light,
isLight: !!themeSettings.light,
};
}
// TODO: dedupe
function useEvent(name, onTrigger, useCapture = false) {
useEffect(() => {
document.addEventListener(name, onTrigger, useCapture);
return () => {
document.removeEventListener(name, onTrigger, useCapture);
};
}, [onTrigger]);
}
export default useTheme;
+13 -11
View File
@@ -3,6 +3,7 @@
const defaultTheme = require('tailwindcss/defaultTheme');
module.exports = {
darkMode: 'class',
content: [
'./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}',
'../packages/react/src/**/*.{html,js,jsx,md,mdx,ts,tsx}',
@@ -10,18 +11,19 @@ module.exports = {
theme: {
extend: {
colors: {
primary: '#c792ea',
secondary: '#c3e88d',
tertiary: '#82aaff',
highlight: '#ffcc00',
linegray: '#8a91991a',
lineblack: '#00000095',
bg: '#222222',
// header: '#8a91991a',
// footer: '#8a91991a',
header: '#00000050',
// header: 'transparent',
footer: '#00000050',
highlight: '#bb8800',
// codemirror-theme settings
background: 'var(--background)',
lineBackground: 'var(--lineBackground)',
foreground: 'var(--foreground)',
caret: 'var(--caret)',
selection: 'var(--selection)',
selectionMatch: 'var(--selectionMatch)',
gutterBackground: 'var(--gutterBackground)',
gutterForeground: 'var(--gutterForeground)',
gutterBorder: 'var(--gutterBorder)',
lineHighlight: 'var(--lineHighlight)',
},
typography(theme) {
return {