Merge pull request #121 from tidalcycles/webdirt

Webdirt
This commit is contained in:
Felix Roos 2022-06-02 00:20:45 +02:00 committed by GitHub
commit e335b07efe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 453 additions and 119 deletions

161
doc.json
View File

@ -1354,7 +1354,7 @@
"meta": {
"range": [
1178,
31915
31622
],
"filename": "pattern.mjs",
"lineno": 17,
@ -2876,61 +2876,11 @@
"___id": "T000002R000651",
"___s": true
},
{
"comment": "/**\n *\n * @param {...any} funcs\n * @returns Pattern\n * @example\n * \"<[0 1 2 0]!2 [2 3 4 ~]!2 [[4 5] [4 3] 2 [0 ~]]!2 [0 -3 0 ~]!2>\"\n * .layer(\n * x=>x,\n * x=>x.add(7).late(2),\n * x=>x.add(14).late(4),\n * x=>x.add(21).late(6),\n * )\n * .slow(3)\n * .scale('C2 major')\n * .tone((await piano()).toDestination())\n */",
"meta": {
"range": [
21980,
22055
],
"filename": "pattern.mjs",
"lineno": 700,
"columnno": 2,
"path": "/home/felix/projects/strudel/packages/core",
"code": {
"id": "astnode100007825",
"name": "Pattern#layer",
"type": "MethodDefinition",
"paramnames": [
"funcs"
]
},
"vars": {
"": null
}
},
"params": [
{
"type": {
"names": [
"any"
]
},
"variable": true,
"name": "funcs"
}
],
"returns": [
{
"description": "<p>Pattern</p>"
}
],
"examples": [
"\"<[0 1 2 0]!2 [2 3 4 ~]!2 [[4 5] [4 3] 2 [0 ~]]!2 [0 -3 0 ~]!2>\"\n.layer(\n x=>x,\n x=>x.add(7).late(2),\n x=>x.add(14).late(4),\n x=>x.add(21).late(6),\n)\n.slow(3)\n.scale('C2 major')\n.tone((await piano()).toDestination())"
],
"name": "layer",
"longname": "Pattern#layer",
"kind": "function",
"memberof": "Pattern",
"scope": "instance",
"___id": "T000002R000683",
"___s": true
},
{
"comment": "/**\n * Speed up a pattern by the given factor.\n *\n * @name fast\n * @memberof Pattern\n * @param {number | Pattern} factor speed up factor\n * @returns Pattern\n * @example\n * seq(e5, b4, d5, c5).fast(2)\n */",
"meta": {
"filename": "pattern.mjs",
"lineno": 750,
"lineno": 735,
"columnno": 2,
"path": "/home/felix/projects/strudel/packages/core",
"code": {}
@ -2968,7 +2918,7 @@
"comment": "/**\n * Slow down a pattern over the given number of cycles.\n *\n * @name slow\n * @memberof Pattern\n * @param {number | Pattern} factor slow down factor\n * @returns Pattern\n * @example\n * seq(e5, b4, d5, c5).slow(2)\n */",
"meta": {
"filename": "pattern.mjs",
"lineno": 765,
"lineno": 750,
"columnno": 2,
"path": "/home/felix/projects/strudel/packages/core",
"code": {}
@ -3006,11 +2956,11 @@
"comment": "/**\n * Returns a new pattern where every other cycle is played once, twice as\n * fast, and offset in time by one quarter of a cycle. Creates a kind of\n * breakbeat feel.\n * @returns Pattern\n */",
"meta": {
"range": [
28139,
28235
27788,
27884
],
"filename": "pattern.mjs",
"lineno": 919,
"lineno": 904,
"columnno": 2,
"path": "/home/felix/projects/strudel/packages/core",
"code": {
@ -3042,11 +2992,11 @@
"comment": "/** A discrete value that repeats once per cycle:\n *\n * @param {any} value - The value to repeat\n * @returns {Pattern}\n * @example\n * pure('e4')\n */",
"meta": {
"range": [
35996,
36200
35703,
35907
],
"filename": "pattern.mjs",
"lineno": 1217,
"lineno": 1203,
"columnno": 0,
"path": "/home/felix/projects/strudel/packages/core",
"code": {
@ -3093,11 +3043,11 @@
"comment": "/** The given items are played at the same time at the same length:\n *\n * @param {...any} items - The items to stack\n * @return {Pattern}\n * @example\n * stack(g3, b3, [e4, d4])\n */",
"meta": {
"range": [
36713,
36986
36420,
36693
],
"filename": "pattern.mjs",
"lineno": 1244,
"lineno": 1230,
"columnno": 0,
"path": "/home/felix/projects/strudel/packages/core",
"code": {
@ -3145,11 +3095,11 @@
"comment": "/** Concatenation: combines a list of patterns, switching between them successively, one per cycle:\n *\n * synonyms: {@link cat}\n *\n * @param {...any} items - The items to concatenate\n * @return {Pattern}\n * @example\n * slowcat(e5, b4, [d5, c5])\n *\n */",
"meta": {
"range": [
37240,
38151
36947,
37858
],
"filename": "pattern.mjs",
"lineno": 1261,
"lineno": 1247,
"columnno": 0,
"path": "/home/felix/projects/strudel/packages/core",
"code": {
@ -3197,11 +3147,11 @@
"comment": "/** Concatenation: combines a list of patterns, switching between them successively, one per cycle. Unlike slowcat, this version will skip cycles.\n * @param {...any} items - The items to concatenate\n * @return {Pattern}\n */",
"meta": {
"range": [
38377,
38649
38084,
38356
],
"filename": "pattern.mjs",
"lineno": 1286,
"lineno": 1272,
"columnno": 0,
"path": "/home/felix/projects/strudel/packages/core",
"code": {
@ -3246,11 +3196,11 @@
"comment": "/** Concatenation: as with {@link slowcat}, but squashes a cycle from each pattern into one cycle\n *\n * Synonyms: {@link seq}, {@link sequence}\n *\n * @param {...any} items - The items to concatenate\n * @return {Pattern}\n * @example\n * fastcat(e5, b4, [d5, c5])\n * // sequence(e5, b4, [d5, c5])\n * // seq(e5, b4, [d5, c5])\n */",
"meta": {
"range": [
38977,
39059
38684,
38766
],
"filename": "pattern.mjs",
"lineno": 1307,
"lineno": 1293,
"columnno": 0,
"path": "/home/felix/projects/strudel/packages/core",
"code": {
@ -3298,11 +3248,11 @@
"comment": "/** See {@link slowcat} */",
"meta": {
"range": [
39088,
39147
38795,
38854
],
"filename": "pattern.mjs",
"lineno": 1312,
"lineno": 1298,
"columnno": 0,
"path": "/home/felix/projects/strudel/packages/core",
"code": {
@ -3326,11 +3276,11 @@
"comment": "/** Like {@link fastcat}, but where each step has a temporal weight:\n * @param {...Array} items - The items to concatenate\n * @return {Pattern}\n * @example\n * timeCat([3,e3],[1, g3])\n */",
"meta": {
"range": [
39336,
39695
39043,
39402
],
"filename": "pattern.mjs",
"lineno": 1322,
"lineno": 1308,
"columnno": 0,
"path": "/home/felix/projects/strudel/packages/core",
"code": {
@ -3378,11 +3328,11 @@
"comment": "/** See {@link fastcat} */",
"meta": {
"range": [
39724,
39788
39431,
39495
],
"filename": "pattern.mjs",
"lineno": 1335,
"lineno": 1321,
"columnno": 0,
"path": "/home/felix/projects/strudel/packages/core",
"code": {
@ -3406,11 +3356,11 @@
"comment": "/** See {@link fastcat} */",
"meta": {
"range": [
39817,
39876
39524,
39583
],
"filename": "pattern.mjs",
"lineno": 1340,
"lineno": 1326,
"columnno": 0,
"path": "/home/felix/projects/strudel/packages/core",
"code": {
@ -3643,7 +3593,7 @@
"comment": "/**\n * Change the pitch of each value by the given amount. Expects numbers or note strings as values.\n * The amount can be given as a number of semitones or as a string in interval short notation.\n * If you don't care about enharmonic correctness, just use numbers. Otherwise, pass the interval of\n * the form: ST where S is the degree number and T the type of interval with\n *\n * - M = major\n * - m = minor\n * - P = perfect\n * - A = augmented\n * - d = diminished\n *\n * Examples intervals:\n *\n * - 1P = unison\n * - 3M = major third\n * - 3m = minor third\n * - 4P = perfect fourth\n * - 4A = augmented fourth\n * - 5P = perfect fifth\n * - 5d = diminished fifth\n *\n * @param {string | number} amount Either number of semitones or interval string.\n * @returns Pattern\n * @memberof Pattern\n * @name transpose\n * @example\n * \"c2 c3\".fast(2).transpose(\"<0 -2 5 3>\".slow(2)).transpose(0)\n * @example\n * \"c2 c3\".fast(2).transpose(\"<1P -2M 4P 3m>\".slow(2)).transpose(0)\n */",
"meta": {
"filename": "tonal.mjs",
"lineno": 46,
"lineno": 45,
"columnno": 0,
"path": "/home/felix/projects/strudel/packages/tonal",
"code": {}
@ -3675,14 +3625,14 @@
"scope": "static",
"longname": "Pattern.transpose",
"kind": "member",
"___id": "T000002R003717",
"___id": "T000002R003716",
"___s": true
},
{
"comment": "/**\n * Transposes notes inside the scale by the number of steps.\n * Expected to be called on a Pattern which already has a {@link Pattern#scale}\n *\n * @memberof Pattern\n * @name scaleTranspose\n * @param {offset} offset number of steps inside the scale\n * @returns Pattern\n * @example\n * \"-8 [2,4,6]\"\n * .scale('C4 bebop major')\n * .scaleTranspose(\"<0 -1 -2 -3 -4 -5 -6 -4>\")\n */",
"meta": {
"filename": "tonal.mjs",
"lineno": 99,
"lineno": 98,
"columnno": 0,
"path": "/home/felix/projects/strudel/packages/tonal",
"code": {}
@ -3712,14 +3662,14 @@
"scope": "static",
"longname": "Pattern.scaleTranspose",
"kind": "member",
"___id": "T000002R003721",
"___id": "T000002R003720",
"___s": true
},
{
"comment": "/**\n * Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like {@link Pattern#scaleTranspose}.\n *\n * The scale name has the form \"TO? N\" wher\n *\n * - T = Tonic\n * - O = Octave (optional, defaults to 3)\n * - N = Name of scale, available names can be found [here](https://github.com/tonaljs/tonal/blob/main/packages/scale-type/data.ts).\n *\n * @memberof Pattern\n * @name scale\n * @param {string} scale Name of scale\n * @returns Pattern\n * @example \n * \"0 2 4 6 4 2\"\n * .scale(seq('C2 major', 'C2 minor').slow(2))\n */",
"meta": {
"filename": "tonal.mjs",
"lineno": 125,
"lineno": 124,
"columnno": 0,
"path": "/home/felix/projects/strudel/packages/tonal",
"code": {}
@ -3749,7 +3699,7 @@
"scope": "static",
"longname": "Pattern.scale",
"kind": "member",
"___id": "T000002R003723",
"___id": "T000002R003722",
"___s": true
},
{
@ -3786,7 +3736,33 @@
"scope": "static",
"longname": "Pattern.voicings",
"kind": "member",
"___id": "T000002R003748",
"___id": "T000002R003747",
"___s": true
},
{
"comment": "/**\n *\n * Uses [webdirt](https://github.com/dktr0/WebDirt) as output.\n *\n * <details>\n * <summary>show supported Webdirt controls</summary>\n *\n * - s :: String, -- name of sample bank\n * - n :: Int, -- number of sample within a bank\n * - {@link gain} :: Number, -- clamped from 0 to 2; 1 is default and full-scale\n * - overgain :: Number, -- additional gain added to gain to go past clamp at 2\n * - {@link pan} :: Number, -- range: 0 to 1\n * - nudge :: Number, -- nudge the time of the sample forwards/backwards in seconds\n * - {@link speed} :: Number, -- speed / pitch of the sample\n * - {@link unit} :: String\n * - note :: Number, -- pitch offset in semitones\n * - {@link begin} :: Number, -- cut from sample start, normalized\n * - {@link end} :: Number, -- cut from sample end, normalized\n * - {@link cut} :: Int, -- samples with same cut number will interupt each other\n * - {@link cutoff} :: Number, -- lowpass filter frequency\n * - {@link resonance} :: Number, -- lowpass filter resonance\n * - {@link hcutoff} :: Number, -- highpass filter frequency\n * - {@link hresonance} :: Number, -- highpass filter resonance\n * - {@link bandf} :: Number, -- bandpass filter frequency\n * - {@link bandq} :: Number, -- bandpass filter resonance\n * - {@link vowel} :: String, -- name of vowel ('a' | 'e' | 'i' | 'o' | 'u')\n * - delay :: Number, -- delay wet/dry mix\n * - delaytime :: Number, -- delay time in seconds\n * - delayfeedback :: Number, -- delay feedback\n * - {@link loop} :: Number, -- loop sample n times (relative to sample length)\n * - {@link crush} :: Number, -- bitcrusher (currently not working)\n * - {@link coarse} :: Number, -- coarse effect (currently not working)\n * - {@link shape} :: Number, -- (currently not working)\n\n *\n * </details>\n *\n * @name webdirt\n * @memberof Pattern\n * @returns Pattern\n * @example\n * s(\"bd*2 hh sd hh\").n(\"<0 1>\").webdirt()\n */",
"meta": {
"filename": "webdirt.mjs",
"lineno": 20,
"columnno": 0,
"path": "/home/felix/projects/strudel/packages/webdirt",
"code": {}
},
"description": "<p>Uses <a href=\"https://github.com/dktr0/WebDirt\">webdirt</a> as output.</p>\n<details>\n<summary>show supported Webdirt controls</summary>\n<ul>\n<li>s :: String, -- name of sample bank</li>\n<li>n :: Int, -- number of sample within a bank</li>\n<li>{@link gain} :: Number, -- clamped from 0 to 2; 1 is default and full-scale</li>\n<li>overgain :: Number, -- additional gain added to gain to go past clamp at 2</li>\n<li>{@link pan} :: Number, -- range: 0 to 1</li>\n<li>nudge :: Number, -- nudge the time of the sample forwards/backwards in seconds</li>\n<li>{@link speed} :: Number, -- speed / pitch of the sample</li>\n<li>{@link unit} :: String</li>\n<li>note :: Number, -- pitch offset in semitones</li>\n<li>{@link begin} :: Number, -- cut from sample start, normalized</li>\n<li>{@link end} :: Number, -- cut from sample end, normalized</li>\n<li>{@link cut} :: Int, -- samples with same cut number will interupt each other</li>\n<li>{@link cutoff} :: Number, -- lowpass filter frequency</li>\n<li>{@link resonance} :: Number, -- lowpass filter resonance</li>\n<li>{@link hcutoff} :: Number, -- highpass filter frequency</li>\n<li>{@link hresonance} :: Number, -- highpass filter resonance</li>\n<li>{@link bandf} :: Number, -- bandpass filter frequency</li>\n<li>{@link bandq} :: Number, -- bandpass filter resonance</li>\n<li>{@link vowel} :: String, -- name of vowel ('a' | 'e' | 'i' | 'o' | 'u')</li>\n<li>delay :: Number, -- delay wet/dry mix</li>\n<li>delaytime :: Number, -- delay time in seconds</li>\n<li>delayfeedback :: Number, -- delay feedback</li>\n<li>{@link loop} :: Number, -- loop sample n times (relative to sample length)</li>\n<li>{@link crush} :: Number, -- bitcrusher (currently not working)</li>\n<li>{@link coarse} :: Number, -- coarse effect (currently not working)</li>\n<li>{@link shape} :: Number, -- (currently not working)</li>\n</ul>\n</details>",
"name": "webdirt",
"memberof": "Pattern",
"returns": [
{
"description": "<p>Pattern</p>"
}
],
"examples": [
"s(\"bd*2 hh sd hh\").n(\"<0 1>\").webdirt()"
],
"scope": "static",
"longname": "Pattern.webdirt",
"kind": "member",
"___id": "T000002R004040",
"___s": true
},
{
@ -3862,13 +3838,16 @@
"/home/felix/projects/strudel/packages/webaudio/index.mjs",
"/home/felix/projects/strudel/packages/webaudio/scheduler.mjs",
"/home/felix/projects/strudel/packages/webaudio/webaudio.mjs",
"/home/felix/projects/strudel/packages/webdirt/index.mjs",
"/home/felix/projects/strudel/packages/webdirt/sampler.mjs",
"/home/felix/projects/strudel/packages/webdirt/webdirt.mjs",
"/home/felix/projects/strudel/packages/xen/index.mjs",
"/home/felix/projects/strudel/packages/xen/test/xen.test.mjs",
"/home/felix/projects/strudel/packages/xen/tune.mjs",
"/home/felix/projects/strudel/packages/xen/tunejs.js",
"/home/felix/projects/strudel/packages/xen/xen.mjs"
],
"___id": "T000002R014017",
"___id": "T000002R014063",
"___s": true
}
]

69
package-lock.json generated
View File

@ -2255,6 +2255,10 @@
"resolved": "packages/webaudio",
"link": true
},
"node_modules/@strudel.cycles/webdirt": {
"resolved": "packages/webdirt",
"link": true
},
"node_modules/@strudel.cycles/xen": {
"resolved": "packages/xen",
"link": true
@ -10815,6 +10819,11 @@
"defaults": "^1.0.3"
}
},
"node_modules/WebDirt": {
"version": "1.0.0",
"resolved": "git+ssh://git@github.com/dktr0/WebDirt.git#c3aee2c933c4889ee664a836db8c6436af9e867b",
"license": "ISC"
},
"node_modules/webidl-conversions": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
@ -11271,7 +11280,7 @@
},
"packages/eval": {
"name": "@strudel.cycles/eval",
"version": "0.1.0",
"version": "0.1.1",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.1.0",
@ -11298,22 +11307,22 @@
},
"packages/midi": {
"name": "@strudel.cycles/midi",
"version": "0.1.0",
"version": "0.1.1",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/tone": "^0.1.0",
"@strudel.cycles/tone": "^0.1.1",
"tone": "^14.7.77",
"webmidi": "^2.5.2"
}
},
"packages/mini": {
"name": "@strudel.cycles/mini",
"version": "0.1.0",
"version": "0.1.1",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.1.0",
"@strudel.cycles/eval": "^0.1.0",
"@strudel.cycles/tone": "^0.1.0"
"@strudel.cycles/eval": "^0.1.1",
"@strudel.cycles/tone": "^0.1.1"
}
},
"packages/osc": {
@ -11326,14 +11335,14 @@
},
"packages/react": {
"name": "@strudel.cycles/react",
"version": "0.1.1",
"version": "0.1.2",
"license": "AGPL-3.0-or-later",
"peer": true,
"dependencies": {
"@codemirror/lang-javascript": "^0.19.0",
"@strudel.cycles/core": "*",
"@strudel.cycles/eval": "^0.1.0",
"@strudel.cycles/tone": "^0.1.0",
"@strudel.cycles/eval": "^0.1.1",
"@strudel.cycles/tone": "^0.1.1",
"react-codemirror6": "^1.1.0",
"react-hook-inview": "^4.5.0"
},
@ -11407,7 +11416,7 @@
},
"packages/tonal": {
"name": "@strudel.cycles/tonal",
"version": "0.1.0",
"version": "0.1.1",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.1.0",
@ -11432,7 +11441,7 @@
},
"packages/tone": {
"name": "@strudel.cycles/tone",
"version": "0.1.0",
"version": "0.1.1",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.1.0",
@ -11443,15 +11452,24 @@
},
"packages/webaudio": {
"name": "@strudel.cycles/webaudio",
"version": "0.1.0",
"version": "0.1.1",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.1.0"
}
},
"packages/webdirt": {
"name": "@strudel.cycles/webdirt",
"version": "0.1.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.1.0",
"WebDirt": "github:dktr0/WebDirt"
}
},
"packages/xen": {
"name": "@strudel.cycles/xen",
"version": "0.1.0",
"version": "0.1.1",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@strudel.cycles/core": "^0.1.0"
@ -13281,7 +13299,7 @@
"@strudel.cycles/midi": {
"version": "file:packages/midi",
"requires": {
"@strudel.cycles/tone": "^0.1.0",
"@strudel.cycles/tone": "^0.1.1",
"tone": "^14.7.77",
"webmidi": "^2.5.2"
}
@ -13290,8 +13308,8 @@
"version": "file:packages/mini",
"requires": {
"@strudel.cycles/core": "^0.1.0",
"@strudel.cycles/eval": "^0.1.0",
"@strudel.cycles/tone": "^0.1.0"
"@strudel.cycles/eval": "^0.1.1",
"@strudel.cycles/tone": "^0.1.1"
}
},
"@strudel.cycles/osc": {
@ -13305,8 +13323,8 @@
"requires": {
"@codemirror/lang-javascript": "^0.19.0",
"@strudel.cycles/core": "*",
"@strudel.cycles/eval": "^0.1.0",
"@strudel.cycles/tone": "^0.1.0",
"@strudel.cycles/eval": "^0.1.1",
"@strudel.cycles/tone": "^0.1.1",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
"@vitejs/plugin-react": "^1.3.0",
@ -13401,6 +13419,13 @@
"@strudel.cycles/core": "^0.1.0"
}
},
"@strudel.cycles/webdirt": {
"version": "file:packages/webdirt",
"requires": {
"@strudel.cycles/core": "^0.1.0",
"WebDirt": "github:dktr0/WebDirt"
}
},
"@strudel.cycles/xen": {
"version": "file:packages/xen",
"requires": {
@ -18362,8 +18387,8 @@
"requires": {
"@codemirror/lang-javascript": "^0.19.0",
"@strudel.cycles/core": "*",
"@strudel.cycles/eval": "^0.1.0",
"@strudel.cycles/tone": "^0.1.0",
"@strudel.cycles/eval": "^0.1.1",
"@strudel.cycles/tone": "^0.1.1",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
"@vitejs/plugin-react": "^1.3.0",
@ -20004,6 +20029,10 @@
"defaults": "^1.0.3"
}
},
"WebDirt": {
"version": "git+ssh://git@github.com/dktr0/WebDirt.git#c3aee2c933c4889ee664a836db8c6436af9e867b",
"from": "WebDirt@github:dktr0/WebDirt"
},
"webidl-conversions": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",

File diff suppressed because one or more lines are too long

View File

@ -26,7 +26,7 @@ const ivory = '#abb2bf',
// background = '#292d3e',
background = 'transparent',
tooltipBackground = '#353a42',
selection = 'rgba(128, 203, 196, 0.2)',
selection = 'rgba(128, 203, 196, 0.5)',
cursor = '#ffcc00';
/// The editor theme styles for Material Palenight.
@ -50,7 +50,8 @@ const materialPalenightTheme = EditorView.theme(
},
// done
'&.cm-focused .cm-cursor': {
borderLeftColor: cursor,
backgroundColor: cursor,
width: '3px',
},
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {
@ -80,7 +81,7 @@ const materialPalenightTheme = EditorView.theme(
// done
'.cm-gutters': {
background: '#2C323699',
background: 'transparent',
color: '#676e95',
border: 'none',
},

View File

@ -16,7 +16,7 @@ const ivory = '#abb2bf',
// background = '#292d3e',
background = 'transparent',
tooltipBackground = '#353a42',
selection = 'rgba(128, 203, 196, 0.2)',
selection = 'rgba(128, 203, 196, 0.5)',
cursor = '#ffcc00';
/// The editor theme styles for Material Palenight.
@ -40,7 +40,8 @@ export const materialPalenightTheme = EditorView.theme(
},
// done
'&.cm-focused .cm-cursor': {
borderLeftColor: cursor,
backgroundColor: cursor,
width: '3px',
},
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {
@ -70,7 +71,7 @@ export const materialPalenightTheme = EditorView.theme(
// done
'.cm-gutters': {
background: '#2C323699',
background: 'transparent',
color: '#676e95',
border: 'none',
},

View File

@ -0,0 +1,11 @@
# @strudel.cycles/webdirt
This package adds [webdirt](https://github.com/dktr0/WebDirt) support to strudel!
## Dev Notes
Add default samples to repl:
1. move samples to `repl/public` folder. the samples folder is expected to have subfolders, with samples in it. the subfolders will be the names of the samples.
2. run `./makeSampleMap.sh ../../repl/public/EmuSP12 > ../../repl/public/EmuSP12.json`
3. adapt `loadWebDirt` in App.jsx + MiniRepl.jsx to use the generated json file

View File

@ -0,0 +1,2 @@
export * from './webdirt.mjs';
export * from './sampler.mjs';

View File

@ -0,0 +1,32 @@
#/bin/sh
printf "{\n"
dircount=0
# for d in $searchRoot/*; do
find $1 -mindepth 1 -maxdepth 1 -iname "*" | sort | while read d; do
if [ -d "$d" ]
then
if [ $dircount -ne 0 ]
then
printf ",\n"
fi
(( dircount++ ))
dirname=`basename $d`
printf "\"%s\": [" "$dirname"
search2=$searchRoot/$dirname/*.WAV
filecount=0
find "$d" -iname "*.wav" | sort | while read f; do
# for f in $search2; do
filename=$(printf %q "$f")
basename=${f##*/}
if [[ ${basename:0:1} != "." ]]; then
if [ $filecount -ne 0 ]; then
printf ","
fi
(( filecount++ ))
printf "\"%s/%s\"" "$dirname" "$basename"
fi
done
printf "]"
fi
done
printf "\n}\n"

View File

@ -0,0 +1,28 @@
{
"name": "@strudel.cycles/webdirt",
"version": "0.1.0",
"description": "WebDirt integration for Strudel",
"main": "index.mjs",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"tidalcycles",
"strudel",
"pattern",
"livecoding",
"algorave"
],
"author": "Felix Roos <flix91@gmail.com>",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.1.0",
"WebDirt": "github:dktr0/WebDirt"
}
}

View File

@ -0,0 +1,112 @@
const bufferCache = {}; // string: Promise<ArrayBuffer>
const loadCache = {}; // string: Promise<ArrayBuffer>
export const loadBuffer = (url, ac) => {
if (!loadCache[url]) {
loadCache[url] = fetch(url)
.then((res) => res.arrayBuffer())
.then(async (res) => {
const decoded = await ac.decodeAudioData(res);
bufferCache[url] = decoded;
return decoded;
});
}
return loadCache[url];
};
export const getLoadedBuffer = (url) => {
return bufferCache[url];
};
/* export const playBuffer = (buffer, time = ac.currentTime, destination = ac.destination) => {
const src = ac.createBufferSource();
src.buffer = buffer;
src.connect(destination);
src.start(time);
};
export const playSample = async (url) => playBuffer(await loadBuffer(url)); */
// https://estuary.mcmaster.ca/samples/resources.json
// Array<{ "url":string, "bank": string, "n": number}>
// ritchse/tidal-drum-machines/tree/main/machines/AkaiLinn
const githubCache = {};
let sampleCache = { current: undefined };
export const loadGithubSamples = async (path, nameFn) => {
const storageKey = 'loadGithubSamples ' + path;
const stored = localStorage.getItem(storageKey);
if (stored) {
console.log('[sampler]: loaded sample list from localstorage', path);
githubCache[path] = JSON.parse(stored);
}
if (githubCache[path]) {
sampleCache.current = githubCache[path];
return githubCache[path];
}
console.log('[sampler]: fetching sample list from github', path);
try {
const [user, repo, ...folders] = path.split('/');
const baseUrl = `https://api.github.com/repos/${user}/${repo}/contents`;
const banks = await fetch(`${baseUrl}/${folders.join('/')}`).then((res) => res.json());
// fetch each subfolder
githubCache[path] = (
await Promise.all(
banks.map(async ({ name, path }) => ({
name,
content: await fetch(`${baseUrl}/${path}`)
.then((res) => res.json())
.catch((err) => {
console.error('could not load path', err);
}),
})),
)
)
.filter(({ content }) => !!content)
.reduce(
(acc, { name, content }) => ({
...acc,
[nameFn?.(name) || name]: content.map(({ download_url }) => download_url),
}),
{},
);
} catch (err) {
console.error('[sampler]: failed to fetch sample list from github', err);
return;
}
sampleCache.current = githubCache[path];
localStorage.setItem(storageKey, JSON.stringify(sampleCache.current));
console.log('[sampler]: loaded samples:', sampleCache.current);
return githubCache[path];
};
/**
* load the given sample map for webdirt
*
* @example
* loadSamples({
* bd: '808bd/BD0000.WAV',
* sd: ['808sd/SD0000.WAV','808sd/SD0010.WAV','808sd/SD0050.WAV']
* }, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/');
* s("bd <sd!7 sd(3,4,2)>").n(2).webdirt()
*
*/
export const samples = (sampleMap, baseUrl = '') => {
sampleCache.current = {
...sampleCache.current,
...Object.fromEntries(
Object.entries(sampleMap).map(([key, value]) => [
key,
(typeof value === 'string' ? [value] : value).map((v) =>
(baseUrl + v).replace('github:', 'https://raw.githubusercontent.com/'),
),
]),
),
};
};
export const resetLoadedSamples = () => {
sampleCache.current = undefined;
};
export const getLoadedSamples = () => sampleCache.current;

View File

@ -0,0 +1,98 @@
import * as strudel from '@strudel.cycles/core';
const { Pattern } = strudel;
import * as WebDirt from 'WebDirt';
import { getLoadedSamples, loadBuffer, getLoadedBuffer } from './sampler.mjs';
let webDirt;
/*
example config:
{
sampleMapUrl: 'EmuSP12.json',
sampleFolder: 'EmuSP12',
}
*/
export function loadWebDirt(config) {
webDirt = new WebDirt.WebDirt(config);
webDirt.initializeWebAudio();
}
/**
*
* Uses [webdirt](https://github.com/dktr0/WebDirt) as output.
*
* <details>
* <summary>show supported Webdirt controls</summary>
*
* - s :: String, -- name of sample bank
* - n :: Int, -- number of sample within a bank
* - {@link gain} :: Number, -- clamped from 0 to 2; 1 is default and full-scale
* - overgain :: Number, -- additional gain added to gain to go past clamp at 2
* - {@link pan} :: Number, -- range: 0 to 1
* - nudge :: Number, -- nudge the time of the sample forwards/backwards in seconds
* - {@link speed} :: Number, -- speed / pitch of the sample
* - {@link unit} :: String
* - note :: Number, -- pitch offset in semitones
* - {@link begin} :: Number, -- cut from sample start, normalized
* - {@link end} :: Number, -- cut from sample end, normalized
* - {@link cut} :: Int, -- samples with same cut number will interupt each other
* - {@link cutoff} :: Number, -- lowpass filter frequency
* - {@link resonance} :: Number, -- lowpass filter resonance
* - {@link hcutoff} :: Number, -- highpass filter frequency
* - {@link hresonance} :: Number, -- highpass filter resonance
* - {@link bandf} :: Number, -- bandpass filter frequency
* - {@link bandq} :: Number, -- bandpass filter resonance
* - {@link vowel} :: String, -- name of vowel ('a' | 'e' | 'i' | 'o' | 'u')
* - delay :: Number, -- delay wet/dry mix
* - delaytime :: Number, -- delay time in seconds
* - delayfeedback :: Number, -- delay feedback
* - {@link loop} :: Number, -- loop sample n times (relative to sample length)
* - {@link crush} :: Number, -- bitcrusher (currently not working)
* - {@link coarse} :: Number, -- coarse effect (currently not working)
* - {@link shape} :: Number, -- (currently not working)
*
* </details>
*
* @name webdirt
* @memberof Pattern
* @returns Pattern
* @example
* s("bd*2 hh sd hh").n("<0 1>").webdirt()
*/
Pattern.prototype.webdirt = function () {
// create a WebDirt object and initialize Web Audio context
return this._withHap((hap) => {
const onTrigger = async (time, e, currentTime) => {
if (!webDirt) {
throw new Error('WebDirt not initialized!');
}
const deadline = time - currentTime;
const { s, n = 0, ...rest } = e.value || {};
if (!s) {
console.warn('Pattern.webdirt: no "s" was set!');
}
const samples = getLoadedSamples();
if (!samples?.[s]) {
// try default samples
webDirt.playSample({ s, n, ...rest }, deadline);
return;
}
if (!samples?.[s]) {
console.warn(`Pattern.webdirt: sample "${s}" not found in loaded samples`, samples);
} else {
const bank = samples[s];
const sampleUrl = bank[n % bank.length];
const buffer = getLoadedBuffer(sampleUrl);
if (!buffer) {
console.log(`Pattern.webdirt: load ${s}:${n} from ${sampleUrl}`);
loadBuffer(sampleUrl, webDirt.ac);
} else {
const msg = { buffer: { buffer }, ...rest };
webDirt.playSample(msg, deadline);
}
}
};
return hap.setContext({ ...hap.context, onTrigger });
});
};

16
repl/public/EmuSP12.json Normal file
View File

@ -0,0 +1,16 @@
{
"bd": ["bd/Bassdrum-01.wav","bd/Bassdrum-02.wav","bd/Bassdrum-03.wav","bd/Bassdrum-04.wav","bd/Bassdrum-05.wav","bd/Bassdrum-06.wav","bd/Bassdrum-07.wav","bd/Bassdrum-08.wav","bd/Bassdrum-09.wav","bd/Bassdrum-10.wav","bd/Bassdrum-11.wav","bd/Bassdrum-12.wav","bd/Bassdrum-13.wav","bd/Bassdrum-14.wav"],
"cb": ["cb/Cowbell.wav"],
"cp": ["cp/Clap.wav"],
"cr": ["cr/Crash.wav"],
"hh": ["hh/Hat Closed-01.wav","hh/Hat Closed-02.wav"],
"ht": ["ht/Tom H-01.wav","ht/Tom H-02.wav","ht/Tom H-03.wav","ht/Tom H-04.wav","ht/Tom H-05.wav","ht/Tom H-06.wav"],
"lt": ["lt/Tom L-01.wav","lt/Tom L-02.wav","lt/Tom L-03.wav","lt/Tom L-04.wav","lt/Tom L-05.wav","lt/Tom L-06.wav"],
"misc": ["misc/Metal-01.wav","misc/Metal-02.wav","misc/Metal-03.wav","misc/Scratch.wav","misc/Shot-01.wav","misc/Shot-02.wav","misc/Shot-03.wav"],
"mt": ["mt/Tom M-01.wav","mt/Tom M-02.wav","mt/Tom M-03.wav","mt/Tom M-05.wav"],
"oh": ["oh/Hhopen1.wav"],
"perc": ["perc/Blow1.wav"],
"rd": ["rd/Ride.wav"],
"rim": ["rim/zRim Shot-01.wav","rim/zRim Shot-02.wav"],
"sd": ["sd/Snaredrum-01.wav","sd/Snaredrum-02.wav","sd/Snaredrum-03.wav","sd/Snaredrum-04.wav","sd/Snaredrum-05.wav","sd/Snaredrum-06.wav","sd/Snaredrum-07.wav","sd/Snaredrum-08.wav","sd/Snaredrum-09.wav","sd/Snaredrum-10.wav","sd/Snaredrum-11.wav","sd/Snaredrum-12.wav","sd/Snaredrum-13.wav","sd/Snaredrum-14.wav","sd/Snaredrum-15.wav","sd/Snaredrum-16.wav","sd/Snaredrum-17.wav","sd/Snaredrum-18.wav","sd/Snaredrum-19.wav","sd/Snaredrum-20.wav","sd/Snaredrum-21.wav"]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -12,9 +12,13 @@ import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
import './App.css';
import logo from './logo.svg';
import * as tunes from './tunes.mjs';
import * as WebDirt from 'WebDirt';
import { loadWebDirt, resetLoadedSamples } from '@strudel.cycles/webdirt';
evalScope(
Tone,
controls,
{ WebDirt },
import('@strudel.cycles/core'),
import('@strudel.cycles/tone'),
import('@strudel.cycles/tonal'),
@ -23,8 +27,14 @@ evalScope(
import('@strudel.cycles/xen'),
import('@strudel.cycles/webaudio'),
import('@strudel.cycles/osc'),
import('@strudel.cycles/webdirt'),
);
loadWebDirt({
sampleMapUrl: 'EmuSP12.json',
sampleFolder: 'EmuSP12',
});
const initialUrl = window.location.href;
const codeParam = window.location.href.split('#')[1];
let decoded;
@ -163,6 +173,7 @@ function App() {
setCode(_code);
cleanupDraw();
cleanupUi();
resetLoadedSamples();
const parsed = await evaluate(_code);
setPattern(parsed.pattern);
setActiveCode(_code);

View File

@ -2,6 +2,8 @@ import { Tone } from '@strudel.cycles/tone';
import { evalScope } from '@strudel.cycles/eval';
import { MiniRepl as _MiniRepl } from '@strudel.cycles/react';
import controls from '@strudel.cycles/core/controls.mjs';
import * as WebDirt from 'WebDirt';
import { loadWebDirt } from '@strudel.cycles/webdirt';
export const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination).set({
oscillator: { type: 'triangle' },
@ -21,8 +23,14 @@ evalScope(
import('@strudel.cycles/xen'),
import('@strudel.cycles/webaudio'),
import('@strudel.cycles/osc'),
import('@strudel.cycles/webdirt'),
);
loadWebDirt({
sampleMapUrl: '../EmuSP12.json',
sampleFolder: '../EmuSP12',
});
export function MiniRepl({ tune }) {
return <_MiniRepl tune={tune} defaultSynth={defaultSynth} hideOutsideView={true} />;
}

View File

@ -758,6 +758,12 @@ They can provide streams of numbers that can be sampled at discrete points in ti
{{ 'square2' | jsdoc }}
## Using Samples with Webdirt
You can use the powerful sampling engine [Webdirt](https://github.com/dktr0/WebDirt) with Strudel.
{{ 'Pattern.webdirt' | jsdoc }}
## Using Superdirt via OSC
In mainline tidal, the actual sound is generated via Superdirt, which runs inside Supercollider.