mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-11 13:48:34 +00:00
Merge branch 'main' of github.com:tidalcycles/strudel
This commit is contained in:
commit
363895761e
5
.gitignore
vendored
5
.gitignore
vendored
@ -31,4 +31,7 @@ doc
|
|||||||
out
|
out
|
||||||
.parcel-cache
|
.parcel-cache
|
||||||
repl_old
|
repl_old
|
||||||
tutorial.rendered.mdx
|
tutorial.rendered.mdx
|
||||||
|
doc.json
|
||||||
|
talk/public/EmuSP12
|
||||||
|
talk/public/samples
|
||||||
@ -32,7 +32,9 @@ Use one of the Communication Channels listed above.
|
|||||||
## Improve the Tutorial
|
## Improve the Tutorial
|
||||||
|
|
||||||
If you find some weak spots in the [tutorial](https://strudel.tidalcycles.org/),
|
If you find some weak spots in the [tutorial](https://strudel.tidalcycles.org/),
|
||||||
you are welcome to improve them by editing [this file](https://github.com/tidalcycles/strudel/blob/main/repl/src/tutorial/tutorial.mdx).
|
you are welcome to improve them by editing [this file](https://github.com/tidalcycles/strudel/blob/main/tutorial/tutorial.mdx).
|
||||||
|
|
||||||
|
|
||||||
This will even work without setting up a development environment, only a github account is required.
|
This will even work without setting up a development environment, only a github account is required.
|
||||||
|
|
||||||
## Propose a Feature
|
## Propose a Feature
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://github.com/tidalcycles/strudel/actions)
|
[](https://github.com/tidalcycles/strudel/actions)
|
||||||
|
|
||||||
An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using web technologies. This is unstable software, please tread carefully.
|
An experiment in making a [Tidal](https://github.com/tidalcycles/tidal/) using web technologies. This software is slowly stabilising, but please continue to tread carefully.
|
||||||
|
|
||||||
- Try it here: <https://strudel.tidalcycles.org/>
|
- Try it here: <https://strudel.tidalcycles.org/>
|
||||||
- Tutorial: <https://strudel.tidalcycles.org/tutorial/>
|
- Tutorial: <https://strudel.tidalcycles.org/tutorial/>
|
||||||
|
|||||||
69
package-lock.json
generated
69
package-lock.json
generated
@ -2255,6 +2255,10 @@
|
|||||||
"resolved": "packages/webaudio",
|
"resolved": "packages/webaudio",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@strudel.cycles/webdirt": {
|
||||||
|
"resolved": "packages/webdirt",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@strudel.cycles/xen": {
|
"node_modules/@strudel.cycles/xen": {
|
||||||
"resolved": "packages/xen",
|
"resolved": "packages/xen",
|
||||||
"link": true
|
"link": true
|
||||||
@ -10815,6 +10819,11 @@
|
|||||||
"defaults": "^1.0.3"
|
"defaults": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/WebDirt": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "git+ssh://git@github.com/dktr0/WebDirt.git#425dc8fd023440d9c61ffdb8642e44e2710faea0",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
|
||||||
@ -11271,7 +11280,7 @@
|
|||||||
},
|
},
|
||||||
"packages/eval": {
|
"packages/eval": {
|
||||||
"name": "@strudel.cycles/eval",
|
"name": "@strudel.cycles/eval",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.1.0",
|
"@strudel.cycles/core": "^0.1.0",
|
||||||
@ -11298,22 +11307,22 @@
|
|||||||
},
|
},
|
||||||
"packages/midi": {
|
"packages/midi": {
|
||||||
"name": "@strudel.cycles/midi",
|
"name": "@strudel.cycles/midi",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/tone": "^0.1.0",
|
"@strudel.cycles/tone": "^0.1.1",
|
||||||
"tone": "^14.7.77",
|
"tone": "^14.7.77",
|
||||||
"webmidi": "^2.5.2"
|
"webmidi": "^2.5.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/mini": {
|
"packages/mini": {
|
||||||
"name": "@strudel.cycles/mini",
|
"name": "@strudel.cycles/mini",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.1.0",
|
"@strudel.cycles/core": "^0.1.0",
|
||||||
"@strudel.cycles/eval": "^0.1.0",
|
"@strudel.cycles/eval": "^0.1.1",
|
||||||
"@strudel.cycles/tone": "^0.1.0"
|
"@strudel.cycles/tone": "^0.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/osc": {
|
"packages/osc": {
|
||||||
@ -11326,14 +11335,14 @@
|
|||||||
},
|
},
|
||||||
"packages/react": {
|
"packages/react": {
|
||||||
"name": "@strudel.cycles/react",
|
"name": "@strudel.cycles/react",
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-javascript": "^0.19.0",
|
"@codemirror/lang-javascript": "^0.19.0",
|
||||||
"@strudel.cycles/core": "*",
|
"@strudel.cycles/core": "*",
|
||||||
"@strudel.cycles/eval": "^0.1.0",
|
"@strudel.cycles/eval": "^0.1.1",
|
||||||
"@strudel.cycles/tone": "^0.1.0",
|
"@strudel.cycles/tone": "^0.1.1",
|
||||||
"react-codemirror6": "^1.1.0",
|
"react-codemirror6": "^1.1.0",
|
||||||
"react-hook-inview": "^4.5.0"
|
"react-hook-inview": "^4.5.0"
|
||||||
},
|
},
|
||||||
@ -11407,7 +11416,7 @@
|
|||||||
},
|
},
|
||||||
"packages/tonal": {
|
"packages/tonal": {
|
||||||
"name": "@strudel.cycles/tonal",
|
"name": "@strudel.cycles/tonal",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.1.0",
|
"@strudel.cycles/core": "^0.1.0",
|
||||||
@ -11432,7 +11441,7 @@
|
|||||||
},
|
},
|
||||||
"packages/tone": {
|
"packages/tone": {
|
||||||
"name": "@strudel.cycles/tone",
|
"name": "@strudel.cycles/tone",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.1.0",
|
"@strudel.cycles/core": "^0.1.0",
|
||||||
@ -11443,15 +11452,24 @@
|
|||||||
},
|
},
|
||||||
"packages/webaudio": {
|
"packages/webaudio": {
|
||||||
"name": "@strudel.cycles/webaudio",
|
"name": "@strudel.cycles/webaudio",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.1.0"
|
"@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": {
|
"packages/xen": {
|
||||||
"name": "@strudel.cycles/xen",
|
"name": "@strudel.cycles/xen",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@strudel.cycles/core": "^0.1.0"
|
"@strudel.cycles/core": "^0.1.0"
|
||||||
@ -13281,7 +13299,7 @@
|
|||||||
"@strudel.cycles/midi": {
|
"@strudel.cycles/midi": {
|
||||||
"version": "file:packages/midi",
|
"version": "file:packages/midi",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@strudel.cycles/tone": "^0.1.0",
|
"@strudel.cycles/tone": "^0.1.1",
|
||||||
"tone": "^14.7.77",
|
"tone": "^14.7.77",
|
||||||
"webmidi": "^2.5.2"
|
"webmidi": "^2.5.2"
|
||||||
}
|
}
|
||||||
@ -13290,8 +13308,8 @@
|
|||||||
"version": "file:packages/mini",
|
"version": "file:packages/mini",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@strudel.cycles/core": "^0.1.0",
|
"@strudel.cycles/core": "^0.1.0",
|
||||||
"@strudel.cycles/eval": "^0.1.0",
|
"@strudel.cycles/eval": "^0.1.1",
|
||||||
"@strudel.cycles/tone": "^0.1.0"
|
"@strudel.cycles/tone": "^0.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@strudel.cycles/osc": {
|
"@strudel.cycles/osc": {
|
||||||
@ -13305,8 +13323,8 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"@codemirror/lang-javascript": "^0.19.0",
|
"@codemirror/lang-javascript": "^0.19.0",
|
||||||
"@strudel.cycles/core": "*",
|
"@strudel.cycles/core": "*",
|
||||||
"@strudel.cycles/eval": "^0.1.0",
|
"@strudel.cycles/eval": "^0.1.1",
|
||||||
"@strudel.cycles/tone": "^0.1.0",
|
"@strudel.cycles/tone": "^0.1.1",
|
||||||
"@types/react": "^17.0.2",
|
"@types/react": "^17.0.2",
|
||||||
"@types/react-dom": "^17.0.2",
|
"@types/react-dom": "^17.0.2",
|
||||||
"@vitejs/plugin-react": "^1.3.0",
|
"@vitejs/plugin-react": "^1.3.0",
|
||||||
@ -13401,6 +13419,13 @@
|
|||||||
"@strudel.cycles/core": "^0.1.0"
|
"@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": {
|
"@strudel.cycles/xen": {
|
||||||
"version": "file:packages/xen",
|
"version": "file:packages/xen",
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -18362,8 +18387,8 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"@codemirror/lang-javascript": "^0.19.0",
|
"@codemirror/lang-javascript": "^0.19.0",
|
||||||
"@strudel.cycles/core": "*",
|
"@strudel.cycles/core": "*",
|
||||||
"@strudel.cycles/eval": "^0.1.0",
|
"@strudel.cycles/eval": "^0.1.1",
|
||||||
"@strudel.cycles/tone": "^0.1.0",
|
"@strudel.cycles/tone": "^0.1.1",
|
||||||
"@types/react": "^17.0.2",
|
"@types/react": "^17.0.2",
|
||||||
"@types/react-dom": "^17.0.2",
|
"@types/react-dom": "^17.0.2",
|
||||||
"@vitejs/plugin-react": "^1.3.0",
|
"@vitejs/plugin-react": "^1.3.0",
|
||||||
@ -20004,6 +20029,10 @@
|
|||||||
"defaults": "^1.0.3"
|
"defaults": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"WebDirt": {
|
||||||
|
"version": "git+ssh://git@github.com/dktr0/WebDirt.git#425dc8fd023440d9c61ffdb8642e44e2710faea0",
|
||||||
|
"from": "WebDirt@github:dktr0/WebDirt"
|
||||||
|
},
|
||||||
"webidl-conversions": {
|
"webidl-conversions": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm run test --workspaces --if-present && cd repl && npm run test",
|
"test": "npm run test --workspaces --if-present && cd repl && npm run test",
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna bootstrap",
|
||||||
"setup": "npm i && npm run bootstrap && cd repl && npm i",
|
"setup": "npm i && npm run bootstrap && cd repl && npm i && cd ../tutorial && npm i",
|
||||||
"repl": "cd repl && npm run dev",
|
"repl": "cd repl && npm run dev",
|
||||||
"osc": "cd packages/osc && npm run server",
|
"osc": "cd packages/osc && npm run server",
|
||||||
"build": "rm -rf out && cd repl && npm run build && cd ../tutorial && npm run build",
|
"build": "rm -rf out && cd repl && npm run build && cd ../tutorial && npm run build",
|
||||||
|
|||||||
@ -760,4 +760,13 @@ generic_params.forEach(([type, name, description]) => {
|
|||||||
Pattern.prototype[name] = _setter(controls[name]);
|
Pattern.prototype[name] = _setter(controls[name]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// create custom param
|
||||||
|
controls.createParam = (name) => {
|
||||||
|
Pattern.prototype[name] = _setter(controls[name]);
|
||||||
|
return (...pats) => _name(name, ...pats);
|
||||||
|
};
|
||||||
|
|
||||||
|
controls.createParams = (...names) =>
|
||||||
|
names.reduce((acc, name) => Object.assign(acc, { [name]: createParam(name) }), {});
|
||||||
|
|
||||||
export default controls;
|
export default controls;
|
||||||
|
|||||||
4
packages/core/package-lock.json
generated
4
packages/core/package-lock.json
generated
@ -6,8 +6,8 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@strudel.cycles/core",
|
"name": "@strudel.cycles/core",
|
||||||
"version": "0.0.3",
|
"version": "0.1.0",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bjork": "^0.0.1",
|
"bjork": "^0.0.1",
|
||||||
"fraction.js": "^4.2.0"
|
"fraction.js": "^4.2.0"
|
||||||
|
|||||||
@ -1049,6 +1049,9 @@ export class Pattern {
|
|||||||
.unit('c')
|
.unit('c')
|
||||||
.slow(factor);
|
.slow(factor);
|
||||||
}
|
}
|
||||||
|
onTrigger(onTrigger) {
|
||||||
|
return this._withHap((hap) => hap.setContext({ ...hap.context, onTrigger }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - adopt value.mjs fully..
|
// TODO - adopt value.mjs fully..
|
||||||
|
|||||||
@ -15,9 +15,11 @@ npm i @strudel.cycles/eval --save
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
import { evaluate, extend } from '@strudel.cycles/eval';
|
import { evaluate, extend } from '@strudel.cycles/eval';
|
||||||
import * as strudel from '@strudel.cycles/core';
|
|
||||||
|
|
||||||
extend(strudel); // add strudel to eval scope
|
evalScope(
|
||||||
|
import('@strudel.cycles/core'),
|
||||||
|
// import other strudel packages here
|
||||||
|
); // add strudel to eval scope
|
||||||
|
|
||||||
async function run(code) {
|
async function run(code) {
|
||||||
const { pattern } = await evaluate(code);
|
const { pattern } = await evaluate(code);
|
||||||
|
|||||||
4
packages/eval/package-lock.json
generated
4
packages/eval/package-lock.json
generated
@ -6,8 +6,8 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@strudel.cycles/eval",
|
"name": "@strudel.cycles/eval",
|
||||||
"version": "0.0.3",
|
"version": "0.1.1",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"estraverse": "^5.3.0",
|
"estraverse": "^5.3.0",
|
||||||
"shift-ast": "^6.1.0",
|
"shift-ast": "^6.1.0",
|
||||||
|
|||||||
4
packages/midi/package-lock.json
generated
4
packages/midi/package-lock.json
generated
@ -6,8 +6,8 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@strudel.cycles/midi",
|
"name": "@strudel.cycles/midi",
|
||||||
"version": "0.0.4",
|
"version": "0.1.1",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tone": "^14.7.77",
|
"tone": "^14.7.77",
|
||||||
"webmidi": "^2.5.2"
|
"webmidi": "^2.5.2"
|
||||||
|
|||||||
@ -22,13 +22,15 @@ let startedAt = -1;
|
|||||||
*/
|
*/
|
||||||
Pattern.prototype.osc = function () {
|
Pattern.prototype.osc = function () {
|
||||||
return this._withHap((hap) => {
|
return this._withHap((hap) => {
|
||||||
const onTrigger = (time, hap, currentTime, cps, cycle, delta) => {
|
const onTrigger = (time, hap, currentTime, cps) => {
|
||||||
|
const cycle = hap.wholeOrPart().begin.valueOf();
|
||||||
|
const delta = hap.duration.valueOf();
|
||||||
// time should be audio time of onset
|
// time should be audio time of onset
|
||||||
// currentTime should be current time of audio context (slightly before time)
|
// currentTime should be current time of audio context (slightly before time)
|
||||||
if (startedAt < 0) {
|
if (startedAt < 0) {
|
||||||
startedAt = Date.now() - currentTime * 1000;
|
startedAt = Date.now() - currentTime * 1000;
|
||||||
}
|
}
|
||||||
const controls = Object.assign({}, { cps: cps, cycle: cycle, delta: delta }, hap.value);
|
const controls = Object.assign({}, { cps, cycle, delta }, hap.value);
|
||||||
const keyvals = Object.entries(controls).flat();
|
const keyvals = Object.entries(controls).flat();
|
||||||
const ts = Math.floor(startedAt + (time + latency) * 1000);
|
const ts = Math.floor(startedAt + (time + latency) * 1000);
|
||||||
const message = new OSC.Message('/dirt/play', ...keyvals);
|
const message = new OSC.Message('/dirt/play', ...keyvals);
|
||||||
|
|||||||
4
packages/osc/package-lock.json
generated
4
packages/osc/package-lock.json
generated
@ -6,8 +6,8 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@strudel.cycles/osc",
|
"name": "@strudel.cycles/osc",
|
||||||
"version": "0.0.1",
|
"version": "0.1.0",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"osc-js": "^2.3.2"
|
"osc-js": "^2.3.2"
|
||||||
}
|
}
|
||||||
|
|||||||
4
packages/react/dist/index.cjs.js
vendored
4
packages/react/dist/index.cjs.js
vendored
File diff suppressed because one or more lines are too long
7
packages/react/dist/index.es.js
vendored
7
packages/react/dist/index.es.js
vendored
@ -26,7 +26,7 @@ const ivory = '#abb2bf',
|
|||||||
// background = '#292d3e',
|
// background = '#292d3e',
|
||||||
background = 'transparent',
|
background = 'transparent',
|
||||||
tooltipBackground = '#353a42',
|
tooltipBackground = '#353a42',
|
||||||
selection = 'rgba(128, 203, 196, 0.2)',
|
selection = 'rgba(128, 203, 196, 0.5)',
|
||||||
cursor = '#ffcc00';
|
cursor = '#ffcc00';
|
||||||
|
|
||||||
/// The editor theme styles for Material Palenight.
|
/// The editor theme styles for Material Palenight.
|
||||||
@ -50,7 +50,8 @@ const materialPalenightTheme = EditorView.theme(
|
|||||||
},
|
},
|
||||||
// done
|
// done
|
||||||
'&.cm-focused .cm-cursor': {
|
'&.cm-focused .cm-cursor': {
|
||||||
borderLeftColor: cursor,
|
backgroundColor: cursor,
|
||||||
|
width: '3px',
|
||||||
},
|
},
|
||||||
|
|
||||||
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {
|
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {
|
||||||
@ -80,7 +81,7 @@ const materialPalenightTheme = EditorView.theme(
|
|||||||
|
|
||||||
// done
|
// done
|
||||||
'.cm-gutters': {
|
'.cm-gutters': {
|
||||||
background: '#2C323699',
|
background: 'transparent',
|
||||||
color: '#676e95',
|
color: '#676e95',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
},
|
},
|
||||||
|
|||||||
3984
packages/react/package-lock.json
generated
Normal file
3984
packages/react/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -57,14 +57,7 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
|
|||||||
/* console.warn('no instrument chosen', event);
|
/* console.warn('no instrument chosen', event);
|
||||||
throw new Error(`no instrument chosen for ${JSON.stringify(event)}`); */
|
throw new Error(`no instrument chosen for ${JSON.stringify(event)}`); */
|
||||||
} else {
|
} else {
|
||||||
onTrigger(
|
onTrigger(time, event, currentTime, 1 /* cps */);
|
||||||
time,
|
|
||||||
event,
|
|
||||||
currentTime,
|
|
||||||
1 /* cps */,
|
|
||||||
event.wholeOrPart().begin.valueOf(),
|
|
||||||
event.duration.valueOf(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(err);
|
console.warn(err);
|
||||||
|
|||||||
@ -16,7 +16,7 @@ const ivory = '#abb2bf',
|
|||||||
// background = '#292d3e',
|
// background = '#292d3e',
|
||||||
background = 'transparent',
|
background = 'transparent',
|
||||||
tooltipBackground = '#353a42',
|
tooltipBackground = '#353a42',
|
||||||
selection = 'rgba(128, 203, 196, 0.2)',
|
selection = 'rgba(128, 203, 196, 0.5)',
|
||||||
cursor = '#ffcc00';
|
cursor = '#ffcc00';
|
||||||
|
|
||||||
/// The editor theme styles for Material Palenight.
|
/// The editor theme styles for Material Palenight.
|
||||||
@ -40,7 +40,8 @@ export const materialPalenightTheme = EditorView.theme(
|
|||||||
},
|
},
|
||||||
// done
|
// done
|
||||||
'&.cm-focused .cm-cursor': {
|
'&.cm-focused .cm-cursor': {
|
||||||
borderLeftColor: cursor,
|
backgroundColor: cursor,
|
||||||
|
width: '3px',
|
||||||
},
|
},
|
||||||
|
|
||||||
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {
|
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {
|
||||||
@ -70,7 +71,7 @@ export const materialPalenightTheme = EditorView.theme(
|
|||||||
|
|
||||||
// done
|
// done
|
||||||
'.cm-gutters': {
|
'.cm-gutters': {
|
||||||
background: '#2C323699',
|
background: 'transparent',
|
||||||
color: '#676e95',
|
color: '#676e95',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
},
|
},
|
||||||
|
|||||||
4
packages/tonal/package-lock.json
generated
4
packages/tonal/package-lock.json
generated
@ -6,8 +6,8 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@strudel.cycles/tonal",
|
"name": "@strudel.cycles/tonal",
|
||||||
"version": "0.0.3",
|
"version": "0.1.1",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tonaljs/tonal": "^4.6.5",
|
"@tonaljs/tonal": "^4.6.5",
|
||||||
"webmidi": "^3.0.15"
|
"webmidi": "^3.0.15"
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export const getDrawContext = (id = 'test-canvas') => {
|
|||||||
return canvas.getContext('2d');
|
return canvas.getContext('2d');
|
||||||
};
|
};
|
||||||
|
|
||||||
Pattern.prototype.draw = function (callback, cycleSpan, lookaheadCycles = 1) {
|
Pattern.prototype.draw = function (callback, { from, to, onQuery }) {
|
||||||
if (window.strudelAnimation) {
|
if (window.strudelAnimation) {
|
||||||
cancelAnimationFrame(window.strudelAnimation);
|
cancelAnimationFrame(window.strudelAnimation);
|
||||||
}
|
}
|
||||||
@ -29,19 +29,22 @@ Pattern.prototype.draw = function (callback, cycleSpan, lookaheadCycles = 1) {
|
|||||||
events = [];
|
events = [];
|
||||||
const animate = (time) => {
|
const animate = (time) => {
|
||||||
const t = Tone.getTransport().seconds;
|
const t = Tone.getTransport().seconds;
|
||||||
if (cycleSpan) {
|
if (from !== undefined && to !== undefined) {
|
||||||
const currentCycle = Math.floor(t / cycleSpan);
|
const currentCycle = Math.floor(t);
|
||||||
if (cycle !== currentCycle) {
|
if (cycle !== currentCycle) {
|
||||||
cycle = currentCycle;
|
cycle = currentCycle;
|
||||||
const begin = currentCycle * cycleSpan;
|
const begin = currentCycle + from;
|
||||||
const end = (currentCycle + lookaheadCycles) * cycleSpan;
|
const end = currentCycle + to;
|
||||||
events = this._asNumber(true) // true = silent error
|
setTimeout(() => {
|
||||||
.query(new State(new TimeSpan(begin, end)))
|
events = this._asNumber(true) // true = silent error
|
||||||
.filter(Boolean)
|
.query(new State(new TimeSpan(begin, end)))
|
||||||
.filter((event) => event.part.begin.equals(event.whole.begin));
|
.filter(Boolean)
|
||||||
|
.filter((event) => event.part.begin.equals(event.whole.begin));
|
||||||
|
onQuery?.(events);
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callback(ctx, events, t, cycleSpan, time);
|
callback(ctx, events, t, time);
|
||||||
window.strudelAnimation = requestAnimationFrame(animate);
|
window.strudelAnimation = requestAnimationFrame(animate);
|
||||||
};
|
};
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
|
|||||||
4
packages/tone/package-lock.json
generated
4
packages/tone/package-lock.json
generated
@ -6,8 +6,8 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@strudel.cycles/tone",
|
"name": "@strudel.cycles/tone",
|
||||||
"version": "0.0.4",
|
"version": "0.1.1",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tonejs/piano": "^0.2.1",
|
"@tonejs/piano": "^0.2.1",
|
||||||
"chord-voicings": "^0.0.1",
|
"chord-voicings": "^0.0.1",
|
||||||
|
|||||||
@ -6,39 +6,126 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
|
|
||||||
import { Pattern } from '@strudel.cycles/core';
|
import { Pattern } from '@strudel.cycles/core';
|
||||||
|
|
||||||
|
const scale = (normalized, min, max) => normalized * (max - min) + min;
|
||||||
|
|
||||||
Pattern.prototype.pianoroll = function ({
|
Pattern.prototype.pianoroll = function ({
|
||||||
timeframe = 10,
|
cycles = 4,
|
||||||
|
playhead = 0.5,
|
||||||
|
overscan = 1,
|
||||||
|
flipTime = 0,
|
||||||
|
flipValues = 0,
|
||||||
|
hideNegative = false,
|
||||||
inactive = '#C9E597',
|
inactive = '#C9E597',
|
||||||
active = '#FFCA28',
|
active = '#FFCA28',
|
||||||
background = '#2A3236',
|
// background = '#2A3236',
|
||||||
|
background = 'transparent',
|
||||||
|
minMidi = 10,
|
||||||
maxMidi = 90,
|
maxMidi = 90,
|
||||||
minMidi = 0,
|
autorange = 0,
|
||||||
|
timeframe: timeframeProp,
|
||||||
|
fold = 0,
|
||||||
|
vertical = 0,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const w = window.innerWidth;
|
const ctx = getDrawContext();
|
||||||
const h = window.innerHeight;
|
const w = ctx.canvas.width;
|
||||||
const midiRange = maxMidi - minMidi + 1;
|
const h = ctx.canvas.height;
|
||||||
const height = h / midiRange;
|
let from = -cycles * playhead;
|
||||||
|
let to = cycles * (1 - playhead);
|
||||||
|
|
||||||
|
if (timeframeProp) {
|
||||||
|
console.warn('timeframe is deprecated! use from/to instead');
|
||||||
|
from = 0;
|
||||||
|
to = timeframeProp;
|
||||||
|
}
|
||||||
|
if (!autorange && fold) {
|
||||||
|
console.warn('disabling autorange has no effect when fold is enabled');
|
||||||
|
}
|
||||||
|
const timeAxis = vertical ? h : w;
|
||||||
|
const valueAxis = vertical ? w : h;
|
||||||
|
let timeRange = vertical ? [timeAxis, 0] : [0, timeAxis]; // pixel range for time
|
||||||
|
const timeExtent = to - from; // number of seconds that fit inside the canvas frame
|
||||||
|
const valueRange = vertical ? [0, valueAxis] : [valueAxis, 0]; // pixel range for values
|
||||||
|
let valueExtent = maxMidi - minMidi + 1; // number of "slots" for values, overwritten if autorange true
|
||||||
|
let barThickness = valueAxis / valueExtent; // pixels per value, overwritten if autorange true
|
||||||
|
let foldValues = [];
|
||||||
|
flipTime && timeRange.reverse();
|
||||||
|
flipValues && valueRange.reverse();
|
||||||
|
|
||||||
|
const playheadPosition = scale(-from / timeExtent, ...timeRange);
|
||||||
this.draw(
|
this.draw(
|
||||||
(ctx, events, t) => {
|
(ctx, events, t) => {
|
||||||
ctx.fillStyle = background;
|
ctx.fillStyle = background;
|
||||||
ctx.clearRect(0, 0, w, h);
|
ctx.clearRect(0, 0, w, h);
|
||||||
ctx.fillRect(0, 0, w, h);
|
ctx.fillRect(0, 0, w, h);
|
||||||
events.forEach((event) => {
|
const inFrame = (event) =>
|
||||||
const isActive = event.whole.begin <= t && event.whole.end >= t;
|
(!hideNegative || event.whole.begin >= 0) && event.whole.begin <= t + to && event.whole.end >= t + from;
|
||||||
|
events.filter(inFrame).forEach((event) => {
|
||||||
|
const isActive = event.whole.begin <= t && event.whole.end > t;
|
||||||
ctx.fillStyle = event.context?.color || inactive;
|
ctx.fillStyle = event.context?.color || inactive;
|
||||||
ctx.strokeStyle = event.context?.color || active;
|
ctx.strokeStyle = event.context?.color || active;
|
||||||
ctx.globalAlpha = event.context.velocity ?? 1;
|
ctx.globalAlpha = event.context.velocity ?? 1;
|
||||||
const x = Math.round((event.whole.begin / timeframe) * w);
|
ctx.beginPath();
|
||||||
const width = Math.round(((event.whole.end - event.whole.begin) / timeframe) * w);
|
if (vertical) {
|
||||||
const y = Math.round(h - ((Number(event.value) - minMidi) / midiRange) * h);
|
ctx.moveTo(0, playheadPosition);
|
||||||
const offset = (t / timeframe) * w;
|
ctx.lineTo(valueAxis, playheadPosition);
|
||||||
const margin = 0;
|
} else {
|
||||||
const coords = [x - offset + margin + 1, y + 1, width - 2, height - 2];
|
ctx.moveTo(playheadPosition, 0);
|
||||||
|
ctx.lineTo(playheadPosition, valueAxis);
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
const timePx = scale((event.whole.begin - (flipTime ? to : from)) / timeExtent, ...timeRange);
|
||||||
|
let durationPx = scale(event.duration / timeExtent, 0, timeAxis);
|
||||||
|
|
||||||
|
const valuePx = scale(
|
||||||
|
fold ? foldValues.indexOf(event.value) / foldValues.length : (Number(event.value) - minMidi) / valueExtent,
|
||||||
|
...valueRange,
|
||||||
|
);
|
||||||
|
let margin = 0;
|
||||||
|
const offset = scale(t / timeExtent, ...timeRange);
|
||||||
|
let coords;
|
||||||
|
if (vertical) {
|
||||||
|
coords = [
|
||||||
|
valuePx + 1 - (flipValues ? barThickness : 0), // x
|
||||||
|
timeAxis - offset + timePx + margin + 1 - (flipTime ? 0 : durationPx), // y
|
||||||
|
barThickness - 2, // width
|
||||||
|
durationPx - 2, // height
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
coords = [
|
||||||
|
timePx - offset + margin + 1 - (flipTime ? durationPx : 0), // x
|
||||||
|
valuePx + 1 - (flipValues ? 0 : barThickness), // y
|
||||||
|
durationPx - 2, // widith
|
||||||
|
barThickness - 2, // height
|
||||||
|
];
|
||||||
|
}
|
||||||
isActive ? ctx.strokeRect(...coords) : ctx.fillRect(...coords);
|
isActive ? ctx.strokeRect(...coords) : ctx.fillRect(...coords);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
timeframe,
|
{
|
||||||
2, // lookaheadCycles
|
from: from - overscan,
|
||||||
|
to: to + overscan,
|
||||||
|
onQuery: (events) => {
|
||||||
|
const getValue = (e) => Number(e.value);
|
||||||
|
const { min, max, values } = events.reduce(
|
||||||
|
({ min, max, values }, e) => {
|
||||||
|
const v = getValue(e);
|
||||||
|
return {
|
||||||
|
min: v < min ? v : min,
|
||||||
|
max: v > max ? v : max,
|
||||||
|
values: values.includes(v) ? values : [...values, v],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{ min: Infinity, max: -Infinity, values: [] },
|
||||||
|
);
|
||||||
|
if (autorange) {
|
||||||
|
minMidi = min;
|
||||||
|
maxMidi = max;
|
||||||
|
valueExtent = maxMidi - minMidi + 1;
|
||||||
|
}
|
||||||
|
foldValues = values.sort((a, b) => a - b);
|
||||||
|
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,18 +10,20 @@ import { State, TimeSpan } from '@strudel.cycles/core';
|
|||||||
export class Scheduler {
|
export class Scheduler {
|
||||||
worker;
|
worker;
|
||||||
pattern;
|
pattern;
|
||||||
constructor({ audioContext, interval = 0.2, onEvent }) {
|
constructor({ audioContext, interval = 0.2, onEvent, latency = 0.2 }) {
|
||||||
this.worker = new ClockWorker(
|
this.worker = new ClockWorker(
|
||||||
audioContext,
|
audioContext,
|
||||||
(begin, end) => {
|
(begin, end) => {
|
||||||
this.pattern.query(new State(new TimeSpan(begin, end))).forEach((e) => {
|
this.pattern.query(new State(new TimeSpan(begin + latency, end + latency))).forEach((e) => {
|
||||||
if (!e.part.begin.equals(e.whole.begin)) {
|
if (!e.part.begin.equals(e.whole.begin)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (e.context.onTrigger) {
|
||||||
|
// TODO: kill first param, as it's contained in e
|
||||||
|
e.context.onTrigger(e.whole.begin, e, audioContext.currentTime, 1 /* cps */);
|
||||||
|
}
|
||||||
if (onEvent) {
|
if (onEvent) {
|
||||||
onEvent?.(e);
|
onEvent?.(e);
|
||||||
} else {
|
|
||||||
console.warn('unplayable event: no audio node nor onEvent callback', e);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
11
packages/webdirt/README.md
Normal file
11
packages/webdirt/README.md
Normal 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
|
||||||
2
packages/webdirt/index.mjs
Normal file
2
packages/webdirt/index.mjs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './webdirt.mjs';
|
||||||
|
export * from './sampler.mjs';
|
||||||
32
packages/webdirt/makeSampleMap.sh
Executable file
32
packages/webdirt/makeSampleMap.sh
Executable 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"
|
||||||
28
packages/webdirt/package-lock.json
generated
Normal file
28
packages/webdirt/package-lock.json
generated
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "@strudel.cycles/webdirt",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "@strudel.cycles/webdirt",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"license": "AGPL-3.0-or-later",
|
||||||
|
"dependencies": {
|
||||||
|
"WebDirt": "github:dktr0/WebDirt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/WebDirt": {
|
||||||
|
"name": "webdirt",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "git+ssh://git@github.com/dktr0/WebDirt.git#425dc8fd023440d9c61ffdb8642e44e2710faea0",
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"WebDirt": {
|
||||||
|
"version": "git+ssh://git@github.com/dktr0/WebDirt.git#425dc8fd023440d9c61ffdb8642e44e2710faea0",
|
||||||
|
"from": "WebDirt@github:dktr0/WebDirt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
packages/webdirt/package.json
Normal file
28
packages/webdirt/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
112
packages/webdirt/sampler.mjs
Normal file
112
packages/webdirt/sampler.mjs
Normal 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;
|
||||||
98
packages/webdirt/webdirt.mjs
Normal file
98
packages/webdirt/webdirt.mjs
Normal 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
16
repl/public/EmuSP12.json
Normal 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"]
|
||||||
|
}
|
||||||
BIN
repl/public/EmuSP12/bd/Bassdrum-01.wav
Normal file
BIN
repl/public/EmuSP12/bd/Bassdrum-01.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/bd/Bassdrum-02.wav
Normal file
BIN
repl/public/EmuSP12/bd/Bassdrum-02.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/bd/Bassdrum-03.wav
Normal file
BIN
repl/public/EmuSP12/bd/Bassdrum-03.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/bd/Bassdrum-04.wav
Normal file
BIN
repl/public/EmuSP12/bd/Bassdrum-04.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/bd/Bassdrum-05.wav
Normal file
BIN
repl/public/EmuSP12/bd/Bassdrum-05.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/bd/Bassdrum-06.wav
Normal file
BIN
repl/public/EmuSP12/bd/Bassdrum-06.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/bd/Bassdrum-07.wav
Normal file
BIN
repl/public/EmuSP12/bd/Bassdrum-07.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/bd/Bassdrum-08.wav
Normal file
BIN
repl/public/EmuSP12/bd/Bassdrum-08.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/bd/Bassdrum-09.wav
Normal file
BIN
repl/public/EmuSP12/bd/Bassdrum-09.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/bd/Bassdrum-10.wav
Normal file
BIN
repl/public/EmuSP12/bd/Bassdrum-10.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/bd/Bassdrum-11.wav
Normal file
BIN
repl/public/EmuSP12/bd/Bassdrum-11.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/bd/Bassdrum-12.wav
Normal file
BIN
repl/public/EmuSP12/bd/Bassdrum-12.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/bd/Bassdrum-13.wav
Normal file
BIN
repl/public/EmuSP12/bd/Bassdrum-13.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/bd/Bassdrum-14.wav
Normal file
BIN
repl/public/EmuSP12/bd/Bassdrum-14.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/cb/Cowbell.wav
Normal file
BIN
repl/public/EmuSP12/cb/Cowbell.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/cp/Clap.wav
Normal file
BIN
repl/public/EmuSP12/cp/Clap.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/cr/Crash.wav
Normal file
BIN
repl/public/EmuSP12/cr/Crash.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/hh/Hat Closed-01.wav
Normal file
BIN
repl/public/EmuSP12/hh/Hat Closed-01.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/hh/Hat Closed-02.wav
Normal file
BIN
repl/public/EmuSP12/hh/Hat Closed-02.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/ht/Tom H-01.wav
Normal file
BIN
repl/public/EmuSP12/ht/Tom H-01.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/ht/Tom H-02.wav
Normal file
BIN
repl/public/EmuSP12/ht/Tom H-02.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/ht/Tom H-03.wav
Normal file
BIN
repl/public/EmuSP12/ht/Tom H-03.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/ht/Tom H-04.wav
Normal file
BIN
repl/public/EmuSP12/ht/Tom H-04.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/ht/Tom H-05.wav
Normal file
BIN
repl/public/EmuSP12/ht/Tom H-05.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/ht/Tom H-06.wav
Normal file
BIN
repl/public/EmuSP12/ht/Tom H-06.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/lt/Tom L-01.wav
Normal file
BIN
repl/public/EmuSP12/lt/Tom L-01.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/lt/Tom L-02.wav
Normal file
BIN
repl/public/EmuSP12/lt/Tom L-02.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/lt/Tom L-03.wav
Normal file
BIN
repl/public/EmuSP12/lt/Tom L-03.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/lt/Tom L-04.wav
Normal file
BIN
repl/public/EmuSP12/lt/Tom L-04.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/lt/Tom L-05.wav
Normal file
BIN
repl/public/EmuSP12/lt/Tom L-05.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/lt/Tom L-06.wav
Normal file
BIN
repl/public/EmuSP12/lt/Tom L-06.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/misc/Metal-01.wav
Normal file
BIN
repl/public/EmuSP12/misc/Metal-01.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/misc/Metal-02.wav
Normal file
BIN
repl/public/EmuSP12/misc/Metal-02.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/misc/Metal-03.wav
Normal file
BIN
repl/public/EmuSP12/misc/Metal-03.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/misc/Scratch.wav
Normal file
BIN
repl/public/EmuSP12/misc/Scratch.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/misc/Shot-01.wav
Normal file
BIN
repl/public/EmuSP12/misc/Shot-01.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/misc/Shot-02.wav
Normal file
BIN
repl/public/EmuSP12/misc/Shot-02.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/misc/Shot-03.wav
Normal file
BIN
repl/public/EmuSP12/misc/Shot-03.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/mt/Tom M-01.wav
Normal file
BIN
repl/public/EmuSP12/mt/Tom M-01.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/mt/Tom M-02.wav
Normal file
BIN
repl/public/EmuSP12/mt/Tom M-02.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/mt/Tom M-03.wav
Normal file
BIN
repl/public/EmuSP12/mt/Tom M-03.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/mt/Tom M-05.wav
Normal file
BIN
repl/public/EmuSP12/mt/Tom M-05.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/oh/Hhopen1.wav
Normal file
BIN
repl/public/EmuSP12/oh/Hhopen1.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/perc/Blow1.wav
Normal file
BIN
repl/public/EmuSP12/perc/Blow1.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/rd/Ride.wav
Normal file
BIN
repl/public/EmuSP12/rd/Ride.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/rim/zRim Shot-01.wav
Normal file
BIN
repl/public/EmuSP12/rim/zRim Shot-01.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/rim/zRim Shot-02.wav
Normal file
BIN
repl/public/EmuSP12/rim/zRim Shot-02.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-01.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-01.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-02.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-02.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-03.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-03.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-04.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-04.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-05.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-05.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-06.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-06.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-07.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-07.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-08.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-08.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-09.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-09.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-10.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-10.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-11.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-11.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-12.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-12.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-13.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-13.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-14.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-14.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-15.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-15.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-16.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-16.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-17.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-17.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-18.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-18.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-19.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-19.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-20.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-20.wav
Normal file
Binary file not shown.
BIN
repl/public/EmuSP12/sd/Snaredrum-21.wav
Normal file
BIN
repl/public/EmuSP12/sd/Snaredrum-21.wav
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user