mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-24 05:57:17 -04:00
Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 98dd364ab0 | |||
| ebd1c2f564 | |||
| e15eef9a16 | |||
| bf72908dc9 | |||
| 4bfeaa47bf | |||
| 9a4d8036a6 | |||
| a50bd6375d | |||
| 14508e91a9 | |||
| 2d5064bd5b | |||
| fdc4819e39 | |||
| efddbb616b | |||
| db4d0f26a8 | |||
| 06cffbc31b | |||
| 3cfb0f4c76 | |||
| 436ba067bc | |||
| f8724bf47e | |||
| 1f7e293204 | |||
| 86d2652258 | |||
| f5c0cc2e2d | |||
| 4463d4a417 | |||
| df35a5c9b1 | |||
| dda93b3c13 | |||
| 5de40b6bf5 | |||
| dcbe799d25 | |||
| a5641aef03 | |||
| 668f97a4c8 | |||
| e9d577496b | |||
| 66fba947ac | |||
| 18c7e02609 | |||
| 7e1351507a | |||
| 032acbd27c | |||
| 8b51f0fbd2 | |||
| ead5942ef0 | |||
| cfe369d9fe | |||
| 2750a3c130 | |||
| a7f6d6ecac | |||
| 3680282450 | |||
| 3b3435c999 | |||
| e2ae6278c2 | |||
| 37d6723e3c | |||
| 961bbf6f55 | |||
| fc71d349ed | |||
| 97c8ab0f71 | |||
| 28534ea547 | |||
| 1a75b79a33 | |||
| 75dfb0af35 | |||
| 073fa09648 | |||
| 3015e7b203 | |||
| 7ac0cdc0f9 | |||
| 49234183a3 | |||
| ed7763df92 | |||
| b818a02f76 | |||
| 886f8449fd | |||
| 5de6643604 | |||
| 6283273d81 | |||
| c8f8f02a46 | |||
| 4c5d6f1d6c | |||
| f349e36345 | |||
| 1574c4ab13 | |||
| 0dc9bc3ab6 | |||
| 0006d57a4a | |||
| 70f7e73b9a | |||
| b67b049802 | |||
| ff99dbcd22 | |||
| 0198697737 | |||
| a742a71f67 | |||
| 8f6c1531fa | |||
| d9f56f11cb | |||
| 7ff877464f | |||
| 7c367eb1e8 | |||
| 20848aac09 | |||
| 7716574076 | |||
| 89cd0c769b | |||
| a21b3d788f | |||
| 7994ba8b38 | |||
| 81b4cb2f16 | |||
| 14af1df6a3 | |||
| ac3bd7d7db | |||
| 632e8e9634 | |||
| 1e3f09f69b | |||
| f3f18ffca7 | |||
| ceb3aa0627 | |||
| 014555fe5d | |||
| fdb76867a7 | |||
| 57157fde9c | |||
| 4a3540cf2b | |||
| 540bd938f2 | |||
| c6fbebd996 | |||
| 18d3a7e23a | |||
| cbae355896 | |||
| 924e8a764a | |||
| 40e629cb0e | |||
| 99ec261f79 | |||
| 9daea5bbc2 | |||
| 4704c03d48 | |||
| 9dbf52bedb | |||
| cfd04ab7fa | |||
| 33dbf75e3b | |||
| 8ffaeeb171 | |||
| 451828e5d4 | |||
| 176877f2b8 | |||
| 61b0d08b5f | |||
| f50a6ce1ae | |||
| 8549d6ea8b | |||
| 90a90a87a5 | |||
| 760bdea521 | |||
| 0fd783e83f | |||
| 42caa9058b | |||
| 9ab315ac2c | |||
| a08ad07537 | |||
| c6ce763f7c | |||
| 70d1eb17a7 | |||
| 0393205eda | |||
| 5c05a5a63e | |||
| b01d9f849b | |||
| 0ab904275c | |||
| 5f82a86801 | |||
| 13a5d8de86 | |||
| f024b71808 |
+1
-1
@@ -7,7 +7,7 @@
|
||||
"jsxSingleQuote": false,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"bracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"proseWrap": "preserve",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -15,4 +15,3 @@ export * from './packages/transpiler/index.mjs';
|
||||
export * from './packages/webaudio/index.mjs';
|
||||
export * from './packages/webdirt/index.mjs';
|
||||
export * from './packages/xen/index.mjs';
|
||||
export * from './packages/wam/index.mjs';
|
||||
|
||||
+4
-3
@@ -46,14 +46,15 @@
|
||||
},
|
||||
"homepage": "https://strudel.tidalcycles.org",
|
||||
"dependencies": {
|
||||
"dependency-tree": "^9.0.0",
|
||||
"vitest": "^0.25.7",
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@strudel.cycles/mini": "workspace:*",
|
||||
"@strudel.cycles/tonal": "workspace:*",
|
||||
"@strudel.cycles/transpiler": "workspace:*",
|
||||
"@strudel.cycles/webaudio": "workspace:*",
|
||||
"@strudel.cycles/xen": "workspace:*"
|
||||
"@strudel.cycles/xen": "workspace:*",
|
||||
"acorn": "^8.8.1",
|
||||
"dependency-tree": "^9.0.0",
|
||||
"vitest": "^0.25.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/ui": "^0.25.7",
|
||||
|
||||
+242
-294
@@ -5,19 +5,24 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
*/
|
||||
|
||||
import { Pattern, sequence } from './pattern.mjs';
|
||||
import { zipWith } from './util.mjs';
|
||||
|
||||
const controls = {};
|
||||
const generic_params = [
|
||||
/**
|
||||
* Select a sound / sample by name.
|
||||
* Select a sound / sample by name. When using mininotation, you can also optionally supply 'n' and 'gain' parameters
|
||||
* separated by ':'.
|
||||
*
|
||||
* @name s
|
||||
* @param {string | Pattern} sound The sound / pattern of sounds to pick
|
||||
* @synonyms sound
|
||||
* @example
|
||||
* s("bd hh")
|
||||
* @example
|
||||
* s("bd:0 bd:1 bd:0:0.3 bd:1:1.4")
|
||||
*
|
||||
*/
|
||||
['s', 's', 'sound'],
|
||||
[['s', 'n', 'gain'], 'sound'],
|
||||
/**
|
||||
* Selects the given index from the sample map.
|
||||
* Numbers too high will wrap around.
|
||||
@@ -29,7 +34,7 @@ const generic_params = [
|
||||
* s("bd sd,hh*3").n("<0 1>")
|
||||
*/
|
||||
// also see https://github.com/tidalcycles/strudel/pull/63
|
||||
['f', 'n', 'The sample number to choose for a synth or sampleset'],
|
||||
['n'],
|
||||
/**
|
||||
* Plays the given note name or midi number. A note name consists of
|
||||
*
|
||||
@@ -49,10 +54,8 @@ const generic_params = [
|
||||
* @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"),
|
||||
//['f', 'to', 'for internal sound routing'],
|
||||
[['note', 'n']],
|
||||
|
||||
/**
|
||||
* A pattern of numbers that speed up (or slow down) samples while they play. Currently only supported by osc / superdirt.
|
||||
*
|
||||
@@ -63,7 +66,7 @@ const generic_params = [
|
||||
* s("sax").accelerate("<0 1 2 4 8 16>").slow(2).osc()
|
||||
*
|
||||
*/
|
||||
['f', 'accelerate', 'a pattern of numbers that speed up (or slow down) samples while they play.'],
|
||||
['accelerate'],
|
||||
/**
|
||||
* Controls the gain by an exponential amount.
|
||||
*
|
||||
@@ -73,11 +76,7 @@ const generic_params = [
|
||||
* s("hh*8").gain(".4!2 1 .4!2 1 .4 1")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'gain',
|
||||
'a pattern of numbers that specify volume. Values less than 1 make the sound quieter. Values greater than 1 make the sound louder. For the linear equivalent, see @amp@.',
|
||||
],
|
||||
['gain'],
|
||||
/**
|
||||
* Like {@link gain}, but linear.
|
||||
*
|
||||
@@ -88,17 +87,18 @@ const generic_params = [
|
||||
* s("bd*8").amp(".1*2 .5 .1*2 .5 .1 .5").osc()
|
||||
*
|
||||
*/
|
||||
['f', 'amp', 'like @gain@, but linear.'],
|
||||
['amp'],
|
||||
/**
|
||||
* Amplitude envelope attack time: Specifies how long it takes for the sound to reach its peak value, relative to the onset.
|
||||
*
|
||||
* @name attack
|
||||
* @param {number | Pattern} attack time in seconds.
|
||||
* @synonyms att
|
||||
* @example
|
||||
* note("c3 e3").attack("<0 .1 .5>")
|
||||
*
|
||||
*/
|
||||
['f', 'attack'],
|
||||
['attack', 'att'],
|
||||
|
||||
/**
|
||||
* Select the sound bank to use. To be used together with `s`. The bank name (+ "_") will be prepended to the value of `s`.
|
||||
@@ -109,7 +109,7 @@ const generic_params = [
|
||||
* s("bd sd").bank('RolandTR909') // = s("RolandTR909_bd RolandTR909_sd")
|
||||
*
|
||||
*/
|
||||
['f', 'bank', 'selects sound bank to use'],
|
||||
['bank'],
|
||||
|
||||
/**
|
||||
* Amplitude envelope decay time: the time it takes after the attack time to reach the sustain level.
|
||||
@@ -121,52 +121,46 @@ const generic_params = [
|
||||
* note("c3 e3").decay("<.1 .2 .3 .4>").sustain(0)
|
||||
*
|
||||
*/
|
||||
['f', 'decay', ''],
|
||||
['decay'],
|
||||
/**
|
||||
* Amplitude envelope sustain level: The level which is reached after attack / decay, being sustained until the offset.
|
||||
*
|
||||
* @name sustain
|
||||
* @param {number | Pattern} gain sustain level between 0 and 1
|
||||
* @synonyms sus
|
||||
* @example
|
||||
* note("c3 e3").decay(.2).sustain("<0 .1 .4 .6 1>")
|
||||
*
|
||||
*/
|
||||
['f', 'sustain', ''],
|
||||
['sustain', 'sus'],
|
||||
/**
|
||||
* Amplitude envelope release time: The time it takes after the offset to go from sustain level to zero.
|
||||
*
|
||||
* @name release
|
||||
* @param {number | Pattern} time release time in seconds
|
||||
* @synonyms rel
|
||||
* @example
|
||||
* note("c3 e3 g3 c4").release("<0 .1 .4 .6 1>/2")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'release',
|
||||
'a pattern of numbers to specify the release time (in seconds) of an envelope applied to each sample.',
|
||||
],
|
||||
[
|
||||
'f',
|
||||
'hold',
|
||||
'a pattern of numbers to specify the hold time (in seconds) of an envelope applied to each sample. Only takes effect if `attack` and `release` are also specified.',
|
||||
],
|
||||
['release', 'rel'],
|
||||
['hold'],
|
||||
// TODO: in tidal, it seems to be normalized
|
||||
/**
|
||||
* Sets the center frequency of the **b**and-**p**ass **f**ilter.
|
||||
* Sets the center frequency of the **b**and-**p**ass **f**ilter. When using mininotation, you
|
||||
* can also optionally supply the 'bpq' parameter separated by ':'.
|
||||
*
|
||||
* @name bpf
|
||||
* @param {number | Pattern} frequency center frequency
|
||||
* @synonyms bandf
|
||||
* @synonyms bandf, bp
|
||||
* @example
|
||||
* s("bd sd,hh*3").bpf("<1000 2000 4000 8000>")
|
||||
*
|
||||
*/
|
||||
['f', 'bpf', ''],
|
||||
['f', 'bandf', 'A pattern of numbers from 0 to 1. Sets the center frequency of the band-pass filter.'],
|
||||
[['bandf', 'bandq'], 'bpf', 'bp'],
|
||||
// TODO: in tidal, it seems to be normalized
|
||||
/**
|
||||
* Sets the **b**and-**p**ass **q**-factor (resonance)
|
||||
* Sets the **b**and-**p**ass **q**-factor (resonance).
|
||||
*
|
||||
* @name bpq
|
||||
* @param {number | Pattern} q q factor
|
||||
@@ -175,8 +169,9 @@ const generic_params = [
|
||||
* s("bd sd").bpf(500).bpq("<0 1 2 3>")
|
||||
*
|
||||
*/
|
||||
['f', 'bpq', ''],
|
||||
['f', 'bandq', 'a pattern of anumbers from 0 to 1. Sets the q-factor of the band-pass filter.'],
|
||||
// currently an alias of 'bandq' https://github.com/tidalcycles/strudel/issues/496
|
||||
// ['bpq'],
|
||||
['bandq', 'bpq'],
|
||||
/**
|
||||
* a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample.
|
||||
*
|
||||
@@ -188,11 +183,7 @@ const generic_params = [
|
||||
* s("rave").begin("<0 .25 .5 .75>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'begin',
|
||||
'a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample.',
|
||||
],
|
||||
['begin'],
|
||||
/**
|
||||
* The same as .begin, but cuts off the end off each sample.
|
||||
*
|
||||
@@ -203,11 +194,7 @@ const generic_params = [
|
||||
* s("bd*2,oh*4").end("<.1 .2 .5 1>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'end',
|
||||
'the same as `begin`, but cuts the end off samples, shortening them; e.g. `0.75` to cut off the last quarter of each sample.',
|
||||
],
|
||||
['end'],
|
||||
/**
|
||||
* Loops the sample (from `begin` to `end`) the specified number of times.
|
||||
* Note that the tempo of the loop is not synced with the cycle tempo.
|
||||
@@ -218,7 +205,7 @@ const generic_params = [
|
||||
* s("bd").loop("<1 2 3 4>").osc()
|
||||
*
|
||||
*/
|
||||
['f', 'loop', 'loops the sample (from `begin` to `end`) the specified number of times.'],
|
||||
['loop'],
|
||||
// TODO: currently duplicated with "native" legato
|
||||
// TODO: superdirt legato will do more: https://youtu.be/dQPmE1WaD1k?t=419
|
||||
/**
|
||||
@@ -230,8 +217,8 @@ const generic_params = [
|
||||
* "c4 eb4 g4 bb4".legato("<0.125 .25 .5 .75 1 2 4>")
|
||||
*
|
||||
*/
|
||||
// ['f', 'legato', 'controls the amount of overlap between two adjacent sounds'],
|
||||
// ['f', 'clhatdecay', ''],
|
||||
// ['legato'],
|
||||
// ['clhatdecay'],
|
||||
/**
|
||||
* bit crusher effect.
|
||||
*
|
||||
@@ -241,11 +228,7 @@ const generic_params = [
|
||||
* s("<bd sd>,hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'crush',
|
||||
'bit crushing, a pattern of numbers from 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction).',
|
||||
],
|
||||
['crush'],
|
||||
/**
|
||||
* fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers
|
||||
*
|
||||
@@ -255,11 +238,7 @@ const generic_params = [
|
||||
* s("bd sd,hh*4").coarse("<1 4 8 16 32>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'coarse',
|
||||
'fake-resampling, a pattern of numbers for lowering the sample rate, i.e. 1 for original 2 for half, 3 for a third and so on.',
|
||||
],
|
||||
['coarse'],
|
||||
|
||||
/**
|
||||
* choose the channel the pattern is sent to in superdirt
|
||||
@@ -268,7 +247,7 @@ const generic_params = [
|
||||
* @param {number | Pattern} channel channel number
|
||||
*
|
||||
*/
|
||||
['i', 'channel', 'choose the channel the pattern is sent to in superdirt'],
|
||||
['channel'],
|
||||
/**
|
||||
* In the style of classic drum-machines, `cut` will stop a playing sample as soon as another samples with in same cutgroup is to be played. An example would be an open hi-hat followed by a closed one, essentially muting the open.
|
||||
*
|
||||
@@ -278,35 +257,39 @@ const generic_params = [
|
||||
* s("rd*4").cut(1)
|
||||
*
|
||||
*/
|
||||
[
|
||||
'i',
|
||||
'cut',
|
||||
'In the style of classic drum-machines, `cut` will stop a playing sample as soon as another samples with in same cutgroup is to be played. An example would be an open hi-hat followed by a closed one, essentially muting the open.',
|
||||
],
|
||||
['cut'],
|
||||
/**
|
||||
* Applies the cutoff frequency of the **l**ow-**p**ass **f**ilter.
|
||||
*
|
||||
* When using mininotation, you can also optionally add the 'lpq' parameter, separated by ':'.
|
||||
*
|
||||
* @name lpf
|
||||
* @param {number | Pattern} frequency audible between 0 and 20000
|
||||
* @synonyms cutoff
|
||||
* @synonyms cutoff, ctf, lp
|
||||
* @example
|
||||
* s("bd sd,hh*3").lpf("<4000 2000 1000 500 200 100>")
|
||||
* @example
|
||||
* s("bd*8").lpf("1000:0 1000:10 1000:20 1000:30")
|
||||
*
|
||||
*/
|
||||
['f', 'lpf'],
|
||||
['f', 'cutoff', 'a pattern of numbers from 0 to 1. Applies the cutoff frequency of the low-pass filter.'],
|
||||
[['cutoff', 'resonance'], 'ctf', 'lpf', 'lp'],
|
||||
/**
|
||||
* Applies the cutoff frequency of the **h**igh-**p**ass **f**ilter.
|
||||
*
|
||||
* When using mininotation, you can also optionally add the 'hpq' parameter, separated by ':'.
|
||||
*
|
||||
* @name hpf
|
||||
* @param {number | Pattern} frequency audible between 0 and 20000
|
||||
* @synonyms hcutoff
|
||||
* @synonyms hp, hcutoff
|
||||
* @example
|
||||
* s("bd sd,hh*4").hpf("<4000 2000 1000 500 200 100>")
|
||||
* @example
|
||||
* s("bd sd,hh*4").hpf("<2000 2000:25>")
|
||||
*
|
||||
*/
|
||||
['f', 'hpf', ''],
|
||||
['f', 'hcutoff', ''],
|
||||
// currently an alias of 'hcutoff' https://github.com/tidalcycles/strudel/issues/496
|
||||
// ['hpf'],
|
||||
[['hcutoff', 'hresonance'], 'hpf', 'hp'],
|
||||
/**
|
||||
* Controls the **h**igh-**p**ass **q**-value.
|
||||
*
|
||||
@@ -317,8 +300,7 @@ const generic_params = [
|
||||
* s("bd sd,hh*4").hpf(2000).hpq("<0 10 20 30>")
|
||||
*
|
||||
*/
|
||||
['f', 'hresonance', ''],
|
||||
['f', 'hpq', ''],
|
||||
['hresonance', 'hpq'],
|
||||
/**
|
||||
* Controls the **l**ow-**p**ass **q**-value.
|
||||
*
|
||||
@@ -329,8 +311,8 @@ const generic_params = [
|
||||
* s("bd sd,hh*4").lpf(2000).lpq("<0 10 20 30>")
|
||||
*
|
||||
*/
|
||||
['f', 'lpq'],
|
||||
['f', 'resonance', ''],
|
||||
// currently an alias of 'resonance' https://github.com/tidalcycles/strudel/issues/496
|
||||
['resonance', 'lpq'],
|
||||
/**
|
||||
* DJ filter, below 0.5 is low pass filter, above is high pass filter.
|
||||
*
|
||||
@@ -340,40 +322,48 @@ const generic_params = [
|
||||
* n("0 3 7 [10,24]").s('superzow').octave(3).djf("<.5 .25 .5 .75>").osc()
|
||||
*
|
||||
*/
|
||||
['f', 'djf', 'DJ filter, below 0.5 is low pass filter, above is high pass filter.'],
|
||||
// ['f', 'cutoffegint', ''],
|
||||
['djf'],
|
||||
// ['cutoffegint'],
|
||||
// TODO: does not seem to work
|
||||
/**
|
||||
* Sets the level of the delay signal.
|
||||
*
|
||||
* When using mininotation, you can also optionally add the 'delaytime' and 'delayfeedback' parameter,
|
||||
* separated by ':'.
|
||||
*
|
||||
*
|
||||
* @name delay
|
||||
* @param {number | Pattern} level between 0 and 1
|
||||
* @example
|
||||
* s("bd").delay("<0 .25 .5 1>")
|
||||
* @example
|
||||
* s("bd bd").delay("0.65:0.25:0.9 0.65:0.125:0.7")
|
||||
*
|
||||
*/
|
||||
['f', 'delay', 'a pattern of numbers from 0 to 1. Sets the level of the delay signal.'],
|
||||
[['delay', 'delaytime', 'delayfeedback']],
|
||||
/**
|
||||
* Sets the level of the signal that is fed back into the delay.
|
||||
* Caution: Values >= 1 will result in a signal that gets louder and louder! Don't do it
|
||||
*
|
||||
* @name delayfeedback
|
||||
* @param {number | Pattern} feedback between 0 and 1
|
||||
* @synonyms delayfb, dfb
|
||||
* @example
|
||||
* s("bd").delay(.25).delayfeedback("<.25 .5 .75 1>").slow(2)
|
||||
*
|
||||
*/
|
||||
['f', 'delayfeedback', 'a pattern of numbers from 0 to 1. Sets the amount of delay feedback.'],
|
||||
['delayfeedback', 'delayfb', 'dfb'],
|
||||
/**
|
||||
* Sets the time of the delay effect.
|
||||
*
|
||||
* @name delaytime
|
||||
* @param {number | Pattern} seconds between 0 and Infinity
|
||||
* @synonyms delayt, dt
|
||||
* @example
|
||||
* s("bd").delay(.25).delaytime("<.125 .25 .5 1>").slow(2)
|
||||
*
|
||||
*/
|
||||
['f', 'delaytime', 'a pattern of numbers from 0 to 1. Sets the length of the delay.'],
|
||||
['delaytime', 'delayt', 'dt'],
|
||||
/* // TODO: test
|
||||
* Specifies whether delaytime is calculated relative to cps.
|
||||
*
|
||||
@@ -383,22 +373,19 @@ const generic_params = [
|
||||
* s("sd").delay().lock(1).osc()
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'lock',
|
||||
'A pattern of numbers. Specifies whether delaytime is calculated relative to cps. When set to 1, delaytime is a direct multiple of a cycle.',
|
||||
],
|
||||
['lock'],
|
||||
/**
|
||||
* Set detune of oscillators. Works only with some synths, see <a target="_blank" href="https://tidalcycles.org/docs/patternlib/tutorials/synthesizers">tidal doc</a>
|
||||
*
|
||||
* @name detune
|
||||
* @param {number | Pattern} amount between 0 and 1
|
||||
* @synonyms det
|
||||
* @superdirtOnly
|
||||
* @example
|
||||
* n("0 3 7").s('superzow').octave(3).detune("<0 .25 .5 1 2>").osc()
|
||||
*
|
||||
*/
|
||||
['f', 'detune', ''],
|
||||
['detune', 'det'],
|
||||
/**
|
||||
* Set dryness of reverb. See {@link room} and {@link size} for more information about reverb.
|
||||
*
|
||||
@@ -409,11 +396,7 @@ const generic_params = [
|
||||
* @superdirtOnly
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'dry',
|
||||
'when set to `1` will disable all reverb for this pattern. See `room` and `size` for more information about reverb.',
|
||||
],
|
||||
['dry'],
|
||||
// TODO: does not seem to do anything
|
||||
/*
|
||||
* Used when using {@link begin}/{@link end} or {@link chop}/{@link striate} and friends, to change the fade out time of the 'grain' envelope.
|
||||
@@ -424,17 +407,9 @@ const generic_params = [
|
||||
* s("oh*4").end(.1).fadeTime("<0 .2 .4 .8>").osc()
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'fadeTime',
|
||||
"Used when using begin/end or chop/striate and friends, to change the fade out time of the 'grain' envelope.",
|
||||
],
|
||||
['fadeTime', 'fadeOutTime'],
|
||||
// TODO: see above
|
||||
[
|
||||
'f',
|
||||
'fadeInTime',
|
||||
'As with fadeTime, but controls the fade in time of the grain envelope. Not used if the grain begins at position 0 in the sample.',
|
||||
],
|
||||
['fadeInTime'],
|
||||
/**
|
||||
* Set frequency of sound.
|
||||
*
|
||||
@@ -446,15 +421,15 @@ const generic_params = [
|
||||
* freq("110".mul.out(".5 1.5 .6 [2 3]")).s("superzow").osc()
|
||||
*
|
||||
*/
|
||||
['f', 'freq', ''],
|
||||
['freq'],
|
||||
// TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate
|
||||
['f', 'gate', ''],
|
||||
// ['f', 'hatgrain', ''],
|
||||
// ['f', 'lagogo', ''],
|
||||
// ['f', 'lclap', ''],
|
||||
// ['f', 'lclaves', ''],
|
||||
// ['f', 'lclhat', ''],
|
||||
// ['f', 'lcrash', ''],
|
||||
['gate', 'gat'],
|
||||
// ['hatgrain'],
|
||||
// ['lagogo'],
|
||||
// ['lclap'],
|
||||
// ['lclaves'],
|
||||
// ['lclhat'],
|
||||
// ['lcrash'],
|
||||
// TODO:
|
||||
// https://tidalcycles.org/docs/reference/audio_effects/#leslie-1
|
||||
// https://tidalcycles.org/docs/reference/audio_effects/#leslie
|
||||
@@ -468,7 +443,7 @@ const generic_params = [
|
||||
* @superdirtOnly
|
||||
*
|
||||
*/
|
||||
['f', 'leslie', ''],
|
||||
['leslie'],
|
||||
/**
|
||||
* Rate of modulation / rotation for leslie effect
|
||||
*
|
||||
@@ -480,7 +455,7 @@ const generic_params = [
|
||||
*
|
||||
*/
|
||||
// TODO: the rate seems to "lag" (in the example, 1 will be fast)
|
||||
['f', 'lrate', ''],
|
||||
['lrate'],
|
||||
/**
|
||||
* Physical size of the cabinet in meters. Be careful, it might be slightly larger than your computer. Affects the Doppler amount (pitch warble)
|
||||
*
|
||||
@@ -491,31 +466,28 @@ const generic_params = [
|
||||
* @superdirtOnly
|
||||
*
|
||||
*/
|
||||
['f', 'lsize', ''],
|
||||
// ['f', 'lfo', ''],
|
||||
// ['f', 'lfocutoffint', ''],
|
||||
// ['f', 'lfodelay', ''],
|
||||
// ['f', 'lfoint', ''],
|
||||
// ['f', 'lfopitchint', ''],
|
||||
// ['f', 'lfoshape', ''],
|
||||
// ['f', 'lfosync', ''],
|
||||
// ['f', 'lhitom', ''],
|
||||
// ['f', 'lkick', ''],
|
||||
// ['f', 'llotom', ''],
|
||||
// ['f', 'lophat', ''],
|
||||
// ['f', 'lsnare', ''],
|
||||
['f', 'degree', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'mtranspose', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'ctranspose', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'harmonic', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'stepsPerOctave', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'octaveR', ''], // TODO: what is this? not found in tidal doc
|
||||
// TODO: why is this needed? what's the difference to late / early?
|
||||
[
|
||||
'f',
|
||||
'nudge',
|
||||
'Nudges events into the future by the specified number of seconds. Negative numbers work up to a point as well (due to internal latency)',
|
||||
],
|
||||
['lsize'],
|
||||
// ['lfo'],
|
||||
// ['lfocutoffint'],
|
||||
// ['lfodelay'],
|
||||
// ['lfoint'],
|
||||
// ['lfopitchint'],
|
||||
// ['lfoshape'],
|
||||
// ['lfosync'],
|
||||
// ['lhitom'],
|
||||
// ['lkick'],
|
||||
// ['llotom'],
|
||||
// ['lophat'],
|
||||
// ['lsnare'],
|
||||
['degree'], // TODO: what is this? not found in tidal doc
|
||||
['mtranspose'], // TODO: what is this? not found in tidal doc
|
||||
['ctranspose'], // TODO: what is this? not found in tidal doc
|
||||
['harmonic'], // TODO: what is this? not found in tidal doc
|
||||
['stepsPerOctave'], // TODO: what is this? not found in tidal doc
|
||||
['octaveR'], // TODO: what is this? not found in tidal doc
|
||||
// TODO: why is this needed? what's the difference to late / early? Answer: it's in seconds, and delays the message at
|
||||
// OSC time (so can't be negative, at least not beyond the latency value)
|
||||
['nudge'],
|
||||
// TODO: the following doc is just a guess, it's not documented in tidal doc.
|
||||
/**
|
||||
* Sets the default octave of a synth.
|
||||
@@ -526,9 +498,9 @@ const generic_params = [
|
||||
* 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
|
||||
// ['f', 'ophatdecay', ''],
|
||||
['octave'],
|
||||
['offset'], // TODO: what is this? not found in tidal doc
|
||||
// ['ophatdecay'],
|
||||
// TODO: example
|
||||
/**
|
||||
* An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects.
|
||||
@@ -541,13 +513,9 @@ const generic_params = [
|
||||
* s("~ sd").delay(.5).delaytime(.125).orbit(2)
|
||||
* )
|
||||
*/
|
||||
[
|
||||
'i',
|
||||
'orbit',
|
||||
'a pattern of numbers. An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share hardware output bus offset and global effects, e.g. reverb and delay. The maximum number of orbits is specified in the superdirt startup, numbers higher than maximum will wrap around.',
|
||||
],
|
||||
['f', 'overgain', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'overshape', ''], // TODO: what is this? not found in tidal doc
|
||||
['orbit'],
|
||||
['overgain'], // TODO: what is this? not found in tidal doc Answer: gain is limited to maximum of 2. This allows you to go over that
|
||||
['overshape'], // TODO: what is this? not found in tidal doc. Similar to above, but limited to 1
|
||||
/**
|
||||
* Sets position in stereo.
|
||||
*
|
||||
@@ -557,11 +525,7 @@ const generic_params = [
|
||||
* s("[bd hh]*2").pan("<.5 1 .5 0>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'pan',
|
||||
'a pattern of numbers between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel)',
|
||||
],
|
||||
['pan'],
|
||||
// TODO: this has no effect (see example)
|
||||
/*
|
||||
* Controls how much multichannel output is fanned out
|
||||
@@ -572,11 +536,7 @@ const generic_params = [
|
||||
* s("[bd hh]*2").pan("<.5 1 .5 0>").panspan("<0 .5 1>").osc()
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'panspan',
|
||||
'a pattern of numbers between -inf and inf, which controls how much multichannel output is fanned out (negative is backwards ordering)',
|
||||
],
|
||||
['panspan'],
|
||||
// TODO: this has no effect (see example)
|
||||
/*
|
||||
* Controls how much multichannel output is spread
|
||||
@@ -587,50 +547,42 @@ const generic_params = [
|
||||
* s("[bd hh]*2").pan("<.5 1 .5 0>").pansplay("<0 .5 1>").osc()
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'pansplay',
|
||||
'a pattern of numbers between 0.0 and 1.0, which controls the multichannel spread range (multichannel only)',
|
||||
],
|
||||
[
|
||||
'f',
|
||||
'panwidth',
|
||||
'a pattern of numbers between 0.0 and inf, which controls how much each channel is distributed over neighbours (multichannel only)',
|
||||
],
|
||||
[
|
||||
'f',
|
||||
'panorient',
|
||||
'a pattern of numbers between -1.0 and 1.0, which controls the relative position of the centre pan in a pair of adjacent speakers (multichannel only)',
|
||||
],
|
||||
// ['f', 'pitch1', ''],
|
||||
// ['f', 'pitch2', ''],
|
||||
// ['f', 'pitch3', ''],
|
||||
// ['f', 'portamento', ''],
|
||||
['pansplay'],
|
||||
['panwidth'],
|
||||
['panorient'],
|
||||
// ['pitch1'],
|
||||
// ['pitch2'],
|
||||
// ['pitch3'],
|
||||
// ['portamento'],
|
||||
// TODO: LFO rate see https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare
|
||||
['f', 'rate', "used in SuperDirt softsynths as a control rate or 'speed'"],
|
||||
['rate'],
|
||||
// TODO: slide param for certain synths
|
||||
['f', 'slide', ''],
|
||||
['slide'],
|
||||
// TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare
|
||||
['f', 'semitone', ''],
|
||||
['semitone'],
|
||||
// TODO: dedup with synth param, see https://tidalcycles.org/docs/reference/synthesizers/#superpiano
|
||||
// ['f', 'velocity', ''],
|
||||
['f', 'voice', ''], // TODO: synth param
|
||||
// ['velocity'],
|
||||
['voice'], // TODO: synth param
|
||||
/**
|
||||
* Sets the level of reverb.
|
||||
*
|
||||
* When using mininotation, you can also optionally add the 'size' parameter, separated by ':'.
|
||||
*
|
||||
* @name room
|
||||
* @param {number | Pattern} level between 0 and 1
|
||||
* @example
|
||||
* s("bd sd").room("<0 .2 .4 .6 .8 1>")
|
||||
* @example
|
||||
* s("bd sd").room("<0.9:1 0.9:4>")
|
||||
*
|
||||
*/
|
||||
['f', 'room', 'a pattern of numbers from 0 to 1. Sets the level of reverb.'],
|
||||
[['room', 'size']],
|
||||
/**
|
||||
* Sets the room size of the reverb, see {@link room}.
|
||||
*
|
||||
* @name roomsize
|
||||
* @synonyms size
|
||||
* @param {number | Pattern} size between 0 and 10
|
||||
* @synonyms size, sz
|
||||
* @example
|
||||
* s("bd sd").room(.8).roomsize("<0 1 2 4 8>")
|
||||
*
|
||||
@@ -638,20 +590,11 @@ const generic_params = [
|
||||
// TODO: find out why :
|
||||
// s("bd sd").room(.8).roomsize("<0 .2 .4 .6 .8 [1,0]>").osc()
|
||||
// .. does not work. Is it because room is only one effect?
|
||||
[
|
||||
'f',
|
||||
'size',
|
||||
'a pattern of numbers from 0 to 1. Sets the perceptual size (reverb time) of the `room` to be used in reverb.',
|
||||
],
|
||||
[
|
||||
'f',
|
||||
'roomsize',
|
||||
'a pattern of numbers from 0 to 1. Sets the perceptual size (reverb time) of the `room` to be used in reverb.',
|
||||
],
|
||||
// ['f', 'sagogo', ''],
|
||||
// ['f', 'sclap', ''],
|
||||
// ['f', 'sclaves', ''],
|
||||
// ['f', 'scrash', ''],
|
||||
['size', 'sz', 'roomsize'],
|
||||
// ['sagogo'],
|
||||
// ['sclap'],
|
||||
// ['sclaves'],
|
||||
// ['scrash'],
|
||||
/**
|
||||
* Wave shaping distortion. CAUTION: it might get loud
|
||||
*
|
||||
@@ -661,11 +604,7 @@ const generic_params = [
|
||||
* s("bd sd,hh*4").shape("<0 .2 .4 .6 .8>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'shape',
|
||||
'wave shaping distortion, a pattern of numbers from 0 for no distortion up to 1 for loads of distortion.',
|
||||
],
|
||||
['shape'],
|
||||
/**
|
||||
* Changes the speed of sample playback, i.e. a cheap way of changing pitch.
|
||||
*
|
||||
@@ -677,11 +616,7 @@ const generic_params = [
|
||||
* speed("1 1.5*2 [2 1.1]").s("piano").clip(1)
|
||||
*
|
||||
*/
|
||||
[
|
||||
'f',
|
||||
'speed',
|
||||
'a pattern of numbers which changes the speed of sample playback, i.e. a cheap way of changing pitch. Negative values will play the sample backwards!',
|
||||
],
|
||||
['speed'],
|
||||
/**
|
||||
* Used in conjunction with {@link speed}, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`.
|
||||
*
|
||||
@@ -692,11 +627,7 @@ const generic_params = [
|
||||
* @superdirtOnly
|
||||
*
|
||||
*/
|
||||
[
|
||||
's',
|
||||
'unit',
|
||||
'used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`.',
|
||||
],
|
||||
['unit'],
|
||||
/**
|
||||
* Made by Calum Gunn. Reminiscent of some weird mixture of filter, ring-modulator and pitch-shifter. The SuperCollider manual defines Squiz as:
|
||||
*
|
||||
@@ -709,14 +640,14 @@ const generic_params = [
|
||||
* @superdirtOnly
|
||||
*
|
||||
*/
|
||||
['f', 'squiz', ''],
|
||||
['f', 'stutterdepth', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'stuttertime', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'timescale', ''], // TODO: what is this? not found in tidal doc
|
||||
['f', 'timescalewin', ''], // TODO: what is this? not found in tidal doc
|
||||
// ['f', 'tomdecay', ''],
|
||||
// ['f', 'vcfegint', ''],
|
||||
// ['f', 'vcoegint', ''],
|
||||
['squiz'],
|
||||
// ['stutterdepth'], // TODO: what is this? not found in tidal doc
|
||||
// ['stuttertime'], // TODO: what is this? not found in tidal doc
|
||||
// ['timescale'], // TODO: what is this? not found in tidal doc
|
||||
// ['timescalewin'], // TODO: what is this? not found in tidal doc
|
||||
// ['tomdecay'],
|
||||
// ['vcfegint'],
|
||||
// ['vcoegint'],
|
||||
// TODO: Use a rest (~) to override the effect <- vowel
|
||||
/**
|
||||
*
|
||||
@@ -729,11 +660,7 @@ const generic_params = [
|
||||
* .vowel("<a e i <o u>>")
|
||||
*
|
||||
*/
|
||||
[
|
||||
's',
|
||||
'vowel',
|
||||
'formant filter to make things sound like vowels, a pattern of either `a`, `e`, `i`, `o` or `u`. Use a rest (`~`) for no effect.',
|
||||
],
|
||||
['vowel'],
|
||||
/* // TODO: find out how it works
|
||||
* Made by Calum Gunn. Divides an audio stream into tiny segments, using the signal's zero-crossings as segment boundaries, and discards a fraction of them. Takes a number between 1 and 100, denoted the percentage of segments to drop. The SuperCollider manual describes the Waveloss effect this way:
|
||||
*
|
||||
@@ -744,12 +671,12 @@ const generic_params = [
|
||||
*
|
||||
* @name waveloss
|
||||
*/
|
||||
['f', 'waveloss', ''],
|
||||
['waveloss'],
|
||||
// TODO: midi effects?
|
||||
['f', 'dur', ''],
|
||||
// ['f', 'modwheel', ''],
|
||||
['f', 'expression', ''],
|
||||
['f', 'sustainpedal', ''],
|
||||
['dur'],
|
||||
// ['modwheel'],
|
||||
['expression'],
|
||||
['sustainpedal'],
|
||||
/* // TODO: doesn't seem to do anything
|
||||
*
|
||||
* Tremolo Audio DSP effect
|
||||
@@ -760,59 +687,58 @@ const generic_params = [
|
||||
* n("0,4,7").tremolodepth("<0 .3 .6 .9>").osc()
|
||||
*
|
||||
*/
|
||||
// TODO: tremdp alias
|
||||
['f', 'tremolodepth', "Tremolo Audio DSP effect | params are 'tremolorate' and 'tremolodepth'"],
|
||||
['f', 'tremolorate', "Tremolo Audio DSP effect | params are 'tremolorate' and 'tremolodepth'"],
|
||||
['tremolodepth', 'tremdp'],
|
||||
['tremolorate', 'tremr'],
|
||||
// TODO: doesn't seem to do anything
|
||||
['f', 'phaserdepth', "Phaser Audio DSP effect | params are 'phaserrate' and 'phaserdepth'"],
|
||||
['f', 'phaserrate', "Phaser Audio DSP effect | params are 'phaserrate' and 'phaserdepth'"],
|
||||
['phaserdepth', 'phasdp'],
|
||||
['phaserrate', 'phasr'],
|
||||
|
||||
['f', 'fshift', 'frequency shifter'],
|
||||
['f', 'fshiftnote', 'frequency shifter'],
|
||||
['f', 'fshiftphase', 'frequency shifter'],
|
||||
['fshift'],
|
||||
['fshiftnote'],
|
||||
['fshiftphase'],
|
||||
|
||||
['f', 'triode', 'tube distortion'],
|
||||
['f', 'krush', 'shape/bass enhancer'],
|
||||
['f', 'kcutoff', ''],
|
||||
['f', 'octer', 'octaver effect'],
|
||||
['f', 'octersub', 'octaver effect'],
|
||||
['f', 'octersubsub', 'octaver effect'],
|
||||
['f', 'ring', 'ring modulation'],
|
||||
['f', 'ringf', 'ring modulation'],
|
||||
['f', 'ringdf', 'ring modulation'],
|
||||
['f', 'distort', 'noisy fuzzy distortion'],
|
||||
['f', 'freeze', 'Spectral freeze'],
|
||||
['f', 'xsdelay', ''],
|
||||
['f', 'tsdelay', ''],
|
||||
['f', 'real', 'Spectral conform'],
|
||||
['f', 'imag', ''],
|
||||
['f', 'enhance', 'Spectral enhance'],
|
||||
['f', 'partials', ''],
|
||||
['f', 'comb', 'Spectral comb'],
|
||||
['f', 'smear', 'Spectral smear'],
|
||||
['f', 'scram', 'Spectral scramble'],
|
||||
['f', 'binshift', 'Spectral binshift'],
|
||||
['f', 'hbrick', 'High pass sort of spectral filter'],
|
||||
['f', 'lbrick', 'Low pass sort of spectral filter'],
|
||||
['f', 'midichan', ''],
|
||||
['f', 'control', ''],
|
||||
['f', 'ccn', ''],
|
||||
['f', 'ccv', ''],
|
||||
['f', 'polyTouch', ''],
|
||||
['f', 'midibend', ''],
|
||||
['f', 'miditouch', ''],
|
||||
['f', 'ctlNum', ''],
|
||||
['f', 'frameRate', ''],
|
||||
['f', 'frames', ''],
|
||||
['f', 'hours', ''],
|
||||
['s', 'midicmd', ''],
|
||||
['f', 'minutes', ''],
|
||||
['f', 'progNum', ''],
|
||||
['f', 'seconds', ''],
|
||||
['f', 'songPtr', ''],
|
||||
['f', 'uid', ''],
|
||||
['f', 'val', ''],
|
||||
['f', 'cps', ''],
|
||||
['triode'],
|
||||
['krush'],
|
||||
['kcutoff'],
|
||||
['octer'],
|
||||
['octersub'],
|
||||
['octersubsub'],
|
||||
['ring'],
|
||||
['ringf'],
|
||||
['ringdf'],
|
||||
['distort'],
|
||||
['freeze'],
|
||||
['xsdelay'],
|
||||
['tsdelay'],
|
||||
['real'],
|
||||
['imag'],
|
||||
['enhance'],
|
||||
['partials'],
|
||||
['comb'],
|
||||
['smear'],
|
||||
['scram'],
|
||||
['binshift'],
|
||||
['hbrick'],
|
||||
['lbrick'],
|
||||
['midichan'],
|
||||
['control'],
|
||||
['ccn'],
|
||||
['ccv'],
|
||||
['polyTouch'],
|
||||
['midibend'],
|
||||
['miditouch'],
|
||||
['ctlNum'],
|
||||
['frameRate'],
|
||||
['frames'],
|
||||
['hours'],
|
||||
['midicmd'],
|
||||
['minutes'],
|
||||
['progNum'],
|
||||
['seconds'],
|
||||
['songPtr'],
|
||||
['uid'],
|
||||
['val'],
|
||||
['cps'],
|
||||
/**
|
||||
* If set to 1, samples will be cut to the duration of their event.
|
||||
* In tidal, this would be done with legato, which [is about to land in strudel too](https://github.com/tidalcycles/strudel/issues/111)
|
||||
@@ -823,33 +749,55 @@ const generic_params = [
|
||||
* note("c a f e ~").s("piano").clip(1)
|
||||
*
|
||||
*/
|
||||
['f', 'clip', ''],
|
||||
['clip'],
|
||||
];
|
||||
|
||||
// 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 }));
|
||||
controls.createParam = function (names) {
|
||||
const name = Array.isArray(names) ? names[0] : names;
|
||||
|
||||
const _setter = (func, name) =>
|
||||
function (...pats) {
|
||||
var withVal;
|
||||
if (Array.isArray(names)) {
|
||||
withVal = (xs) => {
|
||||
if (Array.isArray(xs)) {
|
||||
const result = {};
|
||||
xs.forEach((x, i) => {
|
||||
if (i < names.length) {
|
||||
result[names[i]] = x;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
} else {
|
||||
return { [name]: xs };
|
||||
}
|
||||
};
|
||||
} else {
|
||||
withVal = (x) => ({ [name]: x });
|
||||
}
|
||||
|
||||
const func = (...pats) => sequence(...pats).withValue(withVal);
|
||||
|
||||
const setter = function (...pats) {
|
||||
if (!pats.length) {
|
||||
return this.fmap((value) => ({ [name]: value }));
|
||||
return this.fmap(withVal);
|
||||
}
|
||||
return this.set(func(...pats));
|
||||
};
|
||||
|
||||
generic_params.forEach(([type, name, description]) => {
|
||||
controls[name] = (...pats) => _name(name, ...pats);
|
||||
Pattern.prototype[name] = _setter(controls[name], name);
|
||||
});
|
||||
|
||||
// create custom param
|
||||
controls.createParam = (name) => {
|
||||
const func = (...pats) => _name(name, ...pats);
|
||||
Pattern.prototype[name] = _setter(func, name);
|
||||
return (...pats) => _name(name, ...pats);
|
||||
Pattern.prototype[name] = setter;
|
||||
return func;
|
||||
};
|
||||
|
||||
generic_params.forEach(([names, ...aliases]) => {
|
||||
const name = Array.isArray(names) ? names[0] : names;
|
||||
controls[name] = controls.createParam(names);
|
||||
|
||||
aliases.forEach((alias) => {
|
||||
controls[alias] = controls[name];
|
||||
Pattern.prototype[alias] = Pattern.prototype[name];
|
||||
});
|
||||
});
|
||||
|
||||
controls.createParams = (...names) =>
|
||||
names.reduce((acc, name) => Object.assign(acc, { [name]: controls.createParam(name) }), {});
|
||||
|
||||
|
||||
+22
-17
@@ -10,44 +10,49 @@ import { logger } from './logger.mjs';
|
||||
export class Cyclist {
|
||||
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) {
|
||||
this.started = false;
|
||||
this.cps = 1; // TODO
|
||||
this.phase = 0;
|
||||
this.getTime = getTime;
|
||||
this.cps = 1;
|
||||
this.lastTick = 0; // absolute time when last tick (clock callback) happened
|
||||
this.lastBegin = 0; // query begin of last tick
|
||||
this.lastEnd = 0; // query end of last tick
|
||||
this.getTime = getTime; // get absolute time
|
||||
this.onToggle = onToggle;
|
||||
this.latency = latency;
|
||||
this.latency = latency; // fixed trigger time offset
|
||||
const round = (x) => Math.round(x * 1000) / 1000;
|
||||
this.clock = createClock(
|
||||
getTime,
|
||||
// called slightly before each cycle
|
||||
(phase, duration, tick) => {
|
||||
if (tick === 0) {
|
||||
this.origin = phase;
|
||||
}
|
||||
const begin = round(phase - this.origin);
|
||||
this.phase = begin - latency;
|
||||
const end = round(begin + duration);
|
||||
const time = getTime();
|
||||
try {
|
||||
const haps = this.pattern.queryArc(begin, end); // get Haps
|
||||
const time = getTime();
|
||||
const begin = this.lastEnd;
|
||||
this.lastBegin = begin;
|
||||
const end = round(begin + duration * this.cps);
|
||||
this.lastEnd = end;
|
||||
const haps = this.pattern.queryArc(begin, end);
|
||||
const tickdeadline = phase - time; // time left till phase begins
|
||||
this.lastTick = time + tickdeadline;
|
||||
|
||||
haps.forEach((hap) => {
|
||||
if (hap.part.begin.equals(hap.whole.begin)) {
|
||||
const deadline = hap.whole.begin + this.origin - time + latency;
|
||||
const duration = hap.duration * 1;
|
||||
onTrigger?.(hap, deadline, duration);
|
||||
const deadline = (hap.whole.begin - begin) / this.cps + tickdeadline + latency;
|
||||
const duration = hap.duration / this.cps;
|
||||
onTrigger?.(hap, deadline, duration, this.cps);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
logger(`[cyclist] error: ${e.message}`);
|
||||
onError?.(e);
|
||||
}
|
||||
}, // called slightly before each cycle
|
||||
},
|
||||
interval, // duration of each cycle
|
||||
);
|
||||
}
|
||||
getPhase() {
|
||||
return this.getTime() - this.origin - this.latency;
|
||||
}
|
||||
now() {
|
||||
return this.getTime() - this.origin + this.clock.minLatency;
|
||||
const secondsSinceLastTick = this.getTime() - this.lastTick - this.clock.duration;
|
||||
return this.lastBegin + secondsSinceLastTick * this.cps; // + this.clock.minLatency;
|
||||
}
|
||||
setStarted(v) {
|
||||
this.started = v;
|
||||
|
||||
@@ -11,15 +11,9 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
*/
|
||||
|
||||
import { Pattern, timeCat, register, silence } from './pattern.mjs';
|
||||
import { rotate, flatten } from './util.mjs';
|
||||
import { rotate, flatten, splitAt, zipWith } from './util.mjs';
|
||||
import Fraction from './fraction.mjs';
|
||||
|
||||
const splitAt = function (index, value) {
|
||||
return [value.slice(0, index), value.slice(index)];
|
||||
};
|
||||
|
||||
const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i]));
|
||||
|
||||
const left = function (n, x) {
|
||||
const [ons, offs] = n;
|
||||
const [xs, ys] = x;
|
||||
|
||||
@@ -6,12 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
import { isPattern } from './index.mjs';
|
||||
|
||||
let scoped = false;
|
||||
export const evalScope = async (...args) => {
|
||||
if (scoped) {
|
||||
console.warn('evalScope was called more than once.');
|
||||
}
|
||||
scoped = true;
|
||||
const results = await Promise.allSettled(args);
|
||||
const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value);
|
||||
results.forEach((result, i) => {
|
||||
@@ -42,9 +37,6 @@ function safeEval(str, options = {}) {
|
||||
}
|
||||
|
||||
export const evaluate = async (code, transpiler) => {
|
||||
if (!scoped) {
|
||||
await evalScope(); // at least scope Pattern.prototype.boostrap
|
||||
}
|
||||
if (transpiler) {
|
||||
code = transpiler(code); // transform syntactically correct js code to semantically usable code
|
||||
}
|
||||
|
||||
@@ -126,6 +126,19 @@ export class Hap {
|
||||
setContext(context) {
|
||||
return new Hap(this.whole, this.part, this.value, context);
|
||||
}
|
||||
|
||||
ensureObjectValue() {
|
||||
/* if (isNote(hap.value)) {
|
||||
// supports primitive hap values that look like notes
|
||||
hap.value = { note: hap.value };
|
||||
} */
|
||||
if (typeof this.value !== 'object') {
|
||||
throw new Error(
|
||||
`expected hap.value to be an object, but got "${this.value}". Hint: append .note() or .s() to the end`,
|
||||
'error',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Hap;
|
||||
|
||||
+113
-38
@@ -820,22 +820,6 @@ export class Pattern {
|
||||
);
|
||||
}
|
||||
|
||||
addTrigger(onTrigger) {
|
||||
return this.withHap((hap) =>
|
||||
hap.setContext({
|
||||
...hap.context,
|
||||
onTrigger: (...args) => {
|
||||
if (hap.context.onTrigger) {
|
||||
hap.context.onTrigger(...args);
|
||||
}
|
||||
onTrigger(...args);
|
||||
},
|
||||
// this flag causes the default output to be ignored
|
||||
dominantTrigger: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
log(func = (_, hap) => `[hap] ${hap.showWhole(true)}`, getData = (_, hap) => ({ hap })) {
|
||||
return this.onTrigger((...args) => {
|
||||
logger(func(...args), undefined, getData(...args));
|
||||
@@ -856,7 +840,7 @@ export class Pattern {
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// functions relating to chords/patterns of lists
|
||||
// functions relating to chords/patterns of lists/lists of patterns
|
||||
|
||||
// returns Array<Hap[]> where each list of haps satisfies eq
|
||||
function groupHapsBy(eq, haps) {
|
||||
@@ -905,6 +889,35 @@ Pattern.prototype.arp = function (pat) {
|
||||
return this.arpWith((haps) => pat.fmap((i) => haps[i % haps.length]));
|
||||
};
|
||||
|
||||
/*
|
||||
* Takes a time duration followed by one or more patterns, and shifts the given patterns in time, so they are
|
||||
* distributed equally over the given time duration. They are then combined with the pattern 'weave' is called on, after it has been stretched out (i.e. slowed down by) the time duration.
|
||||
* @name weave
|
||||
* @memberof Pattern
|
||||
* @example pan(saw).weave(4, s("bd(3,8)"), s("~ sd"))
|
||||
* @example n("0 1 2 3 4 5 6 7").weave(8, s("bd(3,8)"), s("~ sd"))
|
||||
|
||||
addToPrototype('weave', function (t, ...pats) {
|
||||
return this.weaveWith(t, ...pats.map((x) => set.out(x)));
|
||||
});
|
||||
|
||||
*/
|
||||
/*
|
||||
* Like 'weave', but accepts functions rather than patterns, which are applied to the pattern.
|
||||
* @name weaveWith
|
||||
* @memberof Pattern
|
||||
|
||||
addToPrototype('weaveWith', function (t, ...funcs) {
|
||||
const pat = this;
|
||||
const l = funcs.length;
|
||||
t = Fraction(t);
|
||||
if (l == 0) {
|
||||
return silence;
|
||||
}
|
||||
return stack(...funcs.map((func, i) => pat.inside(t, func).early(Fraction(i).div(l))))._slow(t);
|
||||
});
|
||||
*/
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// compose matrix functions
|
||||
|
||||
@@ -1391,39 +1404,45 @@ export const func = curry((a, b) => reify(b).func(a));
|
||||
* @noAutocomplete
|
||||
*
|
||||
*/
|
||||
export function register(name, func) {
|
||||
export function register(name, func, patternify = true) {
|
||||
if (Array.isArray(name)) {
|
||||
const result = {};
|
||||
for (const name_item of name) {
|
||||
result[name_item] = register(name_item, func);
|
||||
result[name_item] = register(name_item, func, patternify);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
const arity = func.length;
|
||||
var pfunc; // the patternified function
|
||||
|
||||
pfunc = function (...args) {
|
||||
args = args.map(reify);
|
||||
const pat = args[args.length - 1];
|
||||
if (arity === 1) {
|
||||
return func(pat);
|
||||
}
|
||||
const [left, ...right] = args.slice(0, -1);
|
||||
let mapFn = (...args) => {
|
||||
// make sure to call func with the correct argument count
|
||||
// args.length is expected to be <= arity-1
|
||||
// so we set undefined args explicitly undefined
|
||||
Array(arity - 1)
|
||||
.fill()
|
||||
.map((_, i) => args[i] ?? undefined);
|
||||
return func(...args, pat);
|
||||
if (patternify) {
|
||||
pfunc = function (...args) {
|
||||
args = args.map(reify);
|
||||
const pat = args[args.length - 1];
|
||||
if (arity === 1) {
|
||||
return func(pat);
|
||||
}
|
||||
const [left, ...right] = args.slice(0, -1);
|
||||
let mapFn = (...args) => {
|
||||
// make sure to call func with the correct argument count
|
||||
// args.length is expected to be <= arity-1
|
||||
// so we set undefined args explicitly undefined
|
||||
Array(arity - 1)
|
||||
.fill()
|
||||
.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 = curry(mapFn, null, arity - 1);
|
||||
return right.reduce((acc, p) => acc.appLeft(p), left.fmap(mapFn)).innerJoin();
|
||||
};
|
||||
} else {
|
||||
pfunc = function (...args) {
|
||||
args = args.map(reify);
|
||||
return func(...args);
|
||||
};
|
||||
}
|
||||
|
||||
Pattern.prototype[name] = function (...args) {
|
||||
args = args.map(reify);
|
||||
// For methods that take a single argument (plus 'this'), allow
|
||||
// multiple arguments but sequence them
|
||||
if (arity === 2 && args.length !== 1) {
|
||||
@@ -1431,6 +1450,7 @@ export function register(name, func) {
|
||||
} else if (arity !== args.length + 1) {
|
||||
throw new Error(`.${name}() expects ${arity - 1} inputs but got ${args.length}.`);
|
||||
}
|
||||
args = args.map(reify);
|
||||
return pfunc(...args, this);
|
||||
};
|
||||
|
||||
@@ -2210,6 +2230,61 @@ const _loopAt = function (factor, pat, cps = 1) {
|
||||
.slow(factor);
|
||||
};
|
||||
|
||||
/*
|
||||
* Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers.
|
||||
* @name slice
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
* s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(1.5)
|
||||
*/
|
||||
|
||||
const slice = register(
|
||||
'slice',
|
||||
function (npat, ipat, opat) {
|
||||
return npat.innerBind((n) =>
|
||||
ipat.outerBind((i) =>
|
||||
opat.outerBind((o) => {
|
||||
// If it's not an object, assume it's a string and make it a 's' control parameter
|
||||
o = o instanceof Object ? o : { s: o };
|
||||
// Remember we must stay pure and avoid editing the object directly
|
||||
const toAdd = { begin: i / n, end: (i + 1) / n, _slices: n };
|
||||
return pure({ ...toAdd, ...o });
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
false, // turns off auto-patternification
|
||||
);
|
||||
|
||||
/*
|
||||
* Works the same as slice, but changes the playback speed of each slice to match the duration of its step.
|
||||
* @name splice
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* await samples('github:tidalcycles/Dirt-Samples/master')
|
||||
* s("breaks165").splice(8, "0 1 [2 3 0]@2 3 0@2 7").hurry(0.65)
|
||||
*/
|
||||
|
||||
const splice = register(
|
||||
'splice',
|
||||
function (npat, ipat, opat) {
|
||||
const sliced = slice(npat, ipat, opat);
|
||||
return sliced.withHap(function (hap) {
|
||||
return hap.withValue((v) => ({
|
||||
...{
|
||||
speed: (1 / v._slices / hap.whole.duration) * (v.speed || 1),
|
||||
unit: 'c',
|
||||
},
|
||||
...v,
|
||||
}));
|
||||
});
|
||||
},
|
||||
false, // turns off auto-patternification
|
||||
);
|
||||
|
||||
const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) {
|
||||
return _loopAt(factor, pat, 1);
|
||||
});
|
||||
|
||||
+20
-4
@@ -2,6 +2,7 @@ import { Cyclist } from './cyclist.mjs';
|
||||
import { evaluate as _evaluate } from './evaluate.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
import { setTime } from './time.mjs';
|
||||
import { evalScope } from './evaluate.mjs';
|
||||
|
||||
export function repl({
|
||||
interval,
|
||||
@@ -17,13 +18,12 @@ export function repl({
|
||||
}) {
|
||||
const scheduler = new Cyclist({
|
||||
interval,
|
||||
onTrigger: async (hap, deadline, duration) => {
|
||||
onTrigger: async (hap, deadline, duration, cps) => {
|
||||
try {
|
||||
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
|
||||
await defaultOutput(hap, deadline, duration);
|
||||
await defaultOutput(hap, deadline, duration, cps);
|
||||
}
|
||||
if (hap.context.onTrigger) {
|
||||
const cps = 1;
|
||||
// call signature of output / onTrigger is different...
|
||||
await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps);
|
||||
}
|
||||
@@ -42,6 +42,17 @@ export function repl({
|
||||
}
|
||||
try {
|
||||
beforeEval?.({ code });
|
||||
scheduler.setCps(1); // reset cps in case the code does not contain a setCps call
|
||||
// problem: when the code does contain a setCps after an awaited promise,
|
||||
// the cps will be 1 until the promise resolves
|
||||
// example:
|
||||
/*
|
||||
await new Promise(resolve => setTimeout(resolve,1000))
|
||||
setCps(.5)
|
||||
note("c a f e")
|
||||
*/
|
||||
// to make sure the setCps inside the code is called immediately,
|
||||
// it has to be placed first
|
||||
let { pattern } = await _evaluate(code, transpiler);
|
||||
|
||||
logger(`[eval] code updated`);
|
||||
@@ -58,5 +69,10 @@ export function repl({
|
||||
const stop = () => scheduler.stop();
|
||||
const start = () => scheduler.start();
|
||||
const pause = () => scheduler.pause();
|
||||
return { scheduler, evaluate, start, stop, pause };
|
||||
const setCps = (cps) => scheduler.setCps(cps);
|
||||
evalScope({
|
||||
setCps,
|
||||
setcps: setCps,
|
||||
});
|
||||
return { scheduler, evaluate, start, stop, pause, setCps };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
controls.test.mjs - <short description TODO>
|
||||
Copyright (C) 2023 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/controls.test.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import controls from '../controls.mjs';
|
||||
import { mini } from '../../mini/mini.mjs';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('controls', () => {
|
||||
it('should support controls', () => {
|
||||
expect(controls.s('bd').firstCycleValues).toEqual([{ s: 'bd' }]);
|
||||
});
|
||||
it('should support compound controls', () => {
|
||||
expect(controls.s(mini('bd:3')).firstCycleValues).toEqual([{ s: 'bd', n: 3 }]);
|
||||
expect(controls.s(mini('bd:3 sd:4:1.4')).firstCycleValues).toEqual([
|
||||
{ s: 'bd', n: 3 },
|
||||
{ s: 'sd', n: 4, gain: 1.4 },
|
||||
]);
|
||||
});
|
||||
it('should support ignore extra elements in compound controls', () => {
|
||||
expect(controls.s(mini('bd:3:0.4 sd:4:0.5:3:17')).firstCycleValues).toEqual([
|
||||
{ s: 'bd', n: 3, gain: 0.4 },
|
||||
{ s: 'sd', n: 4, gain: 0.5 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -959,4 +959,48 @@ 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));
|
||||
});
|
||||
});
|
||||
describe('weave', () => {
|
||||
it('Can distribute patterns along a pattern', () => {
|
||||
sameFirst(n(0, 1).weave(2, s('bd', silence), s(silence, 'sd')), sequence(s('bd').n(0), s('sd').n(1)));
|
||||
});
|
||||
});
|
||||
*/
|
||||
describe('slice', () => {
|
||||
it('Can slice a sample', () => {
|
||||
sameFirst(
|
||||
s('break').slice(4, sequence(0, 1, 2, 3)),
|
||||
sequence(
|
||||
{ begin: 0, end: 0.25, s: 'break', _slices: 4 },
|
||||
{ begin: 0.25, end: 0.5, s: 'break', _slices: 4 },
|
||||
{ begin: 0.5, end: 0.75, s: 'break', _slices: 4 },
|
||||
{ begin: 0.75, end: 1, s: 'break', _slices: 4 },
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('splice', () => {
|
||||
it('Can splice a sample', () => {
|
||||
sameFirst(
|
||||
s('break').splice(4, sequence(0, 1, 2, 3)),
|
||||
sequence(
|
||||
{ begin: 0, end: 0.25, s: 'break', _slices: 4, unit: 'c', speed: 1 },
|
||||
{ begin: 0.25, end: 0.5, s: 'break', _slices: 4, unit: 'c', speed: 1 },
|
||||
{ begin: 0.5, end: 0.75, s: 'break', _slices: 4, unit: 'c', speed: 1 },
|
||||
{ begin: 0.75, end: 1, s: 'break', _slices: 4, unit: 'c', speed: 1 },
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -206,3 +206,9 @@ export function parseFractional(numOrString) {
|
||||
}
|
||||
|
||||
export const fractionalArgs = (fn) => mapArgs(fn, parseFractional);
|
||||
|
||||
export const splitAt = function (index, value) {
|
||||
return [value.slice(0, index), value.slice(index)];
|
||||
};
|
||||
|
||||
export const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i]));
|
||||
|
||||
@@ -44,6 +44,6 @@ function createClock(
|
||||
};
|
||||
const getPhase = () => phase;
|
||||
// setCallback
|
||||
return { setDuration, start, stop, pause, duration, getPhase, minLatency };
|
||||
return { setDuration, start, stop, pause, duration, interval, getPhase, minLatency };
|
||||
}
|
||||
export default createClock;
|
||||
|
||||
@@ -28,9 +28,7 @@ export const csound = register('csound', (instrument, pat) => {
|
||||
logger('[csound] not loaded yet', 'warning');
|
||||
return;
|
||||
}
|
||||
if (typeof hap.value !== 'object') {
|
||||
throw new Error('csound only support objects as hap values');
|
||||
}
|
||||
hap.ensureObjectValue();
|
||||
let { gain = 0.8 } = hap.value;
|
||||
gain *= 0.2;
|
||||
|
||||
|
||||
+30
-15
@@ -5,8 +5,9 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
*/
|
||||
|
||||
import * as _WebMidi from 'webmidi';
|
||||
import { Pattern, isPattern, isNote, getPlayableNoteValue, logger } from '@strudel.cycles/core';
|
||||
import { Pattern, isPattern, logger } from '@strudel.cycles/core';
|
||||
import { getAudioContext } from '@strudel.cycles/webaudio';
|
||||
import { toMidi } from '@strudel.cycles/core';
|
||||
|
||||
// if you use WebMidi from outside of this package, make sure to import that instance:
|
||||
export const { WebMidi } = _WebMidi;
|
||||
@@ -63,7 +64,7 @@ function getDevice(output, outputs) {
|
||||
}
|
||||
|
||||
// Pattern.prototype.midi = function (output: string | number, channel = 1) {
|
||||
Pattern.prototype.midi = function (output, channel = 1) {
|
||||
Pattern.prototype.midi = function (output) {
|
||||
if (!supportsMidi()) {
|
||||
throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`);
|
||||
}
|
||||
@@ -90,11 +91,6 @@ Pattern.prototype.midi = function (output, channel = 1) {
|
||||
);
|
||||
}
|
||||
return this.onTrigger((time, hap) => {
|
||||
let note = getPlayableNoteValue(hap);
|
||||
const velocity = hap.context?.velocity ?? 0.9;
|
||||
if (!isNote(note)) {
|
||||
throw new Error('not a note: ' + note);
|
||||
}
|
||||
if (!midiReady) {
|
||||
return;
|
||||
}
|
||||
@@ -106,15 +102,34 @@ Pattern.prototype.midi = function (output, channel = 1) {
|
||||
.join(' | ')}`,
|
||||
);
|
||||
}
|
||||
// console.log('midi', value, output);
|
||||
hap.ensureObjectValue();
|
||||
|
||||
// calculate time
|
||||
const timingOffset = WebMidi.time - getAudioContext().currentTime * 1000;
|
||||
time = time * 1000 + timingOffset;
|
||||
// const inMs = '+' + (time - Tone.getContext().currentTime) * 1000;
|
||||
// await enableWebMidi()
|
||||
device.playNote(note, channel, {
|
||||
time,
|
||||
duration: hap.duration.valueOf() * 1000 - 5,
|
||||
attack: velocity,
|
||||
});
|
||||
|
||||
// destructure value
|
||||
const { note, nrpnn, nrpv, ccn, ccv, midichan = 1 } = hap.value;
|
||||
const velocity = hap.context?.velocity ?? 0.9; // TODO: refactor velocity
|
||||
const duration = hap.duration.valueOf() * 1000 - 5;
|
||||
|
||||
if (note) {
|
||||
const midiNumber = toMidi(note);
|
||||
device.playNote(midiNumber, midichan, {
|
||||
time,
|
||||
duration,
|
||||
attack: velocity,
|
||||
});
|
||||
}
|
||||
if (ccv && ccn) {
|
||||
if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) {
|
||||
throw new Error('expected ccv to be a number between 0 and 1');
|
||||
}
|
||||
if (!['string', 'number'].includes(typeof ccn)) {
|
||||
throw new Error('expected ccn to be a number or a string');
|
||||
}
|
||||
const scaled = Math.round(ccv * 127);
|
||||
device.sendControlChange(ccn, scaled, midichan, { time });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
+383
-283
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,13 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
this.location_ = location();
|
||||
}
|
||||
|
||||
var MemoryStub = function(source)
|
||||
{
|
||||
this.type_ = "memory";
|
||||
this.source_ = source;
|
||||
this.location_ = location();
|
||||
}
|
||||
|
||||
var PatternStub = function(source, alignment)
|
||||
{
|
||||
this.type_ = "pattern";
|
||||
@@ -96,9 +103,11 @@ quote = '"' / "'"
|
||||
// ------------------ steps and cycles ---------------------------
|
||||
|
||||
// single step definition (e.g bd)
|
||||
step_char = [0-9a-zA-Z~] / "-" / "#" / "." / "^" / "_" / ":"
|
||||
step_char = [0-9a-zA-Z~] / "-" / "#" / "." / "_" / "^"
|
||||
step = ws chars:step_char+ ws { return new AtomStub(chars.join("")) }
|
||||
|
||||
memory = ws "^" h:[A-Za-z] t:[A-Za-z0-9_]+ { return new MemoryStub(h + t.join("")) }
|
||||
|
||||
// define a sub cycle e.g. [1 2, 3 [4]]
|
||||
sub_cycle = ws "[" ws s:stack_or_choose ws "]" ws { return s }
|
||||
|
||||
@@ -115,11 +124,11 @@ slow_sequence = ws "<" ws s:sequence ws ">" ws
|
||||
{ s.arguments_.alignment = 'slowcat'; return s; }
|
||||
|
||||
// a slice is either a single step or a sub cycle
|
||||
slice = step / sub_cycle / polymeter / slow_sequence
|
||||
slice = memory / step / sub_cycle / polymeter / slow_sequence
|
||||
|
||||
// slice modifier affects the timing/size of a slice (e.g. [a b c]@3)
|
||||
// at this point, we assume we can represent them as regular sequence operators
|
||||
slice_op = op_weight / op_bjorklund / op_slow / op_fast / op_replicate / op_degrade
|
||||
slice_op = op_weight / op_bjorklund / op_slow / op_fast / op_replicate / op_degrade / op_tail
|
||||
|
||||
op_weight = "@" a:number
|
||||
{ return x => x.options_['weight'] = a }
|
||||
@@ -139,6 +148,9 @@ op_fast = "*"a:slice
|
||||
op_degrade = "?"a:number?
|
||||
{ return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a } }) }
|
||||
|
||||
op_tail = ":" s:slice
|
||||
{ return x => x.options_['ops'].push({ type_: "tail", arguments_ :{ element:s } }) }
|
||||
|
||||
// a slice with an modifier applied i.e [bd@4 sd@3]@2 hh]
|
||||
slice_with_ops = s:slice ops:slice_op*
|
||||
{ const result = new ElementStub(s, {ops: [], weight: 1, reps: 1});
|
||||
|
||||
@@ -14,10 +14,22 @@ function _nextSeed() {
|
||||
return _seedState++;
|
||||
} */
|
||||
|
||||
const memory = {};
|
||||
|
||||
export const remember = (name, pat) => {
|
||||
if (pat) {
|
||||
memory[name] = pat;
|
||||
} else {
|
||||
pat = memory[name];
|
||||
}
|
||||
return pat;
|
||||
};
|
||||
|
||||
const applyOptions = (parent, code) => (pat, i) => {
|
||||
const ast = parent.source_[i];
|
||||
const options = ast.options_;
|
||||
const ops = options?.ops;
|
||||
|
||||
if (ops) {
|
||||
for (const op of ops) {
|
||||
switch (op.type_) {
|
||||
@@ -66,6 +78,11 @@ const applyOptions = (parent, code) => (pat, i) => {
|
||||
pat = strudel.reify(pat).degradeBy(op.arguments_.amount === null ? 0.5 : op.arguments_.amount);
|
||||
break;
|
||||
}
|
||||
case 'tail': {
|
||||
const friend = patternifyAST(op.arguments_.element, code);
|
||||
pat = pat.fmap((a) => (b) => Array.isArray(a) ? [...a, b] : [a, b]).appLeft(friend);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.warn(`operator "${op.type_}" not implemented`);
|
||||
}
|
||||
@@ -132,6 +149,16 @@ export function patternifyAST(ast, code) {
|
||||
case 'element': {
|
||||
return patternifyAST(ast.source_, code);
|
||||
}
|
||||
case 'memory': {
|
||||
const name = ast.source_;
|
||||
if (name in memory) {
|
||||
return new strudel.Pattern((state) => {
|
||||
return memory[name].query(state);
|
||||
});
|
||||
}
|
||||
// TODO - how to default a memory.. via a list?
|
||||
return strudel.pure(undefined);
|
||||
}
|
||||
case 'atom': {
|
||||
if (ast.source_ === '~') {
|
||||
return strudel.silence;
|
||||
|
||||
@@ -140,6 +140,9 @@ describe('mini', () => {
|
||||
expect(haps.length < 230).toBe(true);
|
||||
// 'Had too many cycles remaining after degradeBy 0.8');
|
||||
});
|
||||
it('supports lists', () => {
|
||||
expect(minV('a:b c:d:[e:f] g')).toEqual([['a', 'b'], ['c', 'd', ['e', 'f']], 'g']);
|
||||
});
|
||||
/*it('supports the random choice operator ("|") with nesting', () => {
|
||||
const numCycles = 900;
|
||||
const haps = mini('a | [b | c] | [d | e | f]').queryArc(0, numCycles);
|
||||
|
||||
@@ -45,9 +45,10 @@ let startedAt = -1;
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
*/
|
||||
Pattern.prototype.osc = async function () {
|
||||
const osc = await connect();
|
||||
return this.onTrigger((time, hap, currentTime, cps = 1) => {
|
||||
Pattern.prototype.osc = function () {
|
||||
return this.onTrigger(async (time, hap, currentTime, cps = 1) => {
|
||||
hap.ensureObjectValue();
|
||||
const osc = await connect();
|
||||
const cycle = hap.wholeOrPart().begin.valueOf();
|
||||
const delta = hap.duration.valueOf();
|
||||
// time should be audio time of onset
|
||||
|
||||
+33
-14
@@ -13,24 +13,43 @@ npm i @strudel.cycles/react
|
||||
Here is a minimal example of how to set up a MiniRepl:
|
||||
|
||||
```jsx
|
||||
import { evalScope, controls } from '@strudel.cycles/core';
|
||||
import * as React from 'react';
|
||||
import '@strudel.cycles/react/dist/style.css';
|
||||
import { MiniRepl } from '@strudel.cycles/react';
|
||||
import { prebake } from '../repl/src/prebake.mjs';
|
||||
import { evalScope, controls } from '@strudel.cycles/core';
|
||||
import { samples, initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
||||
|
||||
evalScope(
|
||||
controls,
|
||||
import('@strudel.cycles/core'),
|
||||
import('@strudel.cycles/tonal'),
|
||||
import('@strudel.cycles/mini'),
|
||||
import('@strudel.cycles/webaudio'),
|
||||
/* probably import other strudel packages */
|
||||
);
|
||||
async function prebake() {
|
||||
await samples(
|
||||
'https://strudel.tidalcycles.org/tidal-drum-machines.json',
|
||||
'github:ritchse/tidal-drum-machines/main/machines/'
|
||||
);
|
||||
await samples(
|
||||
'https://strudel.tidalcycles.org/EmuSP12.json',
|
||||
'https://strudel.tidalcycles.org/EmuSP12/'
|
||||
);
|
||||
}
|
||||
|
||||
prebake();
|
||||
async function init() {
|
||||
await evalScope(
|
||||
controls,
|
||||
import('@strudel.cycles/core'),
|
||||
import('@strudel.cycles/mini'),
|
||||
import('@strudel.cycles/webaudio'),
|
||||
import('@strudel.cycles/tonal')
|
||||
);
|
||||
await prebake();
|
||||
initAudioOnFirstClick();
|
||||
}
|
||||
|
||||
export function Repl({ tune }) {
|
||||
return <MiniRepl tune={tune} hideOutsideView={true} />;
|
||||
if (typeof window !== 'undefined') {
|
||||
init();
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return <MiniRepl tune={`s("bd sd,hh*4")`} />;
|
||||
}
|
||||
```
|
||||
|
||||
For a more sophisticated example, check out the [nano-repl](./examples/nano-repl/)!
|
||||
- Open [example on stackblitz](https://stackblitz.com/edit/react-ts-saaair?file=tune.tsx,App.tsx)
|
||||
- Also check out the [nano-repl](./examples/nano-repl/) for a more sophisticated example
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# nano-repl
|
||||
|
||||
this is an example of how to create a repl with strudel and react.
|
||||
|
||||
## Usage
|
||||
|
||||
after cloning the strudel repo:
|
||||
|
||||
```sh
|
||||
pnpm i
|
||||
cd packages/react/examples/nano-repl
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
you should now have a repl running at `http://localhost:5173/`
|
||||
@@ -66,7 +66,7 @@ function App() {
|
||||
const [code, setCode] = useState(defaultTune);
|
||||
const [view, setView] = useState();
|
||||
// const [code, setCode] = useState(`"c3".note().slow(2)`);
|
||||
const { scheduler, evaluate, schedulerError, evalError, isDirty, activeCode, pattern } = useStrudel({
|
||||
const { scheduler, evaluate, schedulerError, evalError, isDirty, activeCode, pattern, started } = useStrudel({
|
||||
code,
|
||||
defaultOutput: webaudioOutput,
|
||||
getTime,
|
||||
@@ -75,8 +75,8 @@ function App() {
|
||||
useHighlighting({
|
||||
view,
|
||||
pattern,
|
||||
active: !activeCode?.includes('strudel disable-highlighting'),
|
||||
getTime: () => scheduler.getPhase(),
|
||||
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
||||
getTime: () => scheduler.now(),
|
||||
});
|
||||
|
||||
const error = evalError || schedulerError;
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--background: #222;
|
||||
--lineBackground: #22222250;
|
||||
--foreground: #fff;
|
||||
--caret: #ffcc00;
|
||||
--selection: rgba(128, 203, 196, 0.5);
|
||||
--selectionMatch: #036dd626;
|
||||
--lineHighlight: #00000050;
|
||||
--gutterBackground: transparent;
|
||||
--gutterForeground: #8a919966;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #123;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,23 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
module.exports = {
|
||||
// TODO: find out if leaving out tutorial path works now
|
||||
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
||||
content: ['./src/**/*.{js,jsx,ts,tsx}', '../../src/**/*.{html,js,jsx,md,mdx,ts,tsx}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
colors: {
|
||||
// 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)',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/react",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.4",
|
||||
"description": "React components for strudel",
|
||||
"main": "src/index.js",
|
||||
"publishConfig": {
|
||||
@@ -37,6 +37,8 @@
|
||||
"@codemirror/state": "^6.2.0",
|
||||
"@codemirror/view": "^6.7.3",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
"@replit/codemirror-emacs": "^6.0.0",
|
||||
"@replit/codemirror-vim": "^6.0.6",
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@strudel.cycles/transpiler": "workspace:*",
|
||||
"@strudel.cycles/webaudio": "workspace:*",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import _CodeMirror from '@uiw/react-codemirror';
|
||||
import { EditorView, Decoration } from '@codemirror/view';
|
||||
import { StateField, StateEffect } from '@codemirror/state';
|
||||
@@ -7,7 +7,9 @@ import strudelTheme from '../themes/strudel-theme';
|
||||
import './style.css';
|
||||
import { useCallback } from 'react';
|
||||
import { autocompletion } from '@codemirror/autocomplete';
|
||||
import { strudelAutocomplete } from './Autocomplete';
|
||||
//import { strudelAutocomplete } from './Autocomplete';
|
||||
import { vim } from '@replit/codemirror-vim';
|
||||
import { emacs } from '@replit/codemirror-emacs';
|
||||
|
||||
export const setFlash = StateEffect.define();
|
||||
const flashField = StateField.define({
|
||||
@@ -56,15 +58,15 @@ const highlightField = StateField.define({
|
||||
haps
|
||||
.map((hap) =>
|
||||
(hap.context.locations || []).map(({ start, end }) => {
|
||||
const color = hap.context.color || e.value.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;
|
||||
if (from > l || to > l) {
|
||||
return; // dont mark outside of range, as it will throw an error
|
||||
}
|
||||
// const mark = Decoration.mark({ attributes: { style: `outline: 1px solid ${color}` } });
|
||||
const mark = Decoration.mark({ attributes: { style: `outline: 1.5px solid ${color};` } });
|
||||
//const mark = Decoration.mark({ attributes: { style: `outline: 2px solid ${color};` } });
|
||||
const mark = Decoration.mark({ attributes: { class: `outline outline-2 outline-foreground` } });
|
||||
return mark.range(from, to);
|
||||
}),
|
||||
)
|
||||
@@ -82,7 +84,7 @@ const highlightField = StateField.define({
|
||||
provide: (f) => EditorView.decorations.from(f),
|
||||
});
|
||||
|
||||
const extensions = [
|
||||
const staticExtensions = [
|
||||
javascript(),
|
||||
highlightField,
|
||||
flashField,
|
||||
@@ -97,6 +99,9 @@ export default function CodeMirror({
|
||||
onViewChanged,
|
||||
onSelectionChange,
|
||||
theme,
|
||||
keybindings,
|
||||
fontSize = 18,
|
||||
fontFamily = 'monospace',
|
||||
options,
|
||||
editorDidMount,
|
||||
}) {
|
||||
@@ -120,8 +125,18 @@ export default function CodeMirror({
|
||||
},
|
||||
[onSelectionChange],
|
||||
);
|
||||
const extensions = useMemo(() => {
|
||||
let bindings = {
|
||||
vim,
|
||||
emacs,
|
||||
};
|
||||
if (bindings[keybindings]) {
|
||||
return [...staticExtensions, bindings[keybindings]()];
|
||||
}
|
||||
return staticExtensions;
|
||||
}, [keybindings]);
|
||||
return (
|
||||
<>
|
||||
<div style={{ fontSize, fontFamily }} className="w-full">
|
||||
<_CodeMirror
|
||||
value={value}
|
||||
theme={theme || strudelTheme}
|
||||
@@ -130,7 +145,7 @@ export default function CodeMirror({
|
||||
onUpdate={handleOnUpdate}
|
||||
extensions={extensions}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import useHighlighting from '../hooks/useHighlighting.mjs';
|
||||
import useStrudel from '../hooks/useStrudel.mjs';
|
||||
import CodeMirror6, { flash } from './CodeMirror6';
|
||||
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';
|
||||
@@ -23,7 +22,6 @@ export function MiniRepl({
|
||||
punchcard,
|
||||
canvasHeight = 200,
|
||||
theme,
|
||||
highlightColor,
|
||||
}) {
|
||||
drawTime = drawTime || (punchcard ? [0, 4] : undefined);
|
||||
const evalOnMount = !!drawTime;
|
||||
@@ -72,7 +70,6 @@ export function MiniRepl({
|
||||
pattern,
|
||||
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
||||
getTime: () => scheduler.now(),
|
||||
color: highlightColor,
|
||||
});
|
||||
|
||||
// keyboard shortcuts
|
||||
@@ -131,19 +128,31 @@ export function MiniRepl({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.container} ref={ref}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.buttons}>
|
||||
<button className={cx(styles.button, started ? 'animate-pulse' : '')} onClick={() => togglePlay()}>
|
||||
<div className="overflow-hidden rounded-t-md bg-background border border-lineHighlight" ref={ref}>
|
||||
<div className="flex justify-between bg-lineHighlight">
|
||||
<div className="flex">
|
||||
<button
|
||||
className={cx(
|
||||
'cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground bg-lineHighlight hover:bg-background',
|
||||
started ? 'animate-pulse' : '',
|
||||
)}
|
||||
onClick={() => togglePlay()}
|
||||
>
|
||||
<Icon type={started ? 'stop' : 'play'} />
|
||||
</button>
|
||||
<button className={cx(isDirty ? styles.button : styles.buttonDisabled)} onClick={() => activateCode()}>
|
||||
<button
|
||||
className={cx(
|
||||
'w-16 flex items-center justify-center p-1 text-foreground border-lineHighlight bg-lineHighlight',
|
||||
isDirty ? 'text-foreground hover:bg-background cursor-pointer' : 'opacity-50 cursor-not-allowed',
|
||||
)}
|
||||
onClick={() => activateCode()}
|
||||
>
|
||||
<Icon type="refresh" />
|
||||
</button>
|
||||
</div>
|
||||
{error && <div className={styles.error}>{error.message}</div>}
|
||||
{error && <div className="text-right p-1 text-sm text-red-200">{error.message}</div>}
|
||||
</div>
|
||||
<div className={styles.body}>
|
||||
<div className="overflow-auto relative">
|
||||
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} theme={theme} />}
|
||||
</div>
|
||||
{drawTime && (
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
.container {
|
||||
@apply overflow-hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply flex justify-between bg-lineHighlight border-t border-l border-r border-lineHighlight rounded-t-md overflow-hidden;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
@apply flex;
|
||||
}
|
||||
|
||||
.button {
|
||||
@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 opacity-50 cursor-not-allowed border-r border-lineHighlight;
|
||||
}
|
||||
|
||||
.error {
|
||||
@apply text-right p-1 text-sm text-red-200;
|
||||
}
|
||||
|
||||
.body {
|
||||
@apply overflow-auto relative;
|
||||
}
|
||||
@@ -1,12 +1,24 @@
|
||||
:root {
|
||||
--background: #222;
|
||||
--lineBackground: #22222250;
|
||||
--foreground: #fff;
|
||||
--caret: #ffcc00;
|
||||
--selection: rgba(128, 203, 196, 0.5);
|
||||
--selectionMatch: #036dd626;
|
||||
--lineHighlight: #00000050;
|
||||
--gutterBackground: transparent;
|
||||
--gutterForeground: #8a919966;
|
||||
}
|
||||
|
||||
.cm-editor {
|
||||
background-color: transparent !important;
|
||||
height: 100%;
|
||||
z-index: 11;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.cm-theme {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.cm-theme-light {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { setHighlights } from '../components/CodeMirror6';
|
||||
const round = (x) => Math.round(x * 1000) / 1000;
|
||||
|
||||
function useHighlighting({ view, pattern, active, getTime, color }) {
|
||||
function useHighlighting({ view, pattern, active, getTime }) {
|
||||
const highlights = useRef([]);
|
||||
const lastEnd = useRef(0);
|
||||
useEffect(() => {
|
||||
@@ -14,12 +15,12 @@ function useHighlighting({ view, pattern, active, getTime, color }) {
|
||||
// force min framerate of 10 fps => fixes crash on tab refocus, where lastEnd could be far away
|
||||
// see https://github.com/tidalcycles/strudel/issues/108
|
||||
const begin = Math.max(lastEnd.current ?? audioTime, audioTime - 1 / 10, -0.01); // negative time seems buggy
|
||||
const span = [begin, audioTime + 1 / 60];
|
||||
const span = [round(begin), round(audioTime + 1 / 60)];
|
||||
lastEnd.current = span[1];
|
||||
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({ haps: highlights.current, color }) }); // highlight all still active + new active haps
|
||||
view.dispatch({ effects: setHighlights.of({ haps: highlights.current }) }); // highlight all still active + new active haps
|
||||
} catch (err) {
|
||||
view.dispatch({ effects: setHighlights.of({ haps: [] }) });
|
||||
}
|
||||
@@ -33,7 +34,7 @@ function useHighlighting({ view, pattern, active, getTime, color }) {
|
||||
view.dispatch({ effects: setHighlights.of({ haps: [] }) });
|
||||
}
|
||||
}
|
||||
}, [pattern, active, view, color]);
|
||||
}, [pattern, active, view]);
|
||||
}
|
||||
|
||||
export default useHighlighting;
|
||||
|
||||
@@ -10,7 +10,6 @@ function useStrudel({
|
||||
getTime,
|
||||
evalOnMount = false,
|
||||
initialCode = '',
|
||||
autolink = false,
|
||||
beforeEval,
|
||||
afterEval,
|
||||
editPattern,
|
||||
@@ -33,7 +32,7 @@ function useStrudel({
|
||||
const shouldPaint = useCallback((pat) => !!(pat?.context?.onPaint && drawContext), [drawContext]);
|
||||
|
||||
// TODO: make sure this hook reruns when scheduler.started changes
|
||||
const { scheduler, evaluate, start, stop, pause } = useMemo(
|
||||
const { scheduler, evaluate, start, stop, pause, setCps } = useMemo(
|
||||
() =>
|
||||
repl({
|
||||
interval,
|
||||
@@ -51,15 +50,13 @@ function useStrudel({
|
||||
setCode(code);
|
||||
beforeEval?.();
|
||||
},
|
||||
afterEval: ({ pattern: _pattern, code }) => {
|
||||
afterEval: (res) => {
|
||||
const { pattern: _pattern, code } = res;
|
||||
setActiveCode(code);
|
||||
setPattern(_pattern);
|
||||
setEvalError();
|
||||
setSchedulerError();
|
||||
if (autolink) {
|
||||
window.location.hash = '#' + encodeURIComponent(btoa(code));
|
||||
}
|
||||
afterEval?.();
|
||||
afterEval?.(res);
|
||||
},
|
||||
onToggle: (v) => {
|
||||
setStarted(v);
|
||||
@@ -156,6 +153,7 @@ function useStrudel({
|
||||
stop,
|
||||
pause,
|
||||
togglePlay,
|
||||
setCps,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export const settings = {
|
||||
background: '#9bbc0f',
|
||||
foreground: '#0f380f', // whats that?
|
||||
caret: '#0f380f',
|
||||
selection: '#306230',
|
||||
selectionMatch: '#ffffff26',
|
||||
lineHighlight: '#8bac0f',
|
||||
lineBackground: '#9bbc0f50',
|
||||
//lineBackground: 'transparent',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#0f380f',
|
||||
light: true,
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#0f380f' },
|
||||
{ tag: t.operator, color: '#0f380f' },
|
||||
{ tag: t.special(t.variableName), color: '#0f380f' },
|
||||
{ tag: t.typeName, color: '#0f380f' },
|
||||
{ tag: t.atom, color: '#0f380f' },
|
||||
{ tag: t.number, color: '#0f380f' },
|
||||
{ tag: t.definition(t.variableName), color: '#0f380f' },
|
||||
{ tag: t.string, color: '#0f380f' },
|
||||
{ tag: t.special(t.string), color: '#0f380f' },
|
||||
{ tag: t.comment, color: '#0f380f' },
|
||||
{ tag: t.variableName, color: '#0f380f' },
|
||||
{ tag: t.tagName, color: '#0f380f' },
|
||||
{ tag: t.bracket, color: '#0f380f' },
|
||||
{ tag: t.meta, color: '#0f380f' },
|
||||
{ tag: t.attributeName, color: '#0f380f' },
|
||||
{ tag: t.propertyName, color: '#0f380f' },
|
||||
{ tag: t.className, color: '#0f380f' },
|
||||
{ tag: t.invalid, color: '#0f380f' },
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export const settings = {
|
||||
background: 'black',
|
||||
foreground: 'white', // whats that?
|
||||
caret: 'white',
|
||||
selection: '#ffffff20',
|
||||
selectionMatch: '#036dd626',
|
||||
lineHighlight: '#ffffff10',
|
||||
lineBackground: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: 'white' },
|
||||
{ tag: t.operator, color: 'white' },
|
||||
{ tag: t.special(t.variableName), color: 'white' },
|
||||
{ tag: t.typeName, color: 'white' },
|
||||
{ tag: t.atom, color: 'white' },
|
||||
{ tag: t.number, color: 'white' },
|
||||
{ tag: t.definition(t.variableName), color: 'white' },
|
||||
{ tag: t.string, color: 'white' },
|
||||
{ tag: t.special(t.string), color: 'white' },
|
||||
{ tag: t.comment, color: 'white' },
|
||||
{ tag: t.variableName, color: 'white' },
|
||||
{ tag: t.tagName, color: 'white' },
|
||||
{ tag: t.bracket, color: 'white' },
|
||||
{ tag: t.meta, color: 'white' },
|
||||
{ tag: t.attributeName, color: 'white' },
|
||||
{ tag: t.propertyName, color: 'white' },
|
||||
{ tag: t.className, color: 'white' },
|
||||
{ tag: t.invalid, color: 'white' },
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export const settings = {
|
||||
background: '#051DB5',
|
||||
lineBackground: '#051DB550',
|
||||
foreground: 'white', // whats that?
|
||||
caret: 'white',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#036dd626',
|
||||
// lineHighlight: '#8a91991a', // original
|
||||
lineHighlight: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
// gutterForeground: '#8a919966',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: 'white' },
|
||||
{ tag: t.operator, color: 'white' },
|
||||
{ tag: t.special(t.variableName), color: 'white' },
|
||||
{ tag: t.typeName, color: 'white' },
|
||||
{ tag: t.atom, color: 'white' },
|
||||
{ tag: t.number, color: 'white' },
|
||||
{ tag: t.definition(t.variableName), color: 'white' },
|
||||
{ tag: t.string, color: 'white' },
|
||||
{ tag: t.special(t.string), color: 'white' },
|
||||
{ tag: t.comment, color: 'white' },
|
||||
{ tag: t.variableName, color: 'white' },
|
||||
{ tag: t.tagName, color: 'white' },
|
||||
{ tag: t.bracket, color: 'white' },
|
||||
{ tag: t.meta, color: 'white' },
|
||||
{ tag: t.attributeName, color: 'white' },
|
||||
{ tag: t.propertyName, color: 'white' },
|
||||
{ tag: t.className, color: 'white' },
|
||||
{ tag: t.invalid, color: 'white' },
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export const settings = {
|
||||
background: 'black',
|
||||
foreground: '#41FF00', // whats that?
|
||||
caret: '#41FF00',
|
||||
selection: '#ffffff20',
|
||||
selectionMatch: '#036dd626',
|
||||
lineHighlight: '#ffffff10',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#41FF00' },
|
||||
{ tag: t.operator, color: '#41FF00' },
|
||||
{ tag: t.special(t.variableName), color: '#41FF00' },
|
||||
{ tag: t.typeName, color: '#41FF00' },
|
||||
{ tag: t.atom, color: '#41FF00' },
|
||||
{ tag: t.number, color: '#41FF00' },
|
||||
{ tag: t.definition(t.variableName), color: '#41FF00' },
|
||||
{ tag: t.string, color: '#41FF00' },
|
||||
{ tag: t.special(t.string), color: '#41FF00' },
|
||||
{ tag: t.comment, color: '#41FF00' },
|
||||
{ tag: t.variableName, color: '#41FF00' },
|
||||
{ tag: t.tagName, color: '#41FF00' },
|
||||
{ tag: t.bracket, color: '#41FF00' },
|
||||
{ tag: t.meta, color: '#41FF00' },
|
||||
{ tag: t.attributeName, color: '#41FF00' },
|
||||
{ tag: t.propertyName, color: '#41FF00' },
|
||||
{ tag: t.className, color: '#41FF00' },
|
||||
{ tag: t.invalid, color: '#41FF00' },
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export const settings = {
|
||||
background: 'white',
|
||||
foreground: 'black', // whats that?
|
||||
caret: 'black',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#ffffff26',
|
||||
lineHighlight: '#cccccc50',
|
||||
lineBackground: '#ffffff50',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: 'black',
|
||||
light: true,
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: 'black' },
|
||||
{ tag: t.operator, color: 'black' },
|
||||
{ tag: t.special(t.variableName), color: 'black' },
|
||||
{ tag: t.typeName, color: 'black' },
|
||||
{ tag: t.atom, color: 'black' },
|
||||
{ tag: t.number, color: 'black' },
|
||||
{ tag: t.definition(t.variableName), color: 'black' },
|
||||
{ tag: t.string, color: 'black' },
|
||||
{ tag: t.special(t.string), color: 'black' },
|
||||
{ tag: t.comment, color: 'black' },
|
||||
{ tag: t.variableName, color: 'black' },
|
||||
{ tag: t.tagName, color: 'black' },
|
||||
{ tag: t.bracket, color: 'black' },
|
||||
{ tag: t.meta, color: 'black' },
|
||||
{ tag: t.attributeName, color: 'black' },
|
||||
{ tag: t.propertyName, color: 'black' },
|
||||
{ tag: t.className, color: 'black' },
|
||||
{ tag: t.invalid, color: 'black' },
|
||||
],
|
||||
});
|
||||
@@ -1,7 +1,21 @@
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
colors: {
|
||||
// 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)',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
corePlugins: {
|
||||
|
||||
@@ -9,6 +9,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
import '../tonal.mjs'; // need to import this to add prototypes
|
||||
import { pure, controls, seq } from '@strudel.cycles/core';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { mini } from '../../mini/mini.mjs';
|
||||
const { n } = controls;
|
||||
|
||||
describe('tonal', () => {
|
||||
@@ -30,4 +31,18 @@ describe('tonal', () => {
|
||||
.firstCycleValues.map((h) => h.note),
|
||||
).toEqual(['C3', 'D3', 'E3']);
|
||||
});
|
||||
it('scale with colon', () => {
|
||||
expect(
|
||||
n(0, 1, 2)
|
||||
.scale('C:major')
|
||||
.firstCycleValues.map((h) => h.note),
|
||||
).toEqual(['C3', 'D3', 'E3']);
|
||||
});
|
||||
it('scale with mininotation colon', () => {
|
||||
expect(
|
||||
n(0, 1, 2)
|
||||
.scale(mini('C:major'))
|
||||
.firstCycleValues.map((h) => h.note),
|
||||
).toEqual(['C3', 'D3', 'E3']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -123,29 +123,37 @@ 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}.
|
||||
*
|
||||
* 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).
|
||||
* A scale consists of a root note (e.g. `c4`, `c`, `f#`, `bb4`) followed by semicolon (':') and then 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()
|
||||
* "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))
|
||||
* .scale("C2:<major minor>")
|
||||
* .note()
|
||||
* @example
|
||||
* "0 1 2 3 4 5 6 7".rev().scale("C2:<major minor>").note()
|
||||
* .s("folkharp")
|
||||
*/
|
||||
|
||||
export const scale = register('scale', function (scale /* : string */, pat) {
|
||||
export const scale = register('scale', function (scale, pat) {
|
||||
// Supports ':' list syntax in mininotation
|
||||
if (Array.isArray(scale)) {
|
||||
scale = scale.join(' ');
|
||||
}
|
||||
return pat.withHap((hap) => {
|
||||
const isObject = typeof hap.value === 'object';
|
||||
let note = isObject ? hap.value.n : hap.value;
|
||||
const asNumber = Number(note);
|
||||
if (!isNaN(asNumber)) {
|
||||
// TODO: worth keeping for supporting ':' in (non-mininotation) strings?
|
||||
scale = scale.replaceAll(':', ' ');
|
||||
let [tonic, scaleName] = Scale.tokenize(scale);
|
||||
const { pc, oct = 3 } = Note.get(tonic);
|
||||
note = scaleOffset(pc + ' ' + scaleName, asNumber, pc + oct);
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
import { register, Pattern, toMidi, valueToMidi } from '@strudel.cycles/core';
|
||||
import { getAudioContext } from '@strudel.cycles/webaudio';
|
||||
import { initializeWamHost } from '@webaudiomodules/sdk';
|
||||
|
||||
// this is a map of all loaded WebAudioModules
|
||||
let wams = {};
|
||||
|
||||
// this is a map of all WebAudioModule instances (possibly more than one per WAM)
|
||||
let wamInstances = {};
|
||||
export const getWamInstances = () => wamInstances;
|
||||
export const getWamInstance = (key) => {
|
||||
if (!key) {
|
||||
throw new Error(`no .wam set`);
|
||||
}
|
||||
let instance = wamInstances[key];
|
||||
if (!instance) {
|
||||
throw new Error(`wam instance "${key}" not found`);
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
|
||||
// holds a promise of the wam host init
|
||||
let hostInit;
|
||||
// maps wam keys to init promises
|
||||
let instanceInit = {};
|
||||
// maps wam keys to a list of params that are automated
|
||||
let automatedWamParams = {};
|
||||
export const getAutomatedWamParams = () => automatedWamParams;
|
||||
|
||||
// host groups of WAMs can interact with one another, but not directly across groups
|
||||
const hostGroupId = 'strudel';
|
||||
|
||||
export const loadWAM = async function (name, url) {
|
||||
// memoized wam host init
|
||||
hostInit = hostInit || initializeWamHost(getAudioContext(), hostGroupId);
|
||||
await hostInit;
|
||||
// memoized wam file import
|
||||
wams[url] = wams[url] || import(/* @vite-ignore */ url);
|
||||
const { default: WAM } = await wams[url];
|
||||
|
||||
const instance = new WAM(hostGroupId, getAudioContext());
|
||||
// memoized instance ini
|
||||
instanceInit[name] =
|
||||
instanceInit[name] ||
|
||||
instance.initialize().then(() => {
|
||||
instance.audioNode.connect(getAudioContext().destination);
|
||||
return instance;
|
||||
});
|
||||
|
||||
// save loaded instance
|
||||
wamInstances[name] = await instanceInit[name];
|
||||
return wamInstances[name];
|
||||
};
|
||||
|
||||
export const loadwam = loadWAM;
|
||||
export const loadWam = loadWAM;
|
||||
|
||||
export const wam = register('wam', function (name, pat) {
|
||||
return pat.set({ wam: name }).addTrigger((time, hap) => {
|
||||
const i = getWamInstance(name);
|
||||
let note = toMidi(hap.value.note);
|
||||
let velocity = hap.context?.velocity ?? 0.75;
|
||||
let endTime = time + hap.duration.valueOf();
|
||||
|
||||
i.audioNode.scheduleEvents({
|
||||
type: 'wam-midi',
|
||||
data: { bytes: [0x90, note, velocity] },
|
||||
time: time,
|
||||
});
|
||||
|
||||
i.audioNode.scheduleEvents({
|
||||
type: 'wam-midi',
|
||||
data: { bytes: [0x80, note, 0] },
|
||||
time: endTime,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export const param = register('param', function (param, value, pat) {
|
||||
return pat.addTrigger((time, hap) => {
|
||||
const { wam } = hap.value;
|
||||
const i = getWamInstance(wam);
|
||||
|
||||
// save param automation
|
||||
if (!automatedWamParams[wam]) {
|
||||
automatedWamParams[wam] = {};
|
||||
}
|
||||
automatedWamParams[wam][param] = true;
|
||||
|
||||
i.audioNode.scheduleEvents({
|
||||
time: time,
|
||||
type: 'wam-automation',
|
||||
data: {
|
||||
id: param,
|
||||
normalized: false,
|
||||
value: value,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"name": "@strudel.cycles/wam",
|
||||
"version": "0.1.0",
|
||||
"description": "WAM2 API for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tidalcycles",
|
||||
"strudel",
|
||||
"pattern",
|
||||
"livecoding",
|
||||
"algorave"
|
||||
],
|
||||
"author": "Tom Burns <tom@burns.ca>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@strudel.cycles/webaudio": "workspace:*",
|
||||
"@webaudiomodules/sdk": "^0.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^3.2.2",
|
||||
"vitest": "^0.25.7"
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import { dependencies } from './package.json';
|
||||
import { resolve } from 'path';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]),
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
},
|
||||
target: 'esnext',
|
||||
},
|
||||
});
|
||||
@@ -97,17 +97,6 @@ const getSoundfontKey = (s) => {
|
||||
return;
|
||||
};
|
||||
|
||||
const splitSN = (s, n) => {
|
||||
if (!s.includes(':')) {
|
||||
return [s, n];
|
||||
}
|
||||
let [s2, n2] = s.split(':');
|
||||
if (isNaN(Number(n2))) {
|
||||
return [s, n];
|
||||
}
|
||||
return [s2, n2];
|
||||
};
|
||||
|
||||
let workletsLoading;
|
||||
function loadWorklets() {
|
||||
if (workletsLoading) {
|
||||
@@ -191,23 +180,11 @@ function effectSend(input, effect, wet) {
|
||||
}
|
||||
|
||||
// export const webaudioOutput = async (t, hap, ct, cps) => {
|
||||
export const webaudioOutput = async (hap, deadline, hapDuration) => {
|
||||
export const webaudioOutput = async (hap, deadline, hapDuration, cps) => {
|
||||
const ac = getAudioContext();
|
||||
/* if (isNote(hap.value)) {
|
||||
// supports primitive hap values that look like notes
|
||||
hap.value = { note: hap.value };
|
||||
} */
|
||||
if (typeof hap.value !== 'object') {
|
||||
logger(
|
||||
`hap.value "${hap.value}" is not supported by webaudio output. Hint: append .note() or .s() to the end`,
|
||||
'error',
|
||||
);
|
||||
/* throw new Error(
|
||||
`hap.value "${hap.value}"" is not supported by webaudio output. Hint: append .note() or .s() to the end`,
|
||||
); */
|
||||
return;
|
||||
}
|
||||
// calculate correct time (tone.js workaround)
|
||||
hap.ensureObjectValue();
|
||||
|
||||
// calculate absolute time
|
||||
let t = ac.currentTime + deadline;
|
||||
// destructure value
|
||||
let {
|
||||
@@ -220,20 +197,14 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
|
||||
note,
|
||||
gain = 0.8,
|
||||
// low pass
|
||||
lpf,
|
||||
cutoff = lpf,
|
||||
lpq = 1,
|
||||
resonance = lpq,
|
||||
cutoff,
|
||||
resonance = 1,
|
||||
// high pass
|
||||
hpf,
|
||||
hcutoff = hpf,
|
||||
hpq = 1,
|
||||
hresonance = hpq,
|
||||
hcutoff,
|
||||
hresonance = 1,
|
||||
// band pass
|
||||
bpf,
|
||||
bandf = bpf,
|
||||
bpq = 1,
|
||||
bandq = bpq,
|
||||
bandf,
|
||||
bandq = 1,
|
||||
//
|
||||
coarse,
|
||||
crush,
|
||||
@@ -253,7 +224,6 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
|
||||
orbit = 1,
|
||||
room,
|
||||
size = 2,
|
||||
roomsize = size,
|
||||
} = hap.value;
|
||||
const { velocity = 1 } = hap.context;
|
||||
gain *= velocity; // legacy fix for velocity
|
||||
@@ -262,12 +232,6 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
|
||||
if (bank && s) {
|
||||
s = `${bank}_${s}`;
|
||||
}
|
||||
if (typeof s === 'string') {
|
||||
[s, n] = splitSN(s, n);
|
||||
}
|
||||
if (typeof note === 'string') {
|
||||
[note, n] = splitSN(note, n);
|
||||
}
|
||||
if (!s || ['sine', 'square', 'triangle', 'sawtooth'].includes(s)) {
|
||||
// destructure adsr here, because the default should be different for synths and samples
|
||||
const { attack = 0.001, decay = 0.05, sustain = 0.6, release = 0.01 } = hap.value;
|
||||
@@ -324,7 +288,7 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
|
||||
bufferSource.playbackRate.value = Math.abs(speed) * bufferSource.playbackRate.value;
|
||||
if (unit === 'c') {
|
||||
// are there other units?
|
||||
bufferSource.playbackRate.value = bufferSource.playbackRate.value * bufferSource.buffer.duration;
|
||||
bufferSource.playbackRate.value = bufferSource.playbackRate.value * bufferSource.buffer.duration * cps;
|
||||
}
|
||||
let duration = soundfont || clip ? hapDuration : bufferSource.buffer.duration / bufferSource.playbackRate.value;
|
||||
// "The computation of the offset into the sound is performed using the sound buffer's natural sample rate,
|
||||
@@ -385,8 +349,8 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
|
||||
}
|
||||
// reverb
|
||||
let reverbSend;
|
||||
if (room > 0 && roomsize > 0) {
|
||||
const reverbNode = getReverb(orbit, roomsize);
|
||||
if (room > 0 && size > 0) {
|
||||
const reverbNode = getReverb(orbit, size);
|
||||
reverbSend = effectSend(post, reverbNode, room);
|
||||
}
|
||||
|
||||
@@ -397,7 +361,7 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
|
||||
chain[0].onended = () => chain.concat([delaySend, reverbSend]).forEach((n) => n?.disconnect());
|
||||
};
|
||||
|
||||
export const webaudioOutputTrigger = (t, hap, ct, cps) => webaudioOutput(hap, t - ct, hap.duration / cps);
|
||||
export const webaudioOutputTrigger = (t, hap, ct, cps) => webaudioOutput(hap, t - ct, hap.duration / cps, cps);
|
||||
|
||||
Pattern.prototype.webaudio = function () {
|
||||
// TODO: refactor (t, hap, ct, cps) to (hap, deadline, duration) ?
|
||||
|
||||
Generated
+93
-75
@@ -11,6 +11,7 @@ importers:
|
||||
'@strudel.cycles/webaudio': workspace:*
|
||||
'@strudel.cycles/xen': workspace:*
|
||||
'@vitest/ui': ^0.25.7
|
||||
acorn: ^8.8.1
|
||||
c8: ^7.12.0
|
||||
canvas: ^2.11.0
|
||||
dependency-tree: ^9.0.0
|
||||
@@ -32,6 +33,7 @@ importers:
|
||||
'@strudel.cycles/transpiler': link:packages/transpiler
|
||||
'@strudel.cycles/webaudio': link:packages/webaudio
|
||||
'@strudel.cycles/xen': link:packages/xen
|
||||
acorn: 8.8.2
|
||||
dependency-tree: 9.0.0
|
||||
vitest: 0.25.8_@vitest+ui@0.25.8
|
||||
devDependencies:
|
||||
@@ -164,6 +166,8 @@ importers:
|
||||
'@codemirror/state': ^6.2.0
|
||||
'@codemirror/view': ^6.7.3
|
||||
'@lezer/highlight': ^1.1.3
|
||||
'@replit/codemirror-emacs': ^6.0.0
|
||||
'@replit/codemirror-vim': ^6.0.6
|
||||
'@strudel.cycles/core': workspace:*
|
||||
'@strudel.cycles/transpiler': workspace:*
|
||||
'@strudel.cycles/webaudio': workspace:*
|
||||
@@ -185,6 +189,8 @@ importers:
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.7.3
|
||||
'@lezer/highlight': 1.1.3
|
||||
'@replit/codemirror-emacs': 6.0.0_cgfc5aojxuwjajwhkrgidrzxoa
|
||||
'@replit/codemirror-vim': 6.0.6_a4vbhepr4qhxm5cldqd4jpyase
|
||||
'@strudel.cycles/core': link:../core
|
||||
'@strudel.cycles/transpiler': link:../transpiler
|
||||
'@strudel.cycles/webaudio': link:../webaudio
|
||||
@@ -312,21 +318,6 @@ importers:
|
||||
vite: 3.2.5
|
||||
vitest: 0.25.8
|
||||
|
||||
packages/wam:
|
||||
specifiers:
|
||||
'@strudel.cycles/core': workspace:*
|
||||
'@strudel.cycles/webaudio': workspace:*
|
||||
'@webaudiomodules/sdk': ^0.0.10
|
||||
vite: ^3.2.2
|
||||
vitest: ^0.25.7
|
||||
dependencies:
|
||||
'@strudel.cycles/core': link:../core
|
||||
'@strudel.cycles/webaudio': link:../webaudio
|
||||
'@webaudiomodules/sdk': 0.0.10
|
||||
devDependencies:
|
||||
vite: 3.2.5
|
||||
vitest: 0.25.8
|
||||
|
||||
packages/webaudio:
|
||||
specifiers:
|
||||
'@strudel.cycles/core': workspace:*
|
||||
@@ -373,6 +364,8 @@ importers:
|
||||
'@docsearch/react': ^3.1.0
|
||||
'@headlessui/react': ^1.7.7
|
||||
'@heroicons/react': ^2.0.13
|
||||
'@nanostores/persistent': ^0.7.0
|
||||
'@nanostores/react': ^0.4.1
|
||||
'@strudel.cycles/core': workspace:*
|
||||
'@strudel.cycles/csound': workspace:*
|
||||
'@strudel.cycles/midi': workspace:*
|
||||
@@ -383,10 +376,10 @@ importers:
|
||||
'@strudel.cycles/soundfonts': workspace:*
|
||||
'@strudel.cycles/tonal': workspace:*
|
||||
'@strudel.cycles/transpiler': workspace:*
|
||||
'@strudel.cycles/wam': workspace:*
|
||||
'@strudel.cycles/webaudio': workspace:*
|
||||
'@strudel.cycles/xen': workspace:*
|
||||
'@supabase/supabase-js': ^1.35.3
|
||||
'@tailwindcss/forms': ^0.5.3
|
||||
'@tailwindcss/typography': ^0.5.8
|
||||
'@types/node': ^18.0.0
|
||||
'@types/react': ^18.0.26
|
||||
@@ -398,6 +391,7 @@ importers:
|
||||
fraction.js: ^4.2.0
|
||||
html-escaper: ^3.0.3
|
||||
nanoid: ^4.0.0
|
||||
nanostores: ^0.7.4
|
||||
preact: ^10.7.3
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
@@ -418,6 +412,8 @@ importers:
|
||||
'@docsearch/react': 3.3.2_y6lbs4o5th67cuzjdmtw5eqh7a
|
||||
'@headlessui/react': 1.7.8_biqbaboplfbrettd7655fr4n2y
|
||||
'@heroicons/react': 2.0.14_react@18.2.0
|
||||
'@nanostores/persistent': 0.7.0_nanostores@0.7.4
|
||||
'@nanostores/react': 0.4.1_nkfnbc2tpc77iht7asm3uqwau4
|
||||
'@strudel.cycles/core': link:../packages/core
|
||||
'@strudel.cycles/csound': link:../packages/csound
|
||||
'@strudel.cycles/midi': link:../packages/midi
|
||||
@@ -428,10 +424,10 @@ importers:
|
||||
'@strudel.cycles/soundfonts': link:../packages/soundfonts
|
||||
'@strudel.cycles/tonal': link:../packages/tonal
|
||||
'@strudel.cycles/transpiler': link:../packages/transpiler
|
||||
'@strudel.cycles/wam': link:../packages/wam
|
||||
'@strudel.cycles/webaudio': link:../packages/webaudio
|
||||
'@strudel.cycles/xen': link:../packages/xen
|
||||
'@supabase/supabase-js': 1.35.7
|
||||
'@tailwindcss/forms': 0.5.3_tailwindcss@3.2.4
|
||||
'@tailwindcss/typography': 0.5.9_tailwindcss@3.2.4
|
||||
'@types/node': 18.11.18
|
||||
'@types/react': 18.0.27
|
||||
@@ -441,6 +437,7 @@ importers:
|
||||
canvas: 2.11.0
|
||||
fraction.js: 4.2.0
|
||||
nanoid: 4.0.0
|
||||
nanostores: 0.7.4
|
||||
preact: 10.11.3
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
@@ -3181,6 +3178,27 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@nanostores/persistent/0.7.0_nanostores@0.7.4:
|
||||
resolution: {integrity: sha512-4PAInL/T1hbftZUJ0cmgdFHBMalUoq7BUXFBy7QfyMv/8X3LPTYNh/yxspL7+J+XM3UNvVI7IFRMMs6FBasjhQ==}
|
||||
engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
nanostores: ^0.7.0
|
||||
dependencies:
|
||||
nanostores: 0.7.4
|
||||
dev: false
|
||||
|
||||
/@nanostores/react/0.4.1_nkfnbc2tpc77iht7asm3uqwau4:
|
||||
resolution: {integrity: sha512-lsv0CYrMxczbXtoV/mxFVEoL/uVjEjseoP89srO/5yNAOkJka+dSFS7LYyWEbuvCPO7EgbtkvRpO5V+OztKQOw==}
|
||||
engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
nanostores: ^0.7.0
|
||||
react: '>=18.0.0'
|
||||
dependencies:
|
||||
nanostores: 0.7.4
|
||||
react: 18.2.0
|
||||
use-sync-external-store: 1.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/@nodelib/fs.scandir/2.1.5:
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -3417,6 +3435,33 @@ packages:
|
||||
tsm: 2.3.0
|
||||
dev: false
|
||||
|
||||
/@replit/codemirror-emacs/6.0.0_cgfc5aojxuwjajwhkrgidrzxoa:
|
||||
resolution: {integrity: sha512-zxSDg3UKm7811hjqNtgvK9G0IBtCWf82Idb9nZQo0ldmGl4d9SV7oCSuXQ58NmOG4AV7coD7kgFSZhEqHhyQhA==}
|
||||
peerDependencies:
|
||||
'@codemirror/autocomplete': ^6.0.2
|
||||
'@codemirror/commands': ^6.0.0
|
||||
'@codemirror/search': ^6.0.0
|
||||
'@codemirror/state': ^6.0.1
|
||||
'@codemirror/view': ^6.0.2
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.4.0_a4vbhepr4qhxm5cldqd4jpyase
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.7.3
|
||||
dev: false
|
||||
|
||||
/@replit/codemirror-vim/6.0.6_a4vbhepr4qhxm5cldqd4jpyase:
|
||||
resolution: {integrity: sha512-/Lc+5AmV+T5pTm5P+rWpL+gseNHNye7xaUWpULczHai5ZLVg/ZE3+MBwK3Ai+/SmZKR/mK2YuXgNKnTGToEGYg==}
|
||||
peerDependencies:
|
||||
'@codemirror/commands': ^6.0.0
|
||||
'@codemirror/language': ^6.1.0
|
||||
'@codemirror/search': ^6.2.0
|
||||
'@codemirror/state': ^6.0.1
|
||||
'@codemirror/view': ^6.0.3
|
||||
dependencies:
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.7.3
|
||||
dev: false
|
||||
|
||||
/@rollup/plugin-babel/5.3.1_3dsfpkpoyvuuxyfgdbpn4j4uzm:
|
||||
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
@@ -3577,6 +3622,15 @@ packages:
|
||||
string.prototype.matchall: 4.0.8
|
||||
dev: true
|
||||
|
||||
/@tailwindcss/forms/0.5.3_tailwindcss@3.2.4:
|
||||
resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==}
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
|
||||
dependencies:
|
||||
mini-svg-data-uri: 1.4.4
|
||||
tailwindcss: 3.2.4
|
||||
dev: false
|
||||
|
||||
/@tailwindcss/typography/0.5.9_tailwindcss@3.2.4:
|
||||
resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==}
|
||||
peerDependencies:
|
||||
@@ -4266,10 +4320,6 @@ packages:
|
||||
resolution: {integrity: sha512-ukOMWnCg1tCvT7WnDfsUKQOFDQGsyR5tNgRpwmqi+5/vzU3ghdDXzvIM4IOPdSb3OeSsBNvmSL8nxIVOqi2WXA==}
|
||||
dev: false
|
||||
|
||||
/@webaudiomodules/sdk/0.0.10:
|
||||
resolution: {integrity: sha512-owzwJL3ETntGH325vxDZ5vJlrEIYlhqVjw9xu1z+M1zebX7vcWAzh/Bf6GxISFeKoVSH4te90q1BYPdzmh+PeA==}
|
||||
dev: false
|
||||
|
||||
/JSONStream/1.3.5:
|
||||
resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
|
||||
hasBin: true
|
||||
@@ -9392,6 +9442,11 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/mini-svg-data-uri/1.4.4:
|
||||
resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/minimatch/3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
dependencies:
|
||||
@@ -9620,6 +9675,11 @@ packages:
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/nanostores/0.7.4:
|
||||
resolution: {integrity: sha512-MBeUVt7NBcXqh7AGT+KSr3O0X/995CZsvcP2QEMP+PXFwb07qv3Vjyq+EX0yS8f12Vv3Tn2g/BvK/OZoMhJlOQ==}
|
||||
engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0}
|
||||
dev: false
|
||||
|
||||
/napi-build-utils/1.0.2:
|
||||
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
|
||||
dev: true
|
||||
@@ -10434,17 +10494,6 @@ 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'}
|
||||
@@ -10455,16 +10504,6 @@ 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==}
|
||||
@@ -10474,23 +10513,6 @@ 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==}
|
||||
@@ -10508,15 +10530,6 @@ 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'}
|
||||
@@ -10525,7 +10538,6 @@ 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==}
|
||||
@@ -12148,8 +12160,6 @@ 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
|
||||
@@ -12166,10 +12176,10 @@ packages:
|
||||
object-hash: 3.0.0
|
||||
picocolors: 1.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-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-selector-parser: 6.0.11
|
||||
postcss-value-parser: 4.2.0
|
||||
quick-lru: 5.1.1
|
||||
@@ -12875,6 +12885,14 @@ packages:
|
||||
punycode: 2.3.0
|
||||
dev: true
|
||||
|
||||
/use-sync-external-store/1.2.0_react@18.2.0:
|
||||
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/utf-8-validate/5.0.10:
|
||||
resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==}
|
||||
engines: {node: '>=6.14.2'}
|
||||
|
||||
@@ -885,39 +885,39 @@ exports[`runs examples > example "begin" example index 0 1`] = `
|
||||
|
||||
exports[`runs examples > example "bpf" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/2 | s:bd bpf:1000 ]",
|
||||
"[ 1/2 → 1/1 | s:sd bpf:1000 ]",
|
||||
"[ 0/1 → 1/3 | s:hh bpf:1000 ]",
|
||||
"[ 1/3 → 2/3 | s:hh bpf:1000 ]",
|
||||
"[ 2/3 → 1/1 | s:hh bpf:1000 ]",
|
||||
"[ 1/1 → 3/2 | s:bd bpf:2000 ]",
|
||||
"[ 3/2 → 2/1 | s:sd bpf:2000 ]",
|
||||
"[ 1/1 → 4/3 | s:hh bpf:2000 ]",
|
||||
"[ 4/3 → 5/3 | s:hh bpf:2000 ]",
|
||||
"[ 5/3 → 2/1 | s:hh bpf:2000 ]",
|
||||
"[ 2/1 → 5/2 | s:bd bpf:4000 ]",
|
||||
"[ 5/2 → 3/1 | s:sd bpf:4000 ]",
|
||||
"[ 2/1 → 7/3 | s:hh bpf:4000 ]",
|
||||
"[ 7/3 → 8/3 | s:hh bpf:4000 ]",
|
||||
"[ 8/3 → 3/1 | s:hh bpf:4000 ]",
|
||||
"[ 3/1 → 7/2 | s:bd bpf:8000 ]",
|
||||
"[ 7/2 → 4/1 | s:sd bpf:8000 ]",
|
||||
"[ 3/1 → 10/3 | s:hh bpf:8000 ]",
|
||||
"[ 10/3 → 11/3 | s:hh bpf:8000 ]",
|
||||
"[ 11/3 → 4/1 | s:hh bpf:8000 ]",
|
||||
"[ 0/1 → 1/2 | s:bd bandf:1000 ]",
|
||||
"[ 1/2 → 1/1 | s:sd bandf:1000 ]",
|
||||
"[ 0/1 → 1/3 | s:hh bandf:1000 ]",
|
||||
"[ 1/3 → 2/3 | s:hh bandf:1000 ]",
|
||||
"[ 2/3 → 1/1 | s:hh bandf:1000 ]",
|
||||
"[ 1/1 → 3/2 | s:bd bandf:2000 ]",
|
||||
"[ 3/2 → 2/1 | s:sd bandf:2000 ]",
|
||||
"[ 1/1 → 4/3 | s:hh bandf:2000 ]",
|
||||
"[ 4/3 → 5/3 | s:hh bandf:2000 ]",
|
||||
"[ 5/3 → 2/1 | s:hh bandf:2000 ]",
|
||||
"[ 2/1 → 5/2 | s:bd bandf:4000 ]",
|
||||
"[ 5/2 → 3/1 | s:sd bandf:4000 ]",
|
||||
"[ 2/1 → 7/3 | s:hh bandf:4000 ]",
|
||||
"[ 7/3 → 8/3 | s:hh bandf:4000 ]",
|
||||
"[ 8/3 → 3/1 | s:hh bandf:4000 ]",
|
||||
"[ 3/1 → 7/2 | s:bd bandf:8000 ]",
|
||||
"[ 7/2 → 4/1 | s:sd bandf:8000 ]",
|
||||
"[ 3/1 → 10/3 | s:hh bandf:8000 ]",
|
||||
"[ 10/3 → 11/3 | s:hh bandf:8000 ]",
|
||||
"[ 11/3 → 4/1 | s:hh bandf:8000 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "bpq" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/2 | s:bd bpf:500 bpq:0 ]",
|
||||
"[ 1/2 → 1/1 | s:sd bpf:500 bpq:0 ]",
|
||||
"[ 1/1 → 3/2 | s:bd bpf:500 bpq:1 ]",
|
||||
"[ 3/2 → 2/1 | s:sd bpf:500 bpq:1 ]",
|
||||
"[ 2/1 → 5/2 | s:bd bpf:500 bpq:2 ]",
|
||||
"[ 5/2 → 3/1 | s:sd bpf:500 bpq:2 ]",
|
||||
"[ 3/1 → 7/2 | s:bd bpf:500 bpq:3 ]",
|
||||
"[ 7/2 → 4/1 | s:sd bpf:500 bpq:3 ]",
|
||||
"[ 0/1 → 1/2 | s:bd bandf:500 bandq:0 ]",
|
||||
"[ 1/2 → 1/1 | s:sd bandf:500 bandq:0 ]",
|
||||
"[ 1/1 → 3/2 | s:bd bandf:500 bandq:1 ]",
|
||||
"[ 3/2 → 2/1 | s:sd bandf:500 bandq:1 ]",
|
||||
"[ 2/1 → 5/2 | s:bd bandf:500 bandq:2 ]",
|
||||
"[ 5/2 → 3/1 | s:sd bandf:500 bandq:2 ]",
|
||||
"[ 3/1 → 7/2 | s:bd bandf:500 bandq:3 ]",
|
||||
"[ 7/2 → 4/1 | s:sd bandf:500 bandq:3 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -1399,6 +1399,19 @@ exports[`runs examples > example "delay" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "delay" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/2 | s:bd delay:0.65 delaytime:0.25 delayfeedback:0.9 ]",
|
||||
"[ 1/2 → 1/1 | s:bd delay:0.65 delaytime:0.125 delayfeedback:0.7 ]",
|
||||
"[ 1/1 → 3/2 | s:bd delay:0.65 delaytime:0.25 delayfeedback:0.9 ]",
|
||||
"[ 3/2 → 2/1 | s:bd delay:0.65 delaytime:0.125 delayfeedback:0.7 ]",
|
||||
"[ 2/1 → 5/2 | s:bd delay:0.65 delaytime:0.25 delayfeedback:0.9 ]",
|
||||
"[ 5/2 → 3/1 | s:bd delay:0.65 delaytime:0.125 delayfeedback:0.7 ]",
|
||||
"[ 3/1 → 7/2 | s:bd delay:0.65 delaytime:0.25 delayfeedback:0.9 ]",
|
||||
"[ 7/2 → 4/1 | s:bd delay:0.65 delaytime:0.125 delayfeedback:0.7 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "delayfeedback" example index 0 1`] = `
|
||||
[
|
||||
"[ (0/1 → 1/1) ⇝ 2/1 | s:bd delay:0.25 delayfeedback:0.25 ]",
|
||||
@@ -1896,78 +1909,107 @@ exports[`runs examples > example "gain" example index 0 1`] = `
|
||||
|
||||
exports[`runs examples > example "hpf" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/2 | s:bd hpf:4000 ]",
|
||||
"[ 1/2 → 1/1 | s:sd hpf:4000 ]",
|
||||
"[ 0/1 → 1/4 | s:hh hpf:4000 ]",
|
||||
"[ 1/4 → 1/2 | s:hh hpf:4000 ]",
|
||||
"[ 1/2 → 3/4 | s:hh hpf:4000 ]",
|
||||
"[ 3/4 → 1/1 | s:hh hpf:4000 ]",
|
||||
"[ 1/1 → 3/2 | s:bd hpf:2000 ]",
|
||||
"[ 3/2 → 2/1 | s:sd hpf:2000 ]",
|
||||
"[ 1/1 → 5/4 | s:hh hpf:2000 ]",
|
||||
"[ 5/4 → 3/2 | s:hh hpf:2000 ]",
|
||||
"[ 3/2 → 7/4 | s:hh hpf:2000 ]",
|
||||
"[ 7/4 → 2/1 | s:hh hpf:2000 ]",
|
||||
"[ 2/1 → 5/2 | s:bd hpf:1000 ]",
|
||||
"[ 5/2 → 3/1 | s:sd hpf:1000 ]",
|
||||
"[ 2/1 → 9/4 | s:hh hpf:1000 ]",
|
||||
"[ 9/4 → 5/2 | s:hh hpf:1000 ]",
|
||||
"[ 5/2 → 11/4 | s:hh hpf:1000 ]",
|
||||
"[ 11/4 → 3/1 | s:hh hpf:1000 ]",
|
||||
"[ 3/1 → 7/2 | s:bd hpf:500 ]",
|
||||
"[ 7/2 → 4/1 | s:sd hpf:500 ]",
|
||||
"[ 3/1 → 13/4 | s:hh hpf:500 ]",
|
||||
"[ 13/4 → 7/2 | s:hh hpf:500 ]",
|
||||
"[ 7/2 → 15/4 | s:hh hpf:500 ]",
|
||||
"[ 15/4 → 4/1 | s:hh hpf:500 ]",
|
||||
"[ 0/1 → 1/2 | s:bd hcutoff:4000 ]",
|
||||
"[ 1/2 → 1/1 | s:sd hcutoff:4000 ]",
|
||||
"[ 0/1 → 1/4 | s:hh hcutoff:4000 ]",
|
||||
"[ 1/4 → 1/2 | s:hh hcutoff:4000 ]",
|
||||
"[ 1/2 → 3/4 | s:hh hcutoff:4000 ]",
|
||||
"[ 3/4 → 1/1 | s:hh hcutoff:4000 ]",
|
||||
"[ 1/1 → 3/2 | s:bd hcutoff:2000 ]",
|
||||
"[ 3/2 → 2/1 | s:sd hcutoff:2000 ]",
|
||||
"[ 1/1 → 5/4 | s:hh hcutoff:2000 ]",
|
||||
"[ 5/4 → 3/2 | s:hh hcutoff:2000 ]",
|
||||
"[ 3/2 → 7/4 | s:hh hcutoff:2000 ]",
|
||||
"[ 7/4 → 2/1 | s:hh hcutoff:2000 ]",
|
||||
"[ 2/1 → 5/2 | s:bd hcutoff:1000 ]",
|
||||
"[ 5/2 → 3/1 | s:sd hcutoff:1000 ]",
|
||||
"[ 2/1 → 9/4 | s:hh hcutoff:1000 ]",
|
||||
"[ 9/4 → 5/2 | s:hh hcutoff:1000 ]",
|
||||
"[ 5/2 → 11/4 | s:hh hcutoff:1000 ]",
|
||||
"[ 11/4 → 3/1 | s:hh hcutoff:1000 ]",
|
||||
"[ 3/1 → 7/2 | s:bd hcutoff:500 ]",
|
||||
"[ 7/2 → 4/1 | s:sd hcutoff:500 ]",
|
||||
"[ 3/1 → 13/4 | s:hh hcutoff:500 ]",
|
||||
"[ 13/4 → 7/2 | s:hh hcutoff:500 ]",
|
||||
"[ 7/2 → 15/4 | s:hh hcutoff:500 ]",
|
||||
"[ 15/4 → 4/1 | s:hh hcutoff:500 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "hpf" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/2 | s:bd hcutoff:2000 ]",
|
||||
"[ 1/2 → 1/1 | s:sd hcutoff:2000 ]",
|
||||
"[ 0/1 → 1/4 | s:hh hcutoff:2000 ]",
|
||||
"[ 1/4 → 1/2 | s:hh hcutoff:2000 ]",
|
||||
"[ 1/2 → 3/4 | s:hh hcutoff:2000 ]",
|
||||
"[ 3/4 → 1/1 | s:hh hcutoff:2000 ]",
|
||||
"[ 1/1 → 3/2 | s:bd hcutoff:2000 hresonance:25 ]",
|
||||
"[ 3/2 → 2/1 | s:sd hcutoff:2000 hresonance:25 ]",
|
||||
"[ 1/1 → 5/4 | s:hh hcutoff:2000 hresonance:25 ]",
|
||||
"[ 5/4 → 3/2 | s:hh hcutoff:2000 hresonance:25 ]",
|
||||
"[ 3/2 → 7/4 | s:hh hcutoff:2000 hresonance:25 ]",
|
||||
"[ 7/4 → 2/1 | s:hh hcutoff:2000 hresonance:25 ]",
|
||||
"[ 2/1 → 5/2 | s:bd hcutoff:2000 ]",
|
||||
"[ 5/2 → 3/1 | s:sd hcutoff:2000 ]",
|
||||
"[ 2/1 → 9/4 | s:hh hcutoff:2000 ]",
|
||||
"[ 9/4 → 5/2 | s:hh hcutoff:2000 ]",
|
||||
"[ 5/2 → 11/4 | s:hh hcutoff:2000 ]",
|
||||
"[ 11/4 → 3/1 | s:hh hcutoff:2000 ]",
|
||||
"[ 3/1 → 7/2 | s:bd hcutoff:2000 hresonance:25 ]",
|
||||
"[ 7/2 → 4/1 | s:sd hcutoff:2000 hresonance:25 ]",
|
||||
"[ 3/1 → 13/4 | s:hh hcutoff:2000 hresonance:25 ]",
|
||||
"[ 13/4 → 7/2 | s:hh hcutoff:2000 hresonance:25 ]",
|
||||
"[ 7/2 → 15/4 | s:hh hcutoff:2000 hresonance:25 ]",
|
||||
"[ 15/4 → 4/1 | s:hh hcutoff:2000 hresonance:25 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "hpq" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/2 | s:bd hpf:2000 hpq:0 ]",
|
||||
"[ 1/2 → 1/1 | s:sd hpf:2000 hpq:0 ]",
|
||||
"[ 0/1 → 1/4 | s:hh hpf:2000 hpq:0 ]",
|
||||
"[ 1/4 → 1/2 | s:hh hpf:2000 hpq:0 ]",
|
||||
"[ 1/2 → 3/4 | s:hh hpf:2000 hpq:0 ]",
|
||||
"[ 3/4 → 1/1 | s:hh hpf:2000 hpq:0 ]",
|
||||
"[ 1/1 → 3/2 | s:bd hpf:2000 hpq:10 ]",
|
||||
"[ 3/2 → 2/1 | s:sd hpf:2000 hpq:10 ]",
|
||||
"[ 1/1 → 5/4 | s:hh hpf:2000 hpq:10 ]",
|
||||
"[ 5/4 → 3/2 | s:hh hpf:2000 hpq:10 ]",
|
||||
"[ 3/2 → 7/4 | s:hh hpf:2000 hpq:10 ]",
|
||||
"[ 7/4 → 2/1 | s:hh hpf:2000 hpq:10 ]",
|
||||
"[ 2/1 → 5/2 | s:bd hpf:2000 hpq:20 ]",
|
||||
"[ 5/2 → 3/1 | s:sd hpf:2000 hpq:20 ]",
|
||||
"[ 2/1 → 9/4 | s:hh hpf:2000 hpq:20 ]",
|
||||
"[ 9/4 → 5/2 | s:hh hpf:2000 hpq:20 ]",
|
||||
"[ 5/2 → 11/4 | s:hh hpf:2000 hpq:20 ]",
|
||||
"[ 11/4 → 3/1 | s:hh hpf:2000 hpq:20 ]",
|
||||
"[ 3/1 → 7/2 | s:bd hpf:2000 hpq:30 ]",
|
||||
"[ 7/2 → 4/1 | s:sd hpf:2000 hpq:30 ]",
|
||||
"[ 3/1 → 13/4 | s:hh hpf:2000 hpq:30 ]",
|
||||
"[ 13/4 → 7/2 | s:hh hpf:2000 hpq:30 ]",
|
||||
"[ 7/2 → 15/4 | s:hh hpf:2000 hpq:30 ]",
|
||||
"[ 15/4 → 4/1 | s:hh hpf:2000 hpq:30 ]",
|
||||
"[ 0/1 → 1/2 | s:bd hcutoff:2000 hresonance:0 ]",
|
||||
"[ 1/2 → 1/1 | s:sd hcutoff:2000 hresonance:0 ]",
|
||||
"[ 0/1 → 1/4 | s:hh hcutoff:2000 hresonance:0 ]",
|
||||
"[ 1/4 → 1/2 | s:hh hcutoff:2000 hresonance:0 ]",
|
||||
"[ 1/2 → 3/4 | s:hh hcutoff:2000 hresonance:0 ]",
|
||||
"[ 3/4 → 1/1 | s:hh hcutoff:2000 hresonance:0 ]",
|
||||
"[ 1/1 → 3/2 | s:bd hcutoff:2000 hresonance:10 ]",
|
||||
"[ 3/2 → 2/1 | s:sd hcutoff:2000 hresonance:10 ]",
|
||||
"[ 1/1 → 5/4 | s:hh hcutoff:2000 hresonance:10 ]",
|
||||
"[ 5/4 → 3/2 | s:hh hcutoff:2000 hresonance:10 ]",
|
||||
"[ 3/2 → 7/4 | s:hh hcutoff:2000 hresonance:10 ]",
|
||||
"[ 7/4 → 2/1 | s:hh hcutoff:2000 hresonance:10 ]",
|
||||
"[ 2/1 → 5/2 | s:bd hcutoff:2000 hresonance:20 ]",
|
||||
"[ 5/2 → 3/1 | s:sd hcutoff:2000 hresonance:20 ]",
|
||||
"[ 2/1 → 9/4 | s:hh hcutoff:2000 hresonance:20 ]",
|
||||
"[ 9/4 → 5/2 | s:hh hcutoff:2000 hresonance:20 ]",
|
||||
"[ 5/2 → 11/4 | s:hh hcutoff:2000 hresonance:20 ]",
|
||||
"[ 11/4 → 3/1 | s:hh hcutoff:2000 hresonance:20 ]",
|
||||
"[ 3/1 → 7/2 | s:bd hcutoff:2000 hresonance:30 ]",
|
||||
"[ 7/2 → 4/1 | s:sd hcutoff:2000 hresonance:30 ]",
|
||||
"[ 3/1 → 13/4 | s:hh hcutoff:2000 hresonance:30 ]",
|
||||
"[ 13/4 → 7/2 | s:hh hcutoff:2000 hresonance:30 ]",
|
||||
"[ 7/2 → 15/4 | s:hh hcutoff:2000 hresonance:30 ]",
|
||||
"[ 15/4 → 4/1 | s:hh hcutoff:2000 hresonance:30 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "hurry" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 3/4 | s:bd speed:1 ]",
|
||||
"[ (3/4 → 1/1) ⇝ 3/2 | s:sd:2 speed:1 ]",
|
||||
"[ 3/4 ⇜ (1/1 → 3/2) | s:sd:2 speed:1 ]",
|
||||
"[ (3/4 → 1/1) ⇝ 3/2 | s:sd n:2 speed:1 ]",
|
||||
"[ 3/4 ⇜ (1/1 → 3/2) | s:sd n:2 speed:1 ]",
|
||||
"[ 3/2 → 15/8 | s:bd speed:2 ]",
|
||||
"[ (15/8 → 2/1) ⇝ 9/4 | s:sd:2 speed:2 ]",
|
||||
"[ 15/8 ⇜ (2/1 → 9/4) | s:sd:2 speed:2 ]",
|
||||
"[ (15/8 → 2/1) ⇝ 9/4 | s:sd n:2 speed:2 ]",
|
||||
"[ 15/8 ⇜ (2/1 → 9/4) | s:sd n:2 speed:2 ]",
|
||||
"[ 9/4 → 21/8 | s:bd speed:2 ]",
|
||||
"[ 21/8 → 3/1 | s:sd:2 speed:2 ]",
|
||||
"[ 21/8 → 3/1 | s:sd n:2 speed:2 ]",
|
||||
"[ 3/1 → 51/16 | s:bd speed:4 ]",
|
||||
"[ 51/16 → 27/8 | s:sd:2 speed:4 ]",
|
||||
"[ 51/16 → 27/8 | s:sd n:2 speed:4 ]",
|
||||
"[ 27/8 → 57/16 | s:bd speed:4 ]",
|
||||
"[ 57/16 → 15/4 | s:sd:2 speed:4 ]",
|
||||
"[ 57/16 → 15/4 | s:sd n:2 speed:4 ]",
|
||||
"[ 15/4 → 63/16 | s:bd speed:4 ]",
|
||||
"[ (63/16 → 4/1) ⇝ 33/8 | s:sd:2 speed:4 ]",
|
||||
"[ (63/16 → 4/1) ⇝ 33/8 | s:sd n:2 speed:4 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -2383,55 +2425,92 @@ exports[`runs examples > example "loopAtCps" example index 0 1`] = `
|
||||
|
||||
exports[`runs examples > example "lpf" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/2 | s:bd lpf:4000 ]",
|
||||
"[ 1/2 → 1/1 | s:sd lpf:4000 ]",
|
||||
"[ 0/1 → 1/3 | s:hh lpf:4000 ]",
|
||||
"[ 1/3 → 2/3 | s:hh lpf:4000 ]",
|
||||
"[ 2/3 → 1/1 | s:hh lpf:4000 ]",
|
||||
"[ 1/1 → 3/2 | s:bd lpf:2000 ]",
|
||||
"[ 3/2 → 2/1 | s:sd lpf:2000 ]",
|
||||
"[ 1/1 → 4/3 | s:hh lpf:2000 ]",
|
||||
"[ 4/3 → 5/3 | s:hh lpf:2000 ]",
|
||||
"[ 5/3 → 2/1 | s:hh lpf:2000 ]",
|
||||
"[ 2/1 → 5/2 | s:bd lpf:1000 ]",
|
||||
"[ 5/2 → 3/1 | s:sd lpf:1000 ]",
|
||||
"[ 2/1 → 7/3 | s:hh lpf:1000 ]",
|
||||
"[ 7/3 → 8/3 | s:hh lpf:1000 ]",
|
||||
"[ 8/3 → 3/1 | s:hh lpf:1000 ]",
|
||||
"[ 3/1 → 7/2 | s:bd lpf:500 ]",
|
||||
"[ 7/2 → 4/1 | s:sd lpf:500 ]",
|
||||
"[ 3/1 → 10/3 | s:hh lpf:500 ]",
|
||||
"[ 10/3 → 11/3 | s:hh lpf:500 ]",
|
||||
"[ 11/3 → 4/1 | s:hh lpf:500 ]",
|
||||
"[ 0/1 → 1/2 | s:bd cutoff:4000 ]",
|
||||
"[ 1/2 → 1/1 | s:sd cutoff:4000 ]",
|
||||
"[ 0/1 → 1/3 | s:hh cutoff:4000 ]",
|
||||
"[ 1/3 → 2/3 | s:hh cutoff:4000 ]",
|
||||
"[ 2/3 → 1/1 | s:hh cutoff:4000 ]",
|
||||
"[ 1/1 → 3/2 | s:bd cutoff:2000 ]",
|
||||
"[ 3/2 → 2/1 | s:sd cutoff:2000 ]",
|
||||
"[ 1/1 → 4/3 | s:hh cutoff:2000 ]",
|
||||
"[ 4/3 → 5/3 | s:hh cutoff:2000 ]",
|
||||
"[ 5/3 → 2/1 | s:hh cutoff:2000 ]",
|
||||
"[ 2/1 → 5/2 | s:bd cutoff:1000 ]",
|
||||
"[ 5/2 → 3/1 | s:sd cutoff:1000 ]",
|
||||
"[ 2/1 → 7/3 | s:hh cutoff:1000 ]",
|
||||
"[ 7/3 → 8/3 | s:hh cutoff:1000 ]",
|
||||
"[ 8/3 → 3/1 | s:hh cutoff:1000 ]",
|
||||
"[ 3/1 → 7/2 | s:bd cutoff:500 ]",
|
||||
"[ 7/2 → 4/1 | s:sd cutoff:500 ]",
|
||||
"[ 3/1 → 10/3 | s:hh cutoff:500 ]",
|
||||
"[ 10/3 → 11/3 | s:hh cutoff:500 ]",
|
||||
"[ 11/3 → 4/1 | s:hh cutoff:500 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "lpf" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:bd cutoff:1000 resonance:0 ]",
|
||||
"[ 1/8 → 1/4 | s:bd cutoff:1000 resonance:0 ]",
|
||||
"[ 1/4 → 3/8 | s:bd cutoff:1000 resonance:10 ]",
|
||||
"[ 3/8 → 1/2 | s:bd cutoff:1000 resonance:10 ]",
|
||||
"[ 1/2 → 5/8 | s:bd cutoff:1000 resonance:20 ]",
|
||||
"[ 5/8 → 3/4 | s:bd cutoff:1000 resonance:20 ]",
|
||||
"[ 3/4 → 7/8 | s:bd cutoff:1000 resonance:30 ]",
|
||||
"[ 7/8 → 1/1 | s:bd cutoff:1000 resonance:30 ]",
|
||||
"[ 1/1 → 9/8 | s:bd cutoff:1000 resonance:0 ]",
|
||||
"[ 9/8 → 5/4 | s:bd cutoff:1000 resonance:0 ]",
|
||||
"[ 5/4 → 11/8 | s:bd cutoff:1000 resonance:10 ]",
|
||||
"[ 11/8 → 3/2 | s:bd cutoff:1000 resonance:10 ]",
|
||||
"[ 3/2 → 13/8 | s:bd cutoff:1000 resonance:20 ]",
|
||||
"[ 13/8 → 7/4 | s:bd cutoff:1000 resonance:20 ]",
|
||||
"[ 7/4 → 15/8 | s:bd cutoff:1000 resonance:30 ]",
|
||||
"[ 15/8 → 2/1 | s:bd cutoff:1000 resonance:30 ]",
|
||||
"[ 2/1 → 17/8 | s:bd cutoff:1000 resonance:0 ]",
|
||||
"[ 17/8 → 9/4 | s:bd cutoff:1000 resonance:0 ]",
|
||||
"[ 9/4 → 19/8 | s:bd cutoff:1000 resonance:10 ]",
|
||||
"[ 19/8 → 5/2 | s:bd cutoff:1000 resonance:10 ]",
|
||||
"[ 5/2 → 21/8 | s:bd cutoff:1000 resonance:20 ]",
|
||||
"[ 21/8 → 11/4 | s:bd cutoff:1000 resonance:20 ]",
|
||||
"[ 11/4 → 23/8 | s:bd cutoff:1000 resonance:30 ]",
|
||||
"[ 23/8 → 3/1 | s:bd cutoff:1000 resonance:30 ]",
|
||||
"[ 3/1 → 25/8 | s:bd cutoff:1000 resonance:0 ]",
|
||||
"[ 25/8 → 13/4 | s:bd cutoff:1000 resonance:0 ]",
|
||||
"[ 13/4 → 27/8 | s:bd cutoff:1000 resonance:10 ]",
|
||||
"[ 27/8 → 7/2 | s:bd cutoff:1000 resonance:10 ]",
|
||||
"[ 7/2 → 29/8 | s:bd cutoff:1000 resonance:20 ]",
|
||||
"[ 29/8 → 15/4 | s:bd cutoff:1000 resonance:20 ]",
|
||||
"[ 15/4 → 31/8 | s:bd cutoff:1000 resonance:30 ]",
|
||||
"[ 31/8 → 4/1 | s:bd cutoff:1000 resonance:30 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "lpq" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/2 | s:bd lpf:2000 lpq:0 ]",
|
||||
"[ 1/2 → 1/1 | s:sd lpf:2000 lpq:0 ]",
|
||||
"[ 0/1 → 1/4 | s:hh lpf:2000 lpq:0 ]",
|
||||
"[ 1/4 → 1/2 | s:hh lpf:2000 lpq:0 ]",
|
||||
"[ 1/2 → 3/4 | s:hh lpf:2000 lpq:0 ]",
|
||||
"[ 3/4 → 1/1 | s:hh lpf:2000 lpq:0 ]",
|
||||
"[ 1/1 → 3/2 | s:bd lpf:2000 lpq:10 ]",
|
||||
"[ 3/2 → 2/1 | s:sd lpf:2000 lpq:10 ]",
|
||||
"[ 1/1 → 5/4 | s:hh lpf:2000 lpq:10 ]",
|
||||
"[ 5/4 → 3/2 | s:hh lpf:2000 lpq:10 ]",
|
||||
"[ 3/2 → 7/4 | s:hh lpf:2000 lpq:10 ]",
|
||||
"[ 7/4 → 2/1 | s:hh lpf:2000 lpq:10 ]",
|
||||
"[ 2/1 → 5/2 | s:bd lpf:2000 lpq:20 ]",
|
||||
"[ 5/2 → 3/1 | s:sd lpf:2000 lpq:20 ]",
|
||||
"[ 2/1 → 9/4 | s:hh lpf:2000 lpq:20 ]",
|
||||
"[ 9/4 → 5/2 | s:hh lpf:2000 lpq:20 ]",
|
||||
"[ 5/2 → 11/4 | s:hh lpf:2000 lpq:20 ]",
|
||||
"[ 11/4 → 3/1 | s:hh lpf:2000 lpq:20 ]",
|
||||
"[ 3/1 → 7/2 | s:bd lpf:2000 lpq:30 ]",
|
||||
"[ 7/2 → 4/1 | s:sd lpf:2000 lpq:30 ]",
|
||||
"[ 3/1 → 13/4 | s:hh lpf:2000 lpq:30 ]",
|
||||
"[ 13/4 → 7/2 | s:hh lpf:2000 lpq:30 ]",
|
||||
"[ 7/2 → 15/4 | s:hh lpf:2000 lpq:30 ]",
|
||||
"[ 15/4 → 4/1 | s:hh lpf:2000 lpq:30 ]",
|
||||
"[ 0/1 → 1/2 | s:bd cutoff:2000 resonance:0 ]",
|
||||
"[ 1/2 → 1/1 | s:sd cutoff:2000 resonance:0 ]",
|
||||
"[ 0/1 → 1/4 | s:hh cutoff:2000 resonance:0 ]",
|
||||
"[ 1/4 → 1/2 | s:hh cutoff:2000 resonance:0 ]",
|
||||
"[ 1/2 → 3/4 | s:hh cutoff:2000 resonance:0 ]",
|
||||
"[ 3/4 → 1/1 | s:hh cutoff:2000 resonance:0 ]",
|
||||
"[ 1/1 → 3/2 | s:bd cutoff:2000 resonance:10 ]",
|
||||
"[ 3/2 → 2/1 | s:sd cutoff:2000 resonance:10 ]",
|
||||
"[ 1/1 → 5/4 | s:hh cutoff:2000 resonance:10 ]",
|
||||
"[ 5/4 → 3/2 | s:hh cutoff:2000 resonance:10 ]",
|
||||
"[ 3/2 → 7/4 | s:hh cutoff:2000 resonance:10 ]",
|
||||
"[ 7/4 → 2/1 | s:hh cutoff:2000 resonance:10 ]",
|
||||
"[ 2/1 → 5/2 | s:bd cutoff:2000 resonance:20 ]",
|
||||
"[ 5/2 → 3/1 | s:sd cutoff:2000 resonance:20 ]",
|
||||
"[ 2/1 → 9/4 | s:hh cutoff:2000 resonance:20 ]",
|
||||
"[ 9/4 → 5/2 | s:hh cutoff:2000 resonance:20 ]",
|
||||
"[ 5/2 → 11/4 | s:hh cutoff:2000 resonance:20 ]",
|
||||
"[ 11/4 → 3/1 | s:hh cutoff:2000 resonance:20 ]",
|
||||
"[ 3/1 → 7/2 | s:bd cutoff:2000 resonance:30 ]",
|
||||
"[ 7/2 → 4/1 | s:sd cutoff:2000 resonance:30 ]",
|
||||
"[ 3/1 → 13/4 | s:hh cutoff:2000 resonance:30 ]",
|
||||
"[ 13/4 → 7/2 | s:hh cutoff:2000 resonance:30 ]",
|
||||
"[ 7/2 → 15/4 | s:hh cutoff:2000 resonance:30 ]",
|
||||
"[ 15/4 → 4/1 | s:hh cutoff:2000 resonance:30 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -3231,16 +3310,29 @@ exports[`runs examples > example "room" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "room" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/2 | s:bd room:0.9 size:1 ]",
|
||||
"[ 1/2 → 1/1 | s:sd room:0.9 size:1 ]",
|
||||
"[ 1/1 → 3/2 | s:bd room:0.9 size:4 ]",
|
||||
"[ 3/2 → 2/1 | s:sd room:0.9 size:4 ]",
|
||||
"[ 2/1 → 5/2 | s:bd room:0.9 size:1 ]",
|
||||
"[ 5/2 → 3/1 | s:sd room:0.9 size:1 ]",
|
||||
"[ 3/1 → 7/2 | s:bd room:0.9 size:4 ]",
|
||||
"[ 7/2 → 4/1 | s:sd room:0.9 size:4 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "roomsize" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/2 | s:bd room:0.8 roomsize:0 ]",
|
||||
"[ 1/2 → 1/1 | s:sd room:0.8 roomsize:0 ]",
|
||||
"[ 1/1 → 3/2 | s:bd room:0.8 roomsize:1 ]",
|
||||
"[ 3/2 → 2/1 | s:sd room:0.8 roomsize:1 ]",
|
||||
"[ 2/1 → 5/2 | s:bd room:0.8 roomsize:2 ]",
|
||||
"[ 5/2 → 3/1 | s:sd room:0.8 roomsize:2 ]",
|
||||
"[ 3/1 → 7/2 | s:bd room:0.8 roomsize:4 ]",
|
||||
"[ 7/2 → 4/1 | s:sd room:0.8 roomsize:4 ]",
|
||||
"[ 0/1 → 1/2 | s:bd room:0.8 size:0 ]",
|
||||
"[ 1/2 → 1/1 | s:sd room:0.8 size:0 ]",
|
||||
"[ 1/1 → 3/2 | s:bd room:0.8 size:1 ]",
|
||||
"[ 3/2 → 2/1 | s:sd room:0.8 size:1 ]",
|
||||
"[ 2/1 → 5/2 | s:bd room:0.8 size:2 ]",
|
||||
"[ 5/2 → 3/1 | s:sd room:0.8 size:2 ]",
|
||||
"[ 3/1 → 7/2 | s:bd room:0.8 size:4 ]",
|
||||
"[ 7/2 → 4/1 | s:sd room:0.8 size:4 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -3304,6 +3396,27 @@ exports[`runs examples > example "s" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "s" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | s:bd n:0 ]",
|
||||
"[ 1/4 → 1/2 | s:bd n:1 ]",
|
||||
"[ 1/2 → 3/4 | s:bd n:0 gain:0.3 ]",
|
||||
"[ 3/4 → 1/1 | s:bd n:1 gain:1.4 ]",
|
||||
"[ 1/1 → 5/4 | s:bd n:0 ]",
|
||||
"[ 5/4 → 3/2 | s:bd n:1 ]",
|
||||
"[ 3/2 → 7/4 | s:bd n:0 gain:0.3 ]",
|
||||
"[ 7/4 → 2/1 | s:bd n:1 gain:1.4 ]",
|
||||
"[ 2/1 → 9/4 | s:bd n:0 ]",
|
||||
"[ 9/4 → 5/2 | s:bd n:1 ]",
|
||||
"[ 5/2 → 11/4 | s:bd n:0 gain:0.3 ]",
|
||||
"[ 11/4 → 3/1 | s:bd n:1 gain:1.4 ]",
|
||||
"[ 3/1 → 13/4 | s:bd n:0 ]",
|
||||
"[ 13/4 → 7/2 | s:bd n:1 ]",
|
||||
"[ 7/2 → 15/4 | s:bd n:0 gain:0.3 ]",
|
||||
"[ 15/4 → 4/1 | s:bd n:1 gain:1.4 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "samples" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | s:bd ]",
|
||||
@@ -3454,6 +3567,43 @@ exports[`runs examples > example "scale" example index 1 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "scale" example index 2 1`] = `
|
||||
[
|
||||
"[ 7/8 → 1/1 | note:C2 s:folkharp ]",
|
||||
"[ 3/4 → 7/8 | note:D2 s:folkharp ]",
|
||||
"[ 5/8 → 3/4 | note:E2 s:folkharp ]",
|
||||
"[ 1/2 → 5/8 | note:F2 s:folkharp ]",
|
||||
"[ 3/8 → 1/2 | note:G2 s:folkharp ]",
|
||||
"[ 1/4 → 3/8 | note:A2 s:folkharp ]",
|
||||
"[ 1/8 → 1/4 | note:B2 s:folkharp ]",
|
||||
"[ 0/1 → 1/8 | note:C3 s:folkharp ]",
|
||||
"[ 15/8 → 2/1 | note:C2 s:folkharp ]",
|
||||
"[ 7/4 → 15/8 | note:D2 s:folkharp ]",
|
||||
"[ 13/8 → 7/4 | note:Eb2 s:folkharp ]",
|
||||
"[ 3/2 → 13/8 | note:F2 s:folkharp ]",
|
||||
"[ 11/8 → 3/2 | note:G2 s:folkharp ]",
|
||||
"[ 5/4 → 11/8 | note:Ab2 s:folkharp ]",
|
||||
"[ 9/8 → 5/4 | note:Bb2 s:folkharp ]",
|
||||
"[ 1/1 → 9/8 | note:C3 s:folkharp ]",
|
||||
"[ 23/8 → 3/1 | note:C2 s:folkharp ]",
|
||||
"[ 11/4 → 23/8 | note:D2 s:folkharp ]",
|
||||
"[ 21/8 → 11/4 | note:E2 s:folkharp ]",
|
||||
"[ 5/2 → 21/8 | note:F2 s:folkharp ]",
|
||||
"[ 19/8 → 5/2 | note:G2 s:folkharp ]",
|
||||
"[ 9/4 → 19/8 | note:A2 s:folkharp ]",
|
||||
"[ 17/8 → 9/4 | note:B2 s:folkharp ]",
|
||||
"[ 2/1 → 17/8 | note:C3 s:folkharp ]",
|
||||
"[ 31/8 → 4/1 | note:C2 s:folkharp ]",
|
||||
"[ 15/4 → 31/8 | note:D2 s:folkharp ]",
|
||||
"[ 29/8 → 15/4 | note:Eb2 s:folkharp ]",
|
||||
"[ 7/2 → 29/8 | note:F2 s:folkharp ]",
|
||||
"[ 27/8 → 7/2 | note:G2 s:folkharp ]",
|
||||
"[ 13/4 → 27/8 | note:Ab2 s:folkharp ]",
|
||||
"[ 25/8 → 13/4 | note:Bb2 s:folkharp ]",
|
||||
"[ 3/1 → 25/8 | note:C3 s:folkharp ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "scaleTranspose" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/2 | note:C3 ]",
|
||||
|
||||
@@ -298,11 +298,11 @@ exports[`renders tunes > tune: bassFuge 1`] = `
|
||||
"[ -3/4 ⇜ (0/1 → 3/4) ⇝ 5/4 | note:C5 s:flbass n:0 gain:0.3 cutoff:2924.3791043233605 resonance:10 clip:1 ]",
|
||||
"[ -3/4 ⇜ (3/4 → 1/1) ⇝ 5/4 | note:A4 s:flbass n:0 gain:0.3 cutoff:2924.3791043233605 resonance:10 clip:1 ]",
|
||||
"[ -3/4 ⇜ (3/4 → 1/1) ⇝ 5/4 | note:C5 s:flbass n:0 gain:0.3 cutoff:2924.3791043233605 resonance:10 clip:1 ]",
|
||||
"[ 0/1 → 1/2 | s:bd:1 ]",
|
||||
"[ 1/2 → 1/1 | s:bd:1 ]",
|
||||
"[ 1/2 → 1/1 | s:sd:0 ]",
|
||||
"[ 1/4 → 1/2 | s:hh:0 ]",
|
||||
"[ 3/4 → 1/1 | s:hh:0 ]",
|
||||
"[ 0/1 → 1/2 | s:bd n:1 ]",
|
||||
"[ 1/2 → 1/1 | s:bd n:1 ]",
|
||||
"[ 1/2 → 1/1 | s:sd n:0 ]",
|
||||
"[ 1/4 → 1/2 | s:hh n:0 ]",
|
||||
"[ 3/4 → 1/1 | s:hh n:0 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -313,7 +313,7 @@ exports[`renders tunes > tune: belldub 1`] = `
|
||||
"[ (5/8 → 1/1) ⇝ 5/4 | s:hh room:0 end:0.04483079938329212 ]",
|
||||
"[ 0/1 → 5/16 | s:mt gain:0.5 room:0.5 ]",
|
||||
"[ (15/16 → 1/1) ⇝ 5/4 | s:lt gain:0.5 room:0.5 ]",
|
||||
"[ (0/1 → 1/1) ⇝ 5/1 | s:misc:2 speed:1 delay:0.5 delaytime:0.3333333333333333 gain:0.4 ]",
|
||||
"[ (0/1 → 1/1) ⇝ 5/1 | s:misc n:2 speed:1 delay:0.5 delaytime:0.3333333333333333 gain:0.4 ]",
|
||||
"[ (5/8 → 1/1) ⇝ 5/4 | note:F3 s:sawtooth gain:0.5 cutoff:400.16785462816676 decay:0.05380063255866716 sustain:0 delay:0.9 room:1 ]",
|
||||
"[ (5/8 → 1/1) ⇝ 5/4 | note:A3 s:sawtooth gain:0.5 cutoff:400.16785462816676 decay:0.05380063255866716 sustain:0 delay:0.9 room:1 ]",
|
||||
"[ (5/8 → 1/1) ⇝ 5/4 | note:Bb3 s:sawtooth gain:0.5 cutoff:400.16785462816676 decay:0.05380063255866716 sustain:0 delay:0.9 room:1 ]",
|
||||
@@ -6974,16 +6974,16 @@ exports[`renders tunes > tune: flatrave 1`] = `
|
||||
"[ 1/2 → 1/1 | s:bd bank:RolandTR909 ]",
|
||||
"[ 1/2 → 1/1 | s:cp bank:RolandTR909 ]",
|
||||
"[ 1/2 → 1/1 | s:sd bank:RolandTR909 ]",
|
||||
"[ 0/1 → 1/4 | s:hh:1 end:0.02000058072071123 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 0/1 ⇜ (1/8 → 1/4) | s:hh:1 end:0.02000058072071123 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 1/4 → 3/8 | s:hh:1 end:0.02000875429921906 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 1/4 → 3/8 | s:hh:1 end:0.02000875429921906 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 3/8 → 1/2 | s:hh:1 end:0.020023446730265706 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 5/8 → 3/4 | s:hh:1 end:0.020086608138500644 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 5/8 → 3/4 | s:hh:1 end:0.020086608138500644 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 3/4 → 7/8 | s:hh:1 end:0.02013941880355398 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 1/8 → 1/4 | s:hh:1 speed:0.5 delay:0.5 end:0.020001936784171157 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 1/8 → 1/4 | s:hh:1 speed:0.5 delay:0.5 end:0.020001936784171157 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 0/1 → 1/4 | s:hh n:1 end:0.02000058072071123 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 0/1 ⇜ (1/8 → 1/4) | s:hh n:1 end:0.02000058072071123 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 1/4 → 3/8 | s:hh n:1 end:0.02000875429921906 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 1/4 → 3/8 | s:hh n:1 end:0.02000875429921906 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 3/8 → 1/2 | s:hh n:1 end:0.020023446730265706 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 5/8 → 3/4 | s:hh n:1 end:0.020086608138500644 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 5/8 → 3/4 | s:hh n:1 end:0.020086608138500644 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 3/4 → 7/8 | s:hh n:1 end:0.02013941880355398 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 1/8 → 1/4 | s:hh n:1 speed:0.5 delay:0.5 end:0.020001936784171157 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 1/8 → 1/4 | s:hh n:1 speed:0.5 delay:0.5 end:0.020001936784171157 bank:RolandTR909 room:0.5 gain:0.4 ]",
|
||||
"[ 1/8 → 1/4 | note:G1 s:sawtooth decay:0.1 sustain:0 ]",
|
||||
"[ 1/4 → 3/8 | note:G1 s:sawtooth decay:0.1 sustain:0 ]",
|
||||
"[ 1/2 → 5/8 | note:G1 s:sawtooth decay:0.1 sustain:0 ]",
|
||||
@@ -8127,8 +8127,8 @@ exports[`renders tunes > tune: loungeSponge 1`] = `
|
||||
|
||||
exports[`renders tunes > tune: meltingsubmarine 1`] = `
|
||||
[
|
||||
"[ (0/1 → 1/1) ⇝ 3/2 | s:bd:5 speed:0.7519542165100574 ]",
|
||||
"[ (3/4 → 1/1) ⇝ 3/2 | s:sd:1 speed:0.7931522866332671 ]",
|
||||
"[ (0/1 → 1/1) ⇝ 3/2 | s:bd n:5 speed:0.7519542165100574 ]",
|
||||
"[ (3/4 → 1/1) ⇝ 3/2 | s:sd n:1 speed:0.7931522866332671 ]",
|
||||
"[ 3/8 → 3/4 | s:hh27 speed:0.7285963821098448 ]",
|
||||
"[ (3/4 → 1/1) ⇝ 9/8 | s:hh27 speed:0.77531205091027 ]",
|
||||
"[ (0/1 → 1/1) ⇝ 3/2 | note:33.129885541275144 decay:0.15 sustain:0 s:sawtooth gain:0.4 cutoff:3669.6267869262615 ]",
|
||||
@@ -8364,15 +8364,15 @@ exports[`renders tunes > tune: randomBells 1`] = `
|
||||
|
||||
exports[`renders tunes > tune: sampleDemo 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | s:woodblock:1 ]",
|
||||
"[ 1/4 → 3/8 | s:woodblock:2 ]",
|
||||
"[ 0/1 → 1/8 | s:brakedrum:1 ]",
|
||||
"[ 3/4 → 7/8 | s:brakedrum:1 ]",
|
||||
"[ 3/8 → 1/2 | s:woodblock:2 speed:2 ]",
|
||||
"[ 1/2 → 1/1 | s:snare_rim:0 speed:2 ]",
|
||||
"[ 0/1 → 1/4 | s:woodblock n:1 ]",
|
||||
"[ 1/4 → 3/8 | s:woodblock n:2 ]",
|
||||
"[ 0/1 → 1/8 | s:brakedrum n:1 ]",
|
||||
"[ 3/4 → 7/8 | s:brakedrum n:1 ]",
|
||||
"[ 3/8 → 1/2 | s:woodblock n:2 speed:2 ]",
|
||||
"[ 1/2 → 1/1 | s:snare_rim n:0 speed:2 ]",
|
||||
"[ (0/1 → 1/1) ⇝ 8/1 | s:gong speed:2 ]",
|
||||
"[ 3/8 → 1/2 | s:brakedrum:1 speed:2 ]",
|
||||
"[ 3/4 → 1/1 | s:cowbell:3 speed:2 ]",
|
||||
"[ 3/8 → 1/2 | s:brakedrum n:1 speed:2 ]",
|
||||
"[ 3/4 → 1/1 | s:cowbell n:3 speed:2 ]",
|
||||
"[ -3/4 ⇜ (0/1 → 1/4) | note:Bb3 s:clavisynth gain:0.2 delay:0.25 pan:0 ]",
|
||||
"[ (3/4 → 1/1) ⇝ 7/4 | note:Bb3 s:clavisynth gain:0.2 delay:0.25 pan:1 ]",
|
||||
"[ -1/4 ⇜ (0/1 → 3/4) | note:F3 s:clavisynth gain:0.2 delay:0.25 pan:1 ]",
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
"@docsearch/react": "^3.1.0",
|
||||
"@headlessui/react": "^1.7.7",
|
||||
"@heroicons/react": "^2.0.13",
|
||||
"@nanostores/persistent": "^0.7.0",
|
||||
"@nanostores/react": "^0.4.1",
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@strudel.cycles/csound": "workspace:*",
|
||||
"@strudel.cycles/midi": "workspace:*",
|
||||
@@ -31,10 +33,10 @@
|
||||
"@strudel.cycles/soundfonts": "workspace:*",
|
||||
"@strudel.cycles/tonal": "workspace:*",
|
||||
"@strudel.cycles/transpiler": "workspace:*",
|
||||
"@strudel.cycles/wam": "workspace:*",
|
||||
"@strudel.cycles/webaudio": "workspace:*",
|
||||
"@strudel.cycles/xen": "workspace:*",
|
||||
"@supabase/supabase-js": "^1.35.3",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/react": "^18.0.26",
|
||||
@@ -44,6 +46,7 @@
|
||||
"canvas": "^2.11.0",
|
||||
"fraction.js": "^4.2.0",
|
||||
"nanoid": "^4.0.0",
|
||||
"nanostores": "^0.7.4",
|
||||
"preact": "^10.7.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@@ -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->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->@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->@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->@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->@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->@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->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->@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->@strudel.cycles/core@0.6.8 -->
|
||||
|
||||
<!-- osc-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->osc-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->@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->@strudel.cycles/core@0.6.8 -->
|
||||
|
||||
<!-- @strudel.cycles/soundfonts@0.6.0->@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->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->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->@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->@tonaljs/tonal@4.10.0 -->
|
||||
|
||||
<!-- chord-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->chord-voicings@0.0.1 -->
|
||||
|
||||
<!-- @strudel.cycles/tonal@0.6.0->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->@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->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->escodegen@2.0.0 -->
|
||||
|
||||
<!-- estree-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->estree-walker@3.0.3 -->
|
||||
|
||||
<!-- @strudel.cycles/webaudio@0.6.1->@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->@strudel.cycles/core@0.6.8 -->
|
||||
|
||||
<!-- @strudel.cycles/webaudio@0.6.0->@strudel.cycles/core@0.6.8 -->
|
||||
|
||||
<!-- chord-voicings@0.0.1->@tonaljs/tonal@4.10.0 -->
|
||||
<g id="edge27" class="edge">
|
||||
<title>chord-voicings@0.0.1->@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->soundfont2@0.4.0 -->
|
||||
<g id="edge28" class="edge">
|
||||
<title>sfumato@0.1.2->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->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->@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->@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->@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->@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->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->@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->@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->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->@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->@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->@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->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->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->@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->@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->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->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->@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->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->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->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->@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->@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->@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->@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.
@@ -0,0 +1,51 @@
|
||||
Copyright 2022 The 3270font Authors (https://github.com/rbanffy/3270font)
|
||||
|
||||
Copyright (c) 2011-2022, Ricardo Banffy.
|
||||
Copyright (c) 1993-2011, Paul Mattes.
|
||||
Copyright (c) 2004-2005, Don Russell.
|
||||
Copyright (c) 2004, Dick Altenbern.
|
||||
Copyright (c) 1990, Jeff Sparkes.
|
||||
Copyright (c) 1989, Georgia Tech Research Corporation (GTRC), Atlanta, GA 30332.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of Ricardo Banffy, Paul Mattes, Don Russell,
|
||||
Dick Altenbern, Jeff Sparkes, GTRC nor the names of their contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL RICARDO BANFFY, PAUL MATTES, DON RUSSELL, DICK ALTENBERN, JEFF
|
||||
SPARKES OR GTRC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
||||
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The Debian Logo glyph is based on the Debian Open Use Logo and is
|
||||
Copyright (c) 1999 Software in the Public Interest, Inc., and it is
|
||||
incorporated here under the terms of the Creative Commons
|
||||
Attribution-ShareAlike 3.0 Unported License. The logo is released
|
||||
under the terms of the GNU Lesser General Public License, version 3 or
|
||||
any later version, or, at your option, of the Creative Commons
|
||||
Attribution-ShareAlike 3.0 Unported License.
|
||||
|
||||
Ubuntu, the Ubuntu logo and the Circle of Friends symbol are
|
||||
registered trademarks of Canonical Ltd.
|
||||
|
||||
The Fontforge SFD font description file is optionally licensed under
|
||||
the SIL Open Font License v1.1 with no Reserved Font Name. This
|
||||
license is available with a FAQ at http://scripts.sil.org/OFL.
|
||||
Binary file not shown.
@@ -0,0 +1,428 @@
|
||||
Attribution-ShareAlike 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More_considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution-ShareAlike 4.0 International Public
|
||||
License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution-ShareAlike 4.0 International Public License ("Public
|
||||
License"). To the extent this Public License may be interpreted as a
|
||||
contract, You are granted the Licensed Rights in consideration of Your
|
||||
acceptance of these terms and conditions, and the Licensor grants You
|
||||
such rights in consideration of benefits the Licensor receives from
|
||||
making the Licensed Material available under these terms and
|
||||
conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. BY-SA Compatible License means a license listed at
|
||||
creativecommons.org/compatiblelicenses, approved by Creative
|
||||
Commons as essentially the equivalent of this Public License.
|
||||
|
||||
d. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
e. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
f. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
g. License Elements means the license attributes listed in the name
|
||||
of a Creative Commons Public License. The License Elements of this
|
||||
Public License are Attribution and ShareAlike.
|
||||
|
||||
h. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
i. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
j. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
k. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
l. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
m. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. Additional offer from the Licensor -- Adapted Material.
|
||||
Every recipient of Adapted Material from You
|
||||
automatically receives an offer from the Licensor to
|
||||
exercise the Licensed Rights in the Adapted Material
|
||||
under the conditions of the Adapter's License You apply.
|
||||
|
||||
c. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
b. ShareAlike.
|
||||
|
||||
In addition to the conditions in Section 3(a), if You Share
|
||||
Adapted Material You produce, the following conditions also apply.
|
||||
|
||||
1. The Adapter's License You apply must be a Creative Commons
|
||||
license with the same License Elements, this version or
|
||||
later, or a BY-SA Compatible License.
|
||||
|
||||
2. You must include the text of, or the URI or hyperlink to, the
|
||||
Adapter's License You apply. You may satisfy this condition
|
||||
in any reasonable manner based on the medium, means, and
|
||||
context in which You Share Adapted Material.
|
||||
|
||||
3. You may not offer or impose any additional or different terms
|
||||
or conditions on, or apply any Effective Technological
|
||||
Measures to, Adapted Material that restrict exercise of the
|
||||
rights granted under the Adapter's License You apply.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material,
|
||||
|
||||
including for purposes of Section 3(b); and
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public
|
||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||
its public licenses to material it publishes and in those instances
|
||||
will be considered the “Licensor.” The text of the Creative Commons
|
||||
public licenses is dedicated to the public domain under the CC0 Public
|
||||
Domain Dedication. Except for the limited purpose of indicating that
|
||||
material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the
|
||||
public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
Copyright (c) 2012, Cody "CodeMan38" Boisclair (cody@zone38.net), with Reserved Font Name "Press Start 2P"
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 520 KiB |
@@ -1,66 +1,64 @@
|
||||
---
|
||||
// fetch all commits for just this page's path
|
||||
type Props = {
|
||||
path: string;
|
||||
path: string;
|
||||
};
|
||||
const { path } = Astro.props as Props;
|
||||
const resolvedPath = `examples/docs/${path}`;
|
||||
const url = `https://api.github.com/repos/withastro/astro/commits?path=${resolvedPath}`;
|
||||
const commitsURL = `https://github.com/withastro/astro/commits/main/${resolvedPath}`;
|
||||
const resolvedPath = `website/src/pages${path}.mdx`;
|
||||
const url = `https://api.github.com/repos/tidalcycles/strudel/commits?path=${resolvedPath}`;
|
||||
const commitsURL = `https://github.com/tidalcycles/strudel/commits/main/${resolvedPath}`;
|
||||
|
||||
type Commit = {
|
||||
author: {
|
||||
id: string;
|
||||
login: string;
|
||||
};
|
||||
author: {
|
||||
id: string;
|
||||
login: string;
|
||||
};
|
||||
};
|
||||
|
||||
async function getCommits(url: string) {
|
||||
try {
|
||||
const token = import.meta.env.SNOWPACK_PUBLIC_GITHUB_TOKEN ?? 'hello';
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
'Cannot find "SNOWPACK_PUBLIC_GITHUB_TOKEN" used for escaping rate-limiting.'
|
||||
);
|
||||
}
|
||||
try {
|
||||
const token = import.meta.env.SNOWPACK_PUBLIC_GITHUB_TOKEN ?? 'hello';
|
||||
if (!token) {
|
||||
throw new Error('Cannot find "SNOWPACK_PUBLIC_GITHUB_TOKEN" used for escaping rate-limiting.');
|
||||
}
|
||||
|
||||
const auth = `Basic ${Buffer.from(token, 'binary').toString('base64')}`;
|
||||
const auth = `Basic ${Buffer.from(token, 'binary').toString('base64')}`;
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: auth,
|
||||
'User-Agent': 'astro-docs/1.0',
|
||||
},
|
||||
});
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: auth,
|
||||
'User-Agent': 'astro-docs/1.0',
|
||||
},
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`Request to fetch commits failed. Reason: ${res.statusText}
|
||||
Message: ${data.message}`
|
||||
);
|
||||
}
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`Request to fetch commits failed. Reason: ${res.statusText}
|
||||
Message: ${data.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
return data as Commit[];
|
||||
} catch (e) {
|
||||
console.warn(`[error] /src/components/AvatarList.astro
|
||||
return data as Commit[];
|
||||
} catch (e) {
|
||||
console.warn(`[error] /src/components/AvatarList.astro
|
||||
${(e as any)?.message ?? e}`);
|
||||
return [] as Commit[];
|
||||
}
|
||||
return [] as Commit[];
|
||||
}
|
||||
}
|
||||
|
||||
function removeDups(arr: Commit[]) {
|
||||
const map = new Map<string, Commit['author']>();
|
||||
const map = new Map<string, Commit['author']>();
|
||||
for (let item of arr) {
|
||||
const author = item.author;
|
||||
// Deduplicate based on author.id
|
||||
//map.set(author.id, { login: author.login, id: author.id });
|
||||
author && map.set(author.id, { login: author.login, id: author.id });
|
||||
}
|
||||
|
||||
for (let item of arr) {
|
||||
const author = item.author;
|
||||
// Deduplicate based on author.id
|
||||
map.set(author.id, { login: author.login, id: author.id });
|
||||
}
|
||||
|
||||
return [...map.values()];
|
||||
return [...map.values()];
|
||||
}
|
||||
|
||||
const data = await getCommits(url);
|
||||
@@ -70,102 +68,102 @@ const additionalContributors = unique.length - recentContributors.length; // lis
|
||||
---
|
||||
|
||||
<!-- Thanks to @5t3ph for https://smolcss.dev/#smol-avatar-list! -->
|
||||
<div class="contributors">
|
||||
<ul class="avatar-list" style={`--avatar-count: ${recentContributors.length}`}>
|
||||
{
|
||||
recentContributors.map((item) => (
|
||||
<li>
|
||||
<a href={`https://github.com/${item.login}`}>
|
||||
<img
|
||||
alt={`Contributor ${item.login}`}
|
||||
title={`Contributor ${item.login}`}
|
||||
width="64"
|
||||
height="64"
|
||||
src={`https://avatars.githubusercontent.com/u/${item.id}`}
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
{
|
||||
additionalContributors > 0 && (
|
||||
<span>
|
||||
<a href={commitsURL}>{`and ${additionalContributors} additional contributor${
|
||||
additionalContributors > 1 ? 's' : ''
|
||||
}.`}</a>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
{unique.length === 0 && <a href={commitsURL}>Contributors</a>}
|
||||
<div class="contributors px-4 mb-8">
|
||||
<ul class="avatar-list" style={`--avatar-count: ${recentContributors.length}`}>
|
||||
{
|
||||
recentContributors.map((item) => (
|
||||
<li>
|
||||
<a href={`https://github.com/${item.login}`}>
|
||||
<img
|
||||
alt={`Contributor ${item.login}`}
|
||||
title={`Contributor ${item.login}`}
|
||||
width="64"
|
||||
height="64"
|
||||
src={`https://avatars.githubusercontent.com/u/${item.id}`}
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
{
|
||||
additionalContributors > 0 && (
|
||||
<span>
|
||||
<a href={commitsURL}>{`and ${additionalContributors} additional contributor${
|
||||
additionalContributors > 1 ? 's' : ''
|
||||
}.`}</a>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
{unique.length === 0 && <a href={commitsURL}>Contributors</a>}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.avatar-list {
|
||||
--avatar-size: 2.5rem;
|
||||
--avatar-count: 3;
|
||||
.avatar-list {
|
||||
--avatar-size: 2.5rem;
|
||||
--avatar-count: 3;
|
||||
|
||||
display: grid;
|
||||
list-style: none;
|
||||
/* Default to displaying most of the avatar to
|
||||
display: grid;
|
||||
list-style: none;
|
||||
/* Default to displaying most of the avatar to
|
||||
enable easier access on touch devices, ensuring
|
||||
the WCAG touch target size is met or exceeded */
|
||||
grid-template-columns: repeat(var(--avatar-count), max(44px, calc(var(--avatar-size) / 1.15)));
|
||||
/* `padding` matches added visual dimensions of
|
||||
grid-template-columns: repeat(var(--avatar-count), max(44px, calc(var(--avatar-size) / 1.15)));
|
||||
/* `padding` matches added visual dimensions of
|
||||
the `box-shadow` to help create a more accurate
|
||||
computed component size */
|
||||
padding: 0.08em;
|
||||
font-size: var(--avatar-size);
|
||||
}
|
||||
padding: 0.08em;
|
||||
font-size: var(--avatar-size);
|
||||
}
|
||||
|
||||
@media (any-hover: hover) and (any-pointer: fine) {
|
||||
.avatar-list {
|
||||
/* We create 1 extra cell to enable the computed
|
||||
@media (any-hover: hover) and (any-pointer: fine) {
|
||||
.avatar-list {
|
||||
/* We create 1 extra cell to enable the computed
|
||||
width to match the final visual width */
|
||||
grid-template-columns: repeat(calc(var(--avatar-count) + 1), calc(var(--avatar-size) / 1.75));
|
||||
}
|
||||
}
|
||||
grid-template-columns: repeat(calc(var(--avatar-count) + 1), calc(var(--avatar-size) / 1.75));
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-list li {
|
||||
width: var(--avatar-size);
|
||||
height: var(--avatar-size);
|
||||
}
|
||||
.avatar-list li {
|
||||
width: var(--avatar-size);
|
||||
height: var(--avatar-size);
|
||||
}
|
||||
|
||||
.avatar-list li:hover ~ li a,
|
||||
.avatar-list li:focus-within ~ li a {
|
||||
transform: translateX(33%);
|
||||
}
|
||||
.avatar-list li:hover ~ li a,
|
||||
.avatar-list li:focus-within ~ li a {
|
||||
transform: translateX(33%);
|
||||
}
|
||||
|
||||
.avatar-list img,
|
||||
.avatar-list a {
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.avatar-list img,
|
||||
.avatar-list a {
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.avatar-list a {
|
||||
transition: transform 180ms ease-in-out;
|
||||
}
|
||||
.avatar-list a {
|
||||
transition: transform 180ms ease-in-out;
|
||||
}
|
||||
|
||||
.avatar-list img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 0 0.05em #fff, 0 0 0 0.08em rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.avatar-list img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 0 0.05em #fff, 0 0 0 0.08em rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.avatar-list a:focus {
|
||||
outline: 2px solid transparent;
|
||||
/* Double-layer trick to work for dark and light backgrounds */
|
||||
box-shadow: 0 0 0 0.08em var(--theme-accent), 0 0 0 0.12em white;
|
||||
}
|
||||
.avatar-list a:focus {
|
||||
outline: 2px solid transparent;
|
||||
/* Double-layer trick to work for dark and light backgrounds */
|
||||
box-shadow: 0 0 0 0.08em var(--theme-accent), 0 0 0 0.12em white;
|
||||
}
|
||||
|
||||
.contributors {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.contributors {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.contributors > * + * {
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
.contributors > * + * {
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
import AvatarList from './AvatarList.astro';
|
||||
type Props = {
|
||||
path: string;
|
||||
};
|
||||
const { path } = Astro.props as Props;
|
||||
---
|
||||
|
||||
<footer>
|
||||
<AvatarList path={path} />
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
footer {
|
||||
margin-top: auto;
|
||||
padding: 2rem;
|
||||
border-top: 3px solid var(--theme-divider);
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,9 @@
|
||||
---
|
||||
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 -->
|
||||
@@ -48,37 +45,43 @@ const { strudelTheme } = settings;
|
||||
</style>
|
||||
{pwaInfo && <Fragment set:html={pwaInfo.webManifest.linkTag} />}
|
||||
|
||||
<script define:vars={{ settings, strudelTheme }} is:inline>
|
||||
<script>
|
||||
import { settings } from '../repl/themes.mjs';
|
||||
import { settingsMap } from '../settings.mjs';
|
||||
import { listenKeys } from 'nanostores';
|
||||
const themeStyle = document.createElement('style');
|
||||
themeStyle.id = 'strudel-theme';
|
||||
document.head.append(themeStyle);
|
||||
function getTheme(name) {
|
||||
function activateTheme(name) {
|
||||
if (!settings[name]) {
|
||||
console.warn('theme', name, 'has no settings');
|
||||
console.warn('theme', name, 'has no settings.. defaulting to strudelTheme settings');
|
||||
}
|
||||
return {
|
||||
name,
|
||||
settings: settings[name] || settings.strudelTheme,
|
||||
};
|
||||
}
|
||||
function setTheme(name) {
|
||||
const { settings } = getTheme(name);
|
||||
const themeSettings = settings[name] || settings.strudelTheme;
|
||||
// set css variables
|
||||
themeStyle.innerHTML = `:root {
|
||||
${Object.entries(settings)
|
||||
${Object.entries(themeSettings)
|
||||
// important to override fallback
|
||||
.map(([key, value]) => `--${key}: ${value} !important;`)
|
||||
.join('\n')}
|
||||
}`;
|
||||
// tailwind dark mode
|
||||
if (settings.light) {
|
||||
if (themeSettings.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));
|
||||
|
||||
activateTheme(settingsMap.get().theme);
|
||||
listenKeys(settingsMap, ['theme'], ({ theme }) => activateTheme(theme));
|
||||
|
||||
// https://medium.com/quick-code/100vh-problem-with-ios-safari-92ab23c852a8
|
||||
const appHeight = () => {
|
||||
const doc = document.documentElement;
|
||||
doc.style.setProperty('--app-height', `${window.innerHeight - 1}px`);
|
||||
};
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('resize', appHeight);
|
||||
appHeight();
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -19,7 +19,10 @@ 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-lineHighlight text-foreground" 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> -->
|
||||
@@ -36,7 +39,7 @@ const sidebar = SIDEBAR[langCode];
|
||||
</div>
|
||||
{/* KNOWN_LANGUAGE_CODES.length > 1 && <LanguageSelect lang={lang} client:idle /> */}
|
||||
<div class="search-item h-10">
|
||||
<!-- <Search client:idle /> -->
|
||||
<!-- <Search client:idle /> -->
|
||||
</div>
|
||||
<a href="./" class="hidden md:flex cursor-pointer items-center space-x-1"
|
||||
><CommandLineIcon className="w-5 h-5" /><span>go to REPL</span>
|
||||
|
||||
@@ -23,7 +23,7 @@ const sidebar = SIDEBAR[langCode];
|
||||
<h2>{header}</h2>
|
||||
<ul>
|
||||
{children.map((child) => {
|
||||
const url = '.' + Astro.site?.pathname + child.link;
|
||||
const url = Astro.site?.pathname + child.link;
|
||||
return (
|
||||
<li class="">
|
||||
<a
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import TableOfContents from './TableOfContents';
|
||||
import MoreMenu from './MoreMenu.astro';
|
||||
import type { MarkdownHeading } from 'astro';
|
||||
import AvatarList from '../Footer/AvatarList.astro';
|
||||
|
||||
type Props = {
|
||||
headings: MarkdownHeading[];
|
||||
@@ -17,4 +18,5 @@ currentPage = currentPage.endsWith('/') ? currentPage.slice(0, -1) : currentPage
|
||||
<nav aria-labelledby="grid-right" class="w-64 text-foreground">
|
||||
<TableOfContents client:media="(min-width: 50em)" headings={headings} currentPage={currentPage} />
|
||||
<MoreMenu editHref={githubEditUrl} />
|
||||
<!-- <AvatarList path={currentPage} /> -->
|
||||
</nav>
|
||||
|
||||
@@ -33,9 +33,9 @@ export const COMMUNITY_INVITE_URL = `https://discord.com/invite/HGEdXmRkzT`;
|
||||
|
||||
// See "Algolia" section of the README for more information.
|
||||
export const ALGOLIA = {
|
||||
indexName: 'XXXXXXXXXX',
|
||||
appId: 'XXXXXXXXXX',
|
||||
apiKey: 'XXXXXXXXXX',
|
||||
indexName: 'strudel-tidalcycles',
|
||||
appId: 'SAZ71S8CLS',
|
||||
apiKey: 'd5044f9d21b80e7721e5b0067a8730b1',
|
||||
};
|
||||
|
||||
export type Sidebar = Record<(typeof KNOWN_LANGUAGE_CODES)[number], Record<string, { text: string; link: string }[]>>;
|
||||
@@ -73,9 +73,9 @@ export const SIDEBAR: Sidebar = {
|
||||
],
|
||||
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' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -4,8 +4,7 @@ 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';
|
||||
import { useSettings } from '../settings.mjs';
|
||||
|
||||
let modules;
|
||||
if (typeof window !== 'undefined') {
|
||||
@@ -29,6 +28,7 @@ if (typeof window !== 'undefined') {
|
||||
|
||||
export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) {
|
||||
const [Repl, setRepl] = useState();
|
||||
const { theme } = useSettings();
|
||||
useEffect(() => {
|
||||
// we have to load this package on the client
|
||||
// because codemirror throws an error on the server
|
||||
@@ -36,7 +36,6 @@ 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
|
||||
@@ -46,7 +45,6 @@ export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) {
|
||||
punchcard={punchcard}
|
||||
canvasHeight={canvasHeight}
|
||||
theme={themes[theme]}
|
||||
highlightColor={settings[theme]?.foreground}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -7,7 +7,6 @@ import LeftSidebar from '../components/LeftSidebar/LeftSidebar.astro';
|
||||
import RightSidebar from '../components/RightSidebar/RightSidebar.astro';
|
||||
import * as CONFIG from '../config';
|
||||
import type { MarkdownHeading } from 'astro';
|
||||
import Footer from '../components/Footer/Footer.astro';
|
||||
import '../docs/docs.css';
|
||||
|
||||
type Props = {
|
||||
@@ -31,7 +30,7 @@ const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
|
||||
</title>
|
||||
</head>
|
||||
|
||||
<body class="h-screen text-gray-50 bg-background">
|
||||
<body class="h-app-height 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} />
|
||||
@@ -49,7 +48,6 @@ const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
|
||||
</aside>
|
||||
</div>
|
||||
</main>
|
||||
<!-- <Footer path={currentFile} /> -->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Repl } from '../repl/Repl.jsx';
|
||||
<HeadCommon />
|
||||
<title>Strudel REPL</title>
|
||||
</head>
|
||||
<body class="bg-background">
|
||||
<body class="h-app-height bg-background">
|
||||
<Repl client:only="react" />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -31,3 +31,5 @@ import { JsDoc } from '../../docs/JsDoc';
|
||||
## stut
|
||||
|
||||
<JsDoc client:idle name="stut" h={0} />
|
||||
|
||||
There are also [Tonal Modifiers](/learn/tonal).
|
||||
|
||||
@@ -14,8 +14,8 @@ 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")`.
|
||||
- We have a word `freq` which is followed by some brackets `()` with some words/letters/numbers inside, surrounded by quotes `"220 275 330 440"` (corresponding to the pitches a3, c#4, e4, a4).
|
||||
- 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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -146,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).
|
||||
|
||||
@@ -64,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.
|
||||
|
||||
@@ -22,12 +22,35 @@ If no outputName is given, it uses the first midi output it finds.
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`stack("<C^7 A7 Dm7 G7>".voicings('lefthand'), "<C3 A2 D3 G2>")
|
||||
tune={`stack("<C^7 A7 Dm7 G7>".voicings('lefthand'), "<C3 A2 D3 G2>").note()
|
||||
.midi()`}
|
||||
/>
|
||||
|
||||
In the console, you will see a log of the available MIDI devices as soon as you run the code, e.g. `Midi connected! Using "Midi Through Port-0".`
|
||||
|
||||
## midichan(number)
|
||||
|
||||
Selects the MIDI channel to use. If not used, `.midi` will use channel 1 by default.
|
||||
|
||||
## ccn && ccv
|
||||
|
||||
- `ccn` sets the cc number. Depends on your synths midi mapping
|
||||
- `ccv` sets the cc value. normalized from 0 to 1.
|
||||
|
||||
<MiniRepl client:idle tune={`note("c a f e").ccn(74).ccv(sine.slow(4)).midi()`} />
|
||||
|
||||
In the above snippet, `ccn` is set to 74, which is the filter cutoff for many synths. `ccv` is controlled by a saw pattern.
|
||||
Having everything in one pattern, the `ccv` pattern will be aligned to the note pattern, because the structure comes from the left by default.
|
||||
But you can also control cc messages separately like this:
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`stack(
|
||||
note("c a f e"),
|
||||
ccv(sine.segment(16).slow(4)).ccn(74)
|
||||
).midi()`}
|
||||
/>
|
||||
|
||||
# SuperDirt API
|
||||
|
||||
In mainline tidal, the actual sound is generated via [SuperDirt](https://github.com/musikinformatik/SuperDirt/), which runs inside SuperCollider.
|
||||
@@ -40,8 +63,8 @@ Getting [SuperDirt](https://github.com/musikinformatik/SuperDirt/) to work with
|
||||
1. install SuperCollider + sc3 plugins, see [Tidal Docs](https://tidalcycles.org/docs/) (Install Tidal) for more info.
|
||||
2. install [node.js](https://nodejs.org/en/)
|
||||
3. download [Strudel Repo](https://github.com/tidalcycles/strudel/) (or git clone, if you have git installed)
|
||||
4. run `npm i` in the strudel directory
|
||||
5. run `npm run osc` to start the osc server, which forwards OSC messages from Strudel REPL to SuperCollider
|
||||
4. run `pnpm i` in the strudel directory
|
||||
5. run `pnpm run osc` to start the osc server, which forwards OSC messages from Strudel REPL to SuperCollider
|
||||
|
||||
Now you're all set!
|
||||
|
||||
@@ -69,3 +92,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)?
|
||||
|
||||
@@ -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`):
|
||||
|
||||
@@ -159,8 +159,6 @@ Using "!" we can repeat without speeding up:
|
||||
|
||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>")`} punchcard />
|
||||
|
||||
In essence, the `x!n` is like a shortcut for `[x*n]@n`.
|
||||
|
||||
## Mini-notation review
|
||||
|
||||
To recap what we've learned so far, compare the following patterns:
|
||||
@@ -235,3 +233,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?
|
||||
|
||||
@@ -7,3 +7,79 @@ layout: ../../layouts/MainLayout.astro
|
||||
|
||||
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 (<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?
|
||||
|
||||
@@ -322,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.
|
||||
|
||||
@@ -106,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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -9,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`
|
||||
@@ -27,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).
|
||||
|
||||
@@ -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)?
|
||||
|
||||
@@ -5,75 +5,58 @@ 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
|
||||
|
||||
https://pnpm.io/workspaces
|
||||
## Tools
|
||||
|
||||
- [pnpm workspaces](https://pnpm.io/)
|
||||
- Publishing packages is done with [lerna](https://lerna.js.org/).
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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)?
|
||||
|
||||
+227
-167
@@ -6,15 +6,13 @@ import { nanoid } from 'nanoid';
|
||||
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { loadedSamples } from './Repl';
|
||||
import { Reference } from './Reference';
|
||||
import { themes, themeColors } from './themes.mjs';
|
||||
import { getAutomatedWamParams, getWamInstances } from '@strudel.cycles/wam';
|
||||
import { themes } from './themes.mjs';
|
||||
import { useSettings, settingsMap, setActiveFooter, defaultSettings } from '../settings.mjs';
|
||||
|
||||
export function Footer({ context }) {
|
||||
// const [activeFooter, setActiveFooter] = useState('console');
|
||||
// const { activeFooter, setActiveFooter, isZen } = useContext?.(ReplContext);
|
||||
const { activeFooter, setActiveFooter, isZen, theme, setTheme } = context;
|
||||
const footerContent = useRef();
|
||||
const [log, setLog] = useState([]);
|
||||
const { activeFooter, isZen } = useSettings();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (footerContent.current && activeFooter === 'console') {
|
||||
@@ -57,8 +55,8 @@ export function Footer({ context }) {
|
||||
<div
|
||||
onClick={() => setActiveFooter(name)}
|
||||
className={cx(
|
||||
'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',
|
||||
'h-8 px-2 text-foreground cursor-pointer hover:opacity-50 flex items-center space-x-1 border-b',
|
||||
activeFooter === name ? 'border-foreground' : 'border-transparent',
|
||||
)}
|
||||
>
|
||||
{label || name}
|
||||
@@ -69,7 +67,6 @@ export function Footer({ context }) {
|
||||
if (isZen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<footer className="bg-lineHighlight z-[20]">
|
||||
<div className="flex justify-between px-2">
|
||||
@@ -78,8 +75,7 @@ export function Footer({ context }) {
|
||||
<FooterTab name="samples" />
|
||||
<FooterTab name="console" />
|
||||
<FooterTab name="reference" />
|
||||
<FooterTab name="theme" />
|
||||
<FooterTab name="wams" />
|
||||
<FooterTab name="settings" />
|
||||
</div>
|
||||
{activeFooter !== '' && (
|
||||
<button onClick={() => setActiveFooter('')} className="text-foreground" aria-label="Close Panel">
|
||||
@@ -92,126 +88,11 @@ export function Footer({ context }) {
|
||||
className="text-white font-mono text-sm h-[360px] flex-none overflow-auto max-w-full relative"
|
||||
ref={footerContent}
|
||||
>
|
||||
{activeFooter === 'intro' && (
|
||||
<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>
|
||||
<p>
|
||||
You have found <span className="underline">strudel</span>, a new live coding platform to write dynamic
|
||||
music pieces in the browser! It is free and open-source and made for beginners and experts alike. To get
|
||||
started:
|
||||
<br />
|
||||
<br />
|
||||
<span className="underline">1. hit play</span> - <span className="underline">2. change something</span>{' '}
|
||||
- <span className="underline">3. hit update</span>
|
||||
<br />
|
||||
If you don't like what you hear, try <span className="underline">shuffle</span>!
|
||||
</p>
|
||||
<p>
|
||||
To learn more about what this all means, check out the{' '}
|
||||
<a href="./learn/getting-started" target="_blank">
|
||||
interactive tutorial
|
||||
</a>
|
||||
. Also feel free to join the{' '}
|
||||
<a href="https://discord.com/invite/HGEdXmRkzT" target="_blank">
|
||||
tidalcycles discord channel
|
||||
</a>{' '}
|
||||
to ask any questions, give feedback or just say hello.
|
||||
</p>
|
||||
<h3>about</h3>
|
||||
<p>
|
||||
strudel is a JavaScript version of{' '}
|
||||
<a href="tidalcycles.org/" target="_blank">
|
||||
tidalcycles
|
||||
</a>
|
||||
, which is a popular live coding language for music, written in Haskell. You can find the source code at{' '}
|
||||
<a href="https://github.com/tidalcycles/strudel" target="_blank">
|
||||
github
|
||||
</a>
|
||||
. Please consider to{' '}
|
||||
<a href="https://opencollective.com/tidalcycles" target="_blank">
|
||||
support this project
|
||||
</a>{' '}
|
||||
to ensure ongoing development 💖
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{activeFooter === 'console' && (
|
||||
<div className="break-all px-4 dark:text-white text-stone-900">
|
||||
{log.map((l, i) => {
|
||||
const message = linkify(l.message);
|
||||
return (
|
||||
<div
|
||||
key={l.id}
|
||||
className={cx(l.type === 'error' && 'text-red-500', l.type === 'highlight' && 'text-highlight')}
|
||||
>
|
||||
<span dangerouslySetInnerHTML={{ __html: message }} />
|
||||
{l.count ? ` (${l.count})` : ''}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{activeFooter === 'samples' && (
|
||||
<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={() => {}}>
|
||||
{' '}
|
||||
{name}(
|
||||
{Array.isArray(samples)
|
||||
? samples.length
|
||||
: typeof samples === 'object'
|
||||
? Object.values(samples).length
|
||||
: 1}
|
||||
){' '}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{activeFooter === 'intro' && <WelcomeTab />}
|
||||
{activeFooter === 'console' && <ConsoleTab log={log} />}
|
||||
{activeFooter === 'samples' && <SamplesTab />}
|
||||
{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>
|
||||
)}
|
||||
{activeFooter === 'wams' && (
|
||||
<div className="break-normal w-full px-4 dark:text-white text-stone-900">
|
||||
<span>{Object.keys(getWamInstances()).length} loaded:</span>
|
||||
<select onChange={(e) => showWAM(e)}>
|
||||
<option key="--">--</option>
|
||||
{Object.keys(getWamInstances()).map((w) => (
|
||||
<option key={w}>{w}</option>
|
||||
))}
|
||||
</select>
|
||||
<div id="wam-gui"></div>
|
||||
<button onClick={() => wamPreset()}>preset</button>
|
||||
</div>
|
||||
)}
|
||||
{activeFooter === 'settings' && <SettingsTab scheduler={context.scheduler} />}
|
||||
</div>
|
||||
)}
|
||||
</footer>
|
||||
@@ -243,49 +124,228 @@ function linkify(inputText) {
|
||||
return replacedText;
|
||||
}
|
||||
|
||||
let wamGui = null;
|
||||
let displayedWam = null;
|
||||
function WelcomeTab() {
|
||||
return (
|
||||
<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>
|
||||
<p>
|
||||
You have found <span className="underline">strudel</span>, a new live coding platform to write dynamic music
|
||||
pieces in the browser! It is free and open-source and made for beginners and experts alike. To get started:
|
||||
<br />
|
||||
<br />
|
||||
<span className="underline">1. hit play</span> - <span className="underline">2. change something</span> -{' '}
|
||||
<span className="underline">3. hit update</span>
|
||||
<br />
|
||||
If you don't like what you hear, try <span className="underline">shuffle</span>!
|
||||
</p>
|
||||
<p>
|
||||
To learn more about what this all means, check out the{' '}
|
||||
<a href="./learn/getting-started" target="_blank">
|
||||
interactive tutorial
|
||||
</a>
|
||||
. Also feel free to join the{' '}
|
||||
<a href="https://discord.com/invite/HGEdXmRkzT" target="_blank">
|
||||
tidalcycles discord channel
|
||||
</a>{' '}
|
||||
to ask any questions, give feedback or just say hello.
|
||||
</p>
|
||||
<h3>about</h3>
|
||||
<p>
|
||||
strudel is a JavaScript version of{' '}
|
||||
<a href="tidalcycles.org/" target="_blank">
|
||||
tidalcycles
|
||||
</a>
|
||||
, which is a popular live coding language for music, written in Haskell. You can find the source code at{' '}
|
||||
<a href="https://github.com/tidalcycles/strudel" target="_blank">
|
||||
github
|
||||
</a>
|
||||
. Please consider to{' '}
|
||||
<a href="https://opencollective.com/tidalcycles" target="_blank">
|
||||
support this project
|
||||
</a>{' '}
|
||||
to ensure ongoing development 💖
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const showWAM = async (e) => {
|
||||
if (displayedWam !== null && wamGui !== null) {
|
||||
displayedWam.destroyGui(wamGui);
|
||||
}
|
||||
function ConsoleTab({ log }) {
|
||||
return (
|
||||
<div id="console-tab" className="break-all px-4 dark:text-white text-stone-900">
|
||||
<pre>{`███████╗████████╗██████╗ ██╗ ██╗██████╗ ███████╗██╗
|
||||
██╔════╝╚══██╔══╝██╔══██╗██║ ██║██╔══██╗██╔════╝██║
|
||||
███████╗ ██║ ██████╔╝██║ ██║██║ ██║█████╗ ██║
|
||||
╚════██║ ██║ ██╔══██╗██║ ██║██║ ██║██╔══╝ ██║
|
||||
███████║ ██║ ██║ ██║╚██████╔╝██████╔╝███████╗███████╗
|
||||
╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝`}</pre>
|
||||
{log.map((l, i) => {
|
||||
const message = linkify(l.message);
|
||||
return (
|
||||
<div key={l.id} className={cx(l.type === 'error' && 'text-red-500', l.type === 'highlight' && 'underline')}>
|
||||
<span dangerouslySetInnerHTML={{ __html: message }} />
|
||||
{l.count ? ` (${l.count})` : ''}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const wam = getWamInstances()[e.target.value];
|
||||
displayedWam = e.target.value;
|
||||
function SamplesTab() {
|
||||
return (
|
||||
<div id="samples-tab" 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:opacity-50" onClick={() => {}}>
|
||||
{' '}
|
||||
{name}(
|
||||
{Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1}){' '}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const gui = await wam.createGui();
|
||||
const guiDiv = document.getElementById('wam-gui');
|
||||
guiDiv.innerHTML = '';
|
||||
guiDiv.appendChild(gui);
|
||||
function ButtonGroup({ value, onChange, items }) {
|
||||
return (
|
||||
<div className="flex grow border border-foreground rounded-md">
|
||||
{Object.entries(items).map(([key, label], i, arr) => (
|
||||
<button
|
||||
key={key}
|
||||
onClick={() => onChange(key)}
|
||||
className={cx(
|
||||
'p-2 grow',
|
||||
i === 0 && 'rounded-l-md',
|
||||
i === arr.length - 1 && 'rounded-r-md',
|
||||
value === key ? 'bg-background' : 'bg-lineHighlight',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const params = await wam.audioNode.getParameterInfo();
|
||||
function SelectInput({ value, options, onChange }) {
|
||||
return (
|
||||
<select
|
||||
className="p-2 bg-background rounded-md text-foreground"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
>
|
||||
{Object.entries(options).map(([k, label]) => (
|
||||
<option key={k} className="bg-background" value={k}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
for (let id of Object.keys(params)) {
|
||||
const param = params[id];
|
||||
const input = document.createElement('div');
|
||||
input.innerHTML = `<label>${id}</label>: type ${param.type}, min ${param.minValue}, max ${param.maxValue}`;
|
||||
guiDiv.appendChild(input);
|
||||
}
|
||||
function NumberSlider({ value, onChange, step = 1, ...rest }) {
|
||||
return (
|
||||
<div className="flex space-x-2 gap-1">
|
||||
<input
|
||||
className="p-2 grow"
|
||||
type="range"
|
||||
value={value}
|
||||
step={step}
|
||||
onChange={(e) => onChange(Number(e.target.value))}
|
||||
{...rest}
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
value={value}
|
||||
step={step}
|
||||
className="w-16 bg-background rounded-md"
|
||||
onChange={(e) => onChange(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FormItem({ label, children }) {
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<label>{label}</label>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const themeOptions = Object.fromEntries(Object.keys(themes).map((k) => [k, k]));
|
||||
const fontFamilyOptions = {
|
||||
monospace: 'monospace',
|
||||
BigBlueTerminal: 'BigBlueTerminal',
|
||||
x3270: 'x3270',
|
||||
PressStart: 'PressStart2P',
|
||||
};
|
||||
|
||||
const wamPreset = async () => {
|
||||
const wam = getWamInstances()[displayedWam];
|
||||
|
||||
const params = await wam.audioNode.getParameterInfo();
|
||||
const values = await wam.audioNode.getParameterValues();
|
||||
|
||||
let preset = {}
|
||||
const automatedParams = getAutomatedWamParams()[displayedWam];
|
||||
|
||||
for (let id of Object.keys(params)) {
|
||||
if (automatedParams[id]) {
|
||||
continue
|
||||
}
|
||||
if (values[id].value.toFixed(6) == params[id].defaultValue.toFixed(6)) {
|
||||
continue
|
||||
}
|
||||
preset[id] = values[id].value;
|
||||
}
|
||||
console.log("preset", preset);
|
||||
function SettingsTab({ scheduler }) {
|
||||
const { theme, keybindings, fontSize, fontFamily } = useSettings();
|
||||
return (
|
||||
<div className="text-foreground p-4 space-y-4">
|
||||
{/* <FormItem label="Tempo">
|
||||
<div className="space-x-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
scheduler.setCps(scheduler.cps - 0.1);
|
||||
}}
|
||||
>
|
||||
slower
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
scheduler.setCps(scheduler.cps + 0.1);
|
||||
}}
|
||||
>
|
||||
faster
|
||||
</button>
|
||||
</div>
|
||||
</FormItem> */}
|
||||
<FormItem label="Theme">
|
||||
<SelectInput options={themeOptions} value={theme} onChange={(theme) => settingsMap.setKey('theme', theme)} />
|
||||
</FormItem>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormItem label="Font Family">
|
||||
<SelectInput
|
||||
options={fontFamilyOptions}
|
||||
value={fontFamily}
|
||||
onChange={(fontFamily) => settingsMap.setKey('fontFamily', fontFamily)}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="Font Size">
|
||||
<NumberSlider
|
||||
value={fontSize}
|
||||
onChange={(fontSize) => settingsMap.setKey('fontSize', fontSize)}
|
||||
min={10}
|
||||
max={40}
|
||||
step={2}
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
<FormItem label="Keybindings">
|
||||
<ButtonGroup
|
||||
value={keybindings}
|
||||
onChange={(keybindings) => settingsMap.setKey('keybindings', keybindings)}
|
||||
items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs' }}
|
||||
></ButtonGroup>
|
||||
</FormItem>
|
||||
<FormItem label="Reset Settings">
|
||||
<button
|
||||
className="bg-background p-2 max-w-[300px] rounded-md hover:opacity-50"
|
||||
onClick={() => {
|
||||
if (confirm('Sure?')) {
|
||||
settingsMap.set(defaultSettings);
|
||||
}
|
||||
}}
|
||||
>
|
||||
restore default settings
|
||||
</button>
|
||||
</FormItem>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+14
-11
@@ -6,6 +6,7 @@ import SparklesIcon from '@heroicons/react/20/solid/SparklesIcon';
|
||||
import StopCircleIcon from '@heroicons/react/20/solid/StopCircleIcon';
|
||||
import { cx } from '@strudel.cycles/react';
|
||||
import React, { useContext } from 'react';
|
||||
import { useSettings, setIsZen } from '../settings.mjs';
|
||||
// import { ReplContext } from './Repl';
|
||||
import './Repl.css';
|
||||
|
||||
@@ -21,11 +22,9 @@ export function Header({ context }) {
|
||||
handleUpdate,
|
||||
handleShuffle,
|
||||
handleShare,
|
||||
isZen,
|
||||
setIsZen,
|
||||
} = context;
|
||||
const isEmbedded = embedded || window.location !== window.parent.location;
|
||||
// useContext(ReplContext)
|
||||
const { isZen } = useSettings();
|
||||
|
||||
return (
|
||||
<header
|
||||
@@ -53,7 +52,11 @@ export function Header({ context }) {
|
||||
>
|
||||
<div
|
||||
className={cx('mt-[1px]', started && 'animate-spin', 'cursor-pointer')}
|
||||
onClick={() => !isEmbedded && setIsZen((z) => !z)}
|
||||
onClick={() => {
|
||||
if (!isEmbedded) {
|
||||
setIsZen(!isZen);
|
||||
}
|
||||
}}
|
||||
>
|
||||
🌀
|
||||
</div>
|
||||
@@ -69,7 +72,7 @@ export function Header({ context }) {
|
||||
<button
|
||||
onClick={handleTogglePlay}
|
||||
title={started ? 'stop' : 'play'}
|
||||
className={cx(!isEmbedded ? 'p-2' : 'px-2', 'hover:text-tertiary', !started && 'animate-pulse')}
|
||||
className={cx(!isEmbedded ? 'p-2' : 'px-2', 'hover:opacity-50', !started && 'animate-pulse')}
|
||||
>
|
||||
{!pending ? (
|
||||
<span className={cx('flex items-center space-x-1', isEmbedded ? '' : 'w-16')}>
|
||||
@@ -86,7 +89,7 @@ export function Header({ context }) {
|
||||
className={cx(
|
||||
'flex items-center space-x-1',
|
||||
!isEmbedded ? 'p-2' : 'px-2',
|
||||
!isDirty || !activeCode ? 'opacity-50' : 'hover:text-tertiary',
|
||||
!isDirty || !activeCode ? 'opacity-50' : 'hover:opacity-50',
|
||||
)}
|
||||
>
|
||||
{/* <CommandLineIcon className="w-6 h-6" /> */}
|
||||
@@ -96,7 +99,7 @@ export function Header({ context }) {
|
||||
{!isEmbedded && (
|
||||
<button
|
||||
title="shuffle"
|
||||
className="hover:text-tertiary p-2 flex items-center space-x-1"
|
||||
className="hover:opacity-50 p-2 flex items-center space-x-1"
|
||||
onClick={handleShuffle}
|
||||
>
|
||||
<SparklesIcon className="w-6 h-6" />
|
||||
@@ -107,7 +110,7 @@ export function Header({ context }) {
|
||||
<button
|
||||
title="share"
|
||||
className={cx(
|
||||
'cursor-pointer hover:text-tertiary flex items-center space-x-1',
|
||||
'cursor-pointer hover:opacity-50 flex items-center space-x-1',
|
||||
!isEmbedded ? 'p-2' : 'px-2',
|
||||
)}
|
||||
onClick={handleShare}
|
||||
@@ -120,21 +123,21 @@ export function Header({ context }) {
|
||||
<a
|
||||
title="learn"
|
||||
href="./learn/getting-started"
|
||||
className={cx('hover:text-tertiary flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
|
||||
className={cx('hover:opacity-50 flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
|
||||
>
|
||||
<AcademicCapIcon className="w-6 h-6" />
|
||||
<span>learn</span>
|
||||
</a>
|
||||
)}
|
||||
{/* {isEmbedded && (
|
||||
<button className={cx('hover:text-tertiary px-2')}>
|
||||
<button className={cx('hover:opacity-50 px-2')}>
|
||||
<a href={window.location.href} target="_blank" rel="noopener noreferrer" title="Open in REPL">
|
||||
🚀
|
||||
</a>
|
||||
</button>
|
||||
)}
|
||||
{isEmbedded && (
|
||||
<button className={cx('hover:text-tertiary px-2')}>
|
||||
<button className={cx('hover:opacity-50 px-2')}>
|
||||
<a
|
||||
onClick={() => {
|
||||
window.location.href = initialUrl;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#code .cm-scroller {
|
||||
padding-bottom: 50vh;
|
||||
font-family: inherit;
|
||||
}
|
||||
#code .cm-line > * {
|
||||
background: var(--lineBackground);
|
||||
|
||||
+17
-23
@@ -23,9 +23,9 @@ 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';
|
||||
import { settingsMap, useSettings, setLatestCode } from '../settings.mjs';
|
||||
|
||||
const initialTheme = localStorage.getItem('strudel-theme') || 'strudelTheme';
|
||||
const { latestCode } = settingsMap.get();
|
||||
|
||||
initAudioOnFirstClick();
|
||||
|
||||
@@ -46,7 +46,6 @@ const modules = [
|
||||
import('@strudel.cycles/serial'),
|
||||
import('@strudel.cycles/soundfonts'),
|
||||
import('@strudel.cycles/csound'),
|
||||
import('@strudel.cycles/wam'),
|
||||
];
|
||||
|
||||
evalScope(
|
||||
@@ -114,25 +113,25 @@ 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);
|
||||
const [pending, setPending] = useState(false);
|
||||
|
||||
const { theme, keybindings, fontSize, fontFamily } = useSettings();
|
||||
|
||||
const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } =
|
||||
useStrudel({
|
||||
initialCode: '// LOADING',
|
||||
defaultOutput: webaudioOutput,
|
||||
getTime,
|
||||
autolink: true,
|
||||
beforeEval: () => {
|
||||
cleanupUi();
|
||||
cleanupDraw();
|
||||
setPending(true);
|
||||
},
|
||||
afterEval: () => {
|
||||
afterEval: ({ code }) => {
|
||||
setPending(false);
|
||||
setLatestCode(code);
|
||||
window.location.hash = '#' + encodeURIComponent(btoa(code));
|
||||
},
|
||||
onToggle: (play) => !play && cleanupDraw(false),
|
||||
drawContext,
|
||||
@@ -141,16 +140,13 @@ export function Repl({ embedded = false }) {
|
||||
// init code
|
||||
useEffect(() => {
|
||||
initCode().then((decoded) => {
|
||||
if (!decoded) {
|
||||
setActiveFooter('intro'); // TODO: get rid
|
||||
}
|
||||
logger(
|
||||
`Welcome to Strudel! ${
|
||||
decoded ? `I have loaded the code from the URL.` : `A random code snippet named "${name}" has been loaded!`
|
||||
} Press play or hit ctrl+enter to run it!`,
|
||||
'highlight',
|
||||
);
|
||||
setCode(decoded || randomTune);
|
||||
setCode(decoded || latestCode || randomTune);
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -173,15 +169,12 @@ 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,
|
||||
});
|
||||
|
||||
//
|
||||
@@ -249,23 +242,18 @@ export function Repl({ embedded = false }) {
|
||||
}
|
||||
};
|
||||
const context = {
|
||||
scheduler,
|
||||
embedded,
|
||||
started,
|
||||
pending,
|
||||
isDirty,
|
||||
lastShared,
|
||||
activeCode,
|
||||
activeFooter,
|
||||
setActiveFooter,
|
||||
handleChangeCode,
|
||||
handleTogglePlay,
|
||||
handleUpdate,
|
||||
handleShuffle,
|
||||
handleShare,
|
||||
isZen,
|
||||
setIsZen,
|
||||
theme,
|
||||
setTheme,
|
||||
};
|
||||
return (
|
||||
// bg-gradient-to-t from-blue-900 to-slate-900
|
||||
@@ -273,7 +261,7 @@ export function Repl({ embedded = false }) {
|
||||
<ReplContext.Provider value={context}>
|
||||
<div
|
||||
className={cx(
|
||||
'h-screen flex flex-col',
|
||||
'h-full flex flex-col',
|
||||
// 'bg-gradient-to-t from-green-900 to-slate-900', //
|
||||
)}
|
||||
>
|
||||
@@ -282,8 +270,14 @@ export function Repl({ embedded = false }) {
|
||||
<CodeMirror
|
||||
theme={themes[theme] || themes.strudelTheme}
|
||||
value={code}
|
||||
keybindings={keybindings}
|
||||
fontSize={fontSize}
|
||||
fontFamily={fontFamily}
|
||||
onChange={handleChangeCode}
|
||||
onViewChanged={setView}
|
||||
onViewChanged={(v) => {
|
||||
setView(v);
|
||||
// window.editorView = v;
|
||||
}}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
/>
|
||||
</section>
|
||||
|
||||
@@ -31,9 +31,19 @@ import {
|
||||
} from '@uiw/codemirror-themes-all';
|
||||
|
||||
import strudelTheme from '@strudel.cycles/react/src/themes/strudel-theme';
|
||||
import bluescreen, { settings as bluescreenSettings } from '@strudel.cycles/react/src/themes/bluescreen';
|
||||
import blackscreen, { settings as blackscreenSettings } from '@strudel.cycles/react/src/themes/blackscreen';
|
||||
import whitescreen, { settings as whitescreenSettings } from '@strudel.cycles/react/src/themes/whitescreen';
|
||||
import algoboy, { settings as algoboySettings } from '@strudel.cycles/react/src/themes/algoboy';
|
||||
import terminal, { settings as terminalSettings } from '@strudel.cycles/react/src/themes/terminal';
|
||||
|
||||
export const themes = {
|
||||
strudelTheme,
|
||||
bluescreen,
|
||||
blackscreen,
|
||||
whitescreen,
|
||||
algoboy,
|
||||
terminal,
|
||||
abcdef,
|
||||
androidstudio,
|
||||
atomone,
|
||||
@@ -82,6 +92,11 @@ export const settings = {
|
||||
// gutterForeground: '#8a919966',
|
||||
gutterForeground: '#8a919966',
|
||||
},
|
||||
bluescreen: bluescreenSettings,
|
||||
blackscreen: blackscreenSettings,
|
||||
whitescreen: whitescreenSettings,
|
||||
algoboy: algoboySettings,
|
||||
terminal: terminalSettings,
|
||||
abcdef: {
|
||||
background: '#0f0f0f',
|
||||
lineBackground: '#0f0f0f50',
|
||||
@@ -395,7 +410,6 @@ export const settings = {
|
||||
gutterBackground: '#1e1e1e',
|
||||
gutterForeground: '#838383',
|
||||
gutterActiveForeground: '#fff',
|
||||
fontFamily: 'Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace',
|
||||
},
|
||||
xcodeLight: {
|
||||
light: true,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { persistentMap } from '@nanostores/persistent';
|
||||
import { useStore } from '@nanostores/react';
|
||||
|
||||
export const defaultSettings = {
|
||||
activeFooter: 'intro',
|
||||
keybindings: 'codemirror',
|
||||
theme: 'strudelTheme',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 18,
|
||||
latestCode: '',
|
||||
isZen: false,
|
||||
};
|
||||
|
||||
export const settingsMap = persistentMap('strudel-settings', defaultSettings);
|
||||
|
||||
export function useSettings() {
|
||||
const state = useStore(settingsMap);
|
||||
return {
|
||||
...state,
|
||||
isZen: [true, 'true'].includes(state.isZen) ? true : false,
|
||||
fontSize: Number(state.fontSize),
|
||||
};
|
||||
}
|
||||
|
||||
export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
|
||||
|
||||
export const setLatestCode = (code) => settingsMap.setKey('latestCode', code);
|
||||
export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);
|
||||
@@ -1,3 +1,16 @@
|
||||
@font-face {
|
||||
font-family: 'PressStart';
|
||||
src: url('/fonts/PressStart2P/PressStart2P-Regular.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'BigBlueTerminal';
|
||||
src: url('/fonts/BigBlueTerminal/BigBlue_TerminalPlus.TTF');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'x3270';
|
||||
src: url('/fonts/3270/3270-Regular.ttf');
|
||||
}
|
||||
|
||||
.cm-gutters {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -5,3 +18,11 @@
|
||||
.prose > h1:not(:first-child) {
|
||||
margin-top: 30px;
|
||||
}
|
||||
:root {
|
||||
--app-height: 100vh;
|
||||
}
|
||||
|
||||
#console-tab,
|
||||
#samples-tab {
|
||||
font-family: BigBlueTerminal, monospace;
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
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;
|
||||
@@ -11,8 +11,6 @@ module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
tertiary: '#82aaff',
|
||||
highlight: '#bb8800',
|
||||
// codemirror-theme settings
|
||||
background: 'var(--background)',
|
||||
lineBackground: 'var(--lineBackground)',
|
||||
@@ -25,6 +23,9 @@ module.exports = {
|
||||
gutterBorder: 'var(--gutterBorder)',
|
||||
lineHighlight: 'var(--lineHighlight)',
|
||||
},
|
||||
spacing: {
|
||||
'app-height': 'var(--app-height)',
|
||||
},
|
||||
typography(theme) {
|
||||
return {
|
||||
DEFAULT: {
|
||||
@@ -41,5 +42,5 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography')],
|
||||
plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user