mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-13 22:35:15 -04:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f0a934e014 | |||
| 328e4db1a2 | |||
| a708265e14 | |||
| a0618f7f47 | |||
| f2bd9fae01 | |||
| fd17377c18 | |||
| 6698fdb6e6 | |||
| e2eab1ee5d | |||
| aee9bf7cc2 | |||
| 63d9d554b9 | |||
| f6c04f9e74 | |||
| e167300194 | |||
| 319400ab13 | |||
| 38b1717fac | |||
| d5938a016c | |||
| 3ac592b18a | |||
| 82d6103907 | |||
| b353db67d4 | |||
| 5334abebaa | |||
| ad053361dc | |||
| ad62ccd8c7 | |||
| ecd93c9a92 | |||
| 774c02afab | |||
| 18f1f7d83d | |||
| 9eb3266891 | |||
| c3a3a68054 | |||
| 925fb30e05 | |||
| 363895761e | |||
| c191accc64 | |||
| 71b6b83ab3 | |||
| d532f8007e | |||
| 44574af3eb | |||
| faa4019b27 | |||
| c1e934d171 | |||
| 5fd88f9213 | |||
| 2bb095d47d | |||
| a4a30e8b60 | |||
| 4c05876066 | |||
| 23be3e5ba3 | |||
| f69d776cf6 | |||
| 25795eaecc | |||
| 3e1e92a6f1 | |||
| f2f3511a2f | |||
| 0ce70f4d0d | |||
| cac5d96c16 | |||
| 25d6ff1c90 | |||
| 87de8c0800 | |||
| 34ba762b42 | |||
| badc4bcc2f | |||
| 729b4e18ab | |||
| c3f03f50b3 | |||
| 8fd4cfe0a3 | |||
| 8dee07082d | |||
| 11e33f5c70 | |||
| 3d68b0c566 |
@@ -2,7 +2,7 @@
|
||||
|
||||
[](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/>
|
||||
- Tutorial: <https://strudel.tidalcycles.org/tutorial/>
|
||||
|
||||
Generated
+170
-6
@@ -2243,6 +2243,10 @@
|
||||
"resolved": "packages/serial",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@strudel.cycles/soundfonts": {
|
||||
"resolved": "packages/soundfonts",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@strudel.cycles/tonal": {
|
||||
"resolved": "packages/tonal",
|
||||
"link": true
|
||||
@@ -3947,6 +3951,15 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
|
||||
"integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/dateformat": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
|
||||
@@ -4955,6 +4968,29 @@
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz",
|
||||
"integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/figures": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
||||
@@ -5134,6 +5170,18 @@
|
||||
"node": ">= 0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||
@@ -7019,9 +7067,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.0.14.tgz",
|
||||
"integrity": "sha512-HL5sSPE/LP6U9qKgngIIPTthuxC0jrfxpYMZ3LdGDD3vTnLs59m2Z7r6+LNDR3ToqEQdkKd6YaaEfJhodJmijQ==",
|
||||
"version": "4.0.17",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.0.17.tgz",
|
||||
"integrity": "sha512-Wfk0ATOK5iPxM4ptrORkFemqroz0ZDxp5MWfYA7H/F+wO17NRWV5Ypxi6p3g2Xmw2bKeiYOl6oVnLHKxBA0VhA==",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
@@ -7582,6 +7630,25 @@
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
@@ -10819,6 +10886,15 @@
|
||||
"defaults": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
|
||||
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/WebDirt": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "git+ssh://git@github.com/dktr0/WebDirt.git#425dc8fd023440d9c61ffdb8642e44e2710faea0",
|
||||
@@ -11414,6 +11490,36 @@
|
||||
"version": "0.1.0",
|
||||
"license": "AGPL-3.0-or-later"
|
||||
},
|
||||
"packages/soundfonts": {
|
||||
"name": "@strudel.cycles/soundfonts",
|
||||
"version": "0.1.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "*",
|
||||
"@strudel.cycles/webaudio": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"node-fetch": "^3.2.6"
|
||||
}
|
||||
},
|
||||
"packages/soundfonts/node_modules/node-fetch": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.6.tgz",
|
||||
"integrity": "sha512-LAy/HZnLADOVkVPubaxHDft29booGglPFDr2Hw0J1AercRh01UiVFm++KMDnJeH9sHgNB4hsXPii7Sgym/sTbw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"packages/tonal": {
|
||||
"name": "@strudel.cycles/tonal",
|
||||
"version": "0.1.1",
|
||||
@@ -13384,6 +13490,27 @@
|
||||
"@strudel.cycles/serial": {
|
||||
"version": "file:packages/serial"
|
||||
},
|
||||
"@strudel.cycles/soundfonts": {
|
||||
"version": "file:packages/soundfonts",
|
||||
"requires": {
|
||||
"@strudel.cycles/core": "*",
|
||||
"@strudel.cycles/webaudio": "*",
|
||||
"node-fetch": "^3.2.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-fetch": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.6.tgz",
|
||||
"integrity": "sha512-LAy/HZnLADOVkVPubaxHDft29booGglPFDr2Hw0J1AercRh01UiVFm++KMDnJeH9sHgNB4hsXPii7Sgym/sTbw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@strudel.cycles/tonal": {
|
||||
"version": "file:packages/tonal",
|
||||
"requires": {
|
||||
@@ -14808,6 +14935,12 @@
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"data-uri-to-buffer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
|
||||
"integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==",
|
||||
"dev": true
|
||||
},
|
||||
"dateformat": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
|
||||
@@ -15481,6 +15614,16 @@
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"fetch-blob": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz",
|
||||
"integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"figures": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
||||
@@ -15609,6 +15752,15 @@
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"fraction.js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||
@@ -17058,9 +17210,9 @@
|
||||
"requires": {}
|
||||
},
|
||||
"marked": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.0.14.tgz",
|
||||
"integrity": "sha512-HL5sSPE/LP6U9qKgngIIPTthuxC0jrfxpYMZ3LdGDD3vTnLs59m2Z7r6+LNDR3ToqEQdkKd6YaaEfJhodJmijQ=="
|
||||
"version": "4.0.17",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.0.17.tgz",
|
||||
"integrity": "sha512-Wfk0ATOK5iPxM4ptrORkFemqroz0ZDxp5MWfYA7H/F+wO17NRWV5Ypxi6p3g2Xmw2bKeiYOl6oVnLHKxBA0VhA=="
|
||||
},
|
||||
"mdurl": {
|
||||
"version": "1.0.1",
|
||||
@@ -17485,6 +17637,12 @@
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true
|
||||
},
|
||||
"node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
@@ -20029,6 +20187,12 @@
|
||||
"defaults": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"web-streams-polyfill": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
|
||||
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
|
||||
"dev": true
|
||||
},
|
||||
"WebDirt": {
|
||||
"version": "git+ssh://git@github.com/dktr0/WebDirt.git#425dc8fd023440d9c61ffdb8642e44e2710faea0",
|
||||
"from": "WebDirt@github:dktr0/WebDirt"
|
||||
|
||||
+2
-1
@@ -6,7 +6,8 @@
|
||||
"scripts": {
|
||||
"test": "npm run test --workspaces --if-present && cd repl && npm run test",
|
||||
"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",
|
||||
"snapshot": "cd repl && npm run snapshot",
|
||||
"repl": "cd repl && npm run dev",
|
||||
"osc": "cd packages/osc && npm run server",
|
||||
"build": "rm -rf out && cd repl && npm run build && cd ../tutorial && npm run build",
|
||||
|
||||
@@ -750,19 +750,23 @@ const generic_params = [
|
||||
|
||||
const _name = (name, ...pats) => sequence(...pats).withValue((x) => ({ [name]: x }));
|
||||
|
||||
const _setter = (func) =>
|
||||
const _setter = (func, name) =>
|
||||
function (...pats) {
|
||||
if (!pats.length) {
|
||||
return this.fmap((value) => ({ [name]: value }));
|
||||
}
|
||||
return this.set(func(...pats));
|
||||
};
|
||||
|
||||
generic_params.forEach(([type, name, description]) => {
|
||||
controls[name] = (...pats) => _name(name, ...pats);
|
||||
Pattern.prototype[name] = _setter(controls[name]);
|
||||
Pattern.prototype[name] = _setter(controls[name], name);
|
||||
});
|
||||
|
||||
// create custom param
|
||||
controls.createParam = (name) => {
|
||||
Pattern.prototype[name] = _setter(controls[name]);
|
||||
const func = (...pats) => _name(name, ...pats);
|
||||
Pattern.prototype[name] = _setter(func, name);
|
||||
return (...pats) => _name(name, ...pats);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
// this is a shortcut to eval code from a gist
|
||||
// why? to be able to shorten strudel code + e.g. be able to change instruments after links have been generated
|
||||
export default (route) =>
|
||||
fetch(`https://gist.githubusercontent.com/${route}?cachebust=${Date.now()}`)
|
||||
export default (route, cache = true) =>
|
||||
fetch(`https://gist.githubusercontent.com/${route}?cachebust=${cache ? '' : Date.now()}`)
|
||||
.then((res) => res.text())
|
||||
.then((code) => eval(code));
|
||||
|
||||
@@ -86,11 +86,9 @@ export class Hap {
|
||||
}
|
||||
|
||||
showWhole() {
|
||||
return `${this.whole == undefined ? '~' : this.whole.show()}: ${this.value}`;
|
||||
}
|
||||
|
||||
showWhole() {
|
||||
return `${this.whole == undefined ? '~' : this.whole.show()}: ${this.value}`;
|
||||
return `${this.whole == undefined ? '~' : this.whole.show()}: ${
|
||||
typeof this.value === 'object' ? JSON.stringify(this.value) : this.value
|
||||
}`;
|
||||
}
|
||||
|
||||
combineContext(b) {
|
||||
|
||||
@@ -15,5 +15,5 @@ export * from './state.mjs';
|
||||
export * from './timespan.mjs';
|
||||
export * from './util.mjs';
|
||||
export * from './speak.mjs';
|
||||
export * as gist from './gist.js';
|
||||
export { default as gist } from './gist.js';
|
||||
// export * from './value.mjs';
|
||||
|
||||
Generated
+3
-3
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@strudel.cycles/core",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@strudel.cycles/core",
|
||||
"version": "0.0.3",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"version": "0.1.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"bjork": "^0.0.1",
|
||||
"fraction.js": "^4.2.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/core",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.2",
|
||||
"description": "Port of Tidal Cycles to JavaScript",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
@@ -1049,6 +1049,9 @@ export class Pattern {
|
||||
.unit('c')
|
||||
.slow(factor);
|
||||
}
|
||||
onTrigger(onTrigger) {
|
||||
return this._withHap((hap) => hap.setContext({ ...hap.context, onTrigger }));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - adopt value.mjs fully..
|
||||
@@ -1386,6 +1389,7 @@ export function pr(args) {
|
||||
}
|
||||
|
||||
export const add = curry((a, pat) => pat.add(a));
|
||||
export const chop = curry((a, pat) => pat.chop(a))
|
||||
export const chunk = curry((a, pat) => pat.chunk(a));
|
||||
export const chunkBack = curry((a, pat) => pat.chunkBack(a));
|
||||
export const div = curry((a, pat) => pat.div(a));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/embed",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"description": "Embeddable Web Component to load a Strudel REPL into an iframe",
|
||||
"main": "embed.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -15,9 +15,11 @@ npm i @strudel.cycles/eval --save
|
||||
|
||||
```js
|
||||
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) {
|
||||
const { pattern } = await evaluate(code);
|
||||
|
||||
Generated
+3
-3
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@strudel.cycles/eval",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@strudel.cycles/eval",
|
||||
"version": "0.0.3",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"version": "0.1.1",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"estraverse": "^5.3.0",
|
||||
"shift-ast": "^6.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/eval",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"description": "Code evaluator for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
@@ -28,7 +28,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "^0.1.0",
|
||||
"@strudel.cycles/core": "^0.1.2",
|
||||
"estraverse": "^5.3.0",
|
||||
"shift-ast": "^6.1.0",
|
||||
"shift-codegen": "^7.0.3",
|
||||
|
||||
Generated
+3
-3
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@strudel.cycles/midi",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@strudel.cycles/midi",
|
||||
"version": "0.0.4",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"version": "0.1.1",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"tone": "^14.7.77",
|
||||
"webmidi": "^2.5.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/midi",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"description": "Midi API for strudel",
|
||||
"main": "index.mjs",
|
||||
"repository": {
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/tone": "^0.1.1",
|
||||
"@strudel.cycles/tone": "^0.1.3",
|
||||
"tone": "^14.7.77",
|
||||
"webmidi": "^2.5.2"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/mini",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"description": "Mini notation for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
@@ -25,8 +25,8 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "^0.1.0",
|
||||
"@strudel.cycles/eval": "^0.1.1",
|
||||
"@strudel.cycles/tone": "^0.1.1"
|
||||
"@strudel.cycles/core": "^0.1.2",
|
||||
"@strudel.cycles/eval": "^0.1.3",
|
||||
"@strudel.cycles/tone": "^0.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,15 @@ let startedAt = -1;
|
||||
*/
|
||||
Pattern.prototype.osc = function () {
|
||||
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
|
||||
// currentTime should be current time of audio context (slightly before time)
|
||||
if (startedAt < 0) {
|
||||
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 ts = Math.floor(startedAt + (time + latency) * 1000);
|
||||
const message = new OSC.Message('/dirt/play', ...keyvals);
|
||||
|
||||
Generated
+3
-3
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@strudel.cycles/osc",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@strudel.cycles/osc",
|
||||
"version": "0.0.1",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"version": "0.1.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"osc-js": "^2.3.2"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/osc",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"description": "OSC messaging for strudel",
|
||||
"main": "osc.mjs",
|
||||
"scripts": {
|
||||
|
||||
Generated
+3984
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/react",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.4",
|
||||
"description": "React components for strudel",
|
||||
"main": "dist/index.cjs.js",
|
||||
"module": "dist/index.es.js",
|
||||
@@ -38,9 +38,9 @@
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-javascript": "^0.19.0",
|
||||
"@strudel.cycles/core": "*",
|
||||
"@strudel.cycles/eval": "^0.1.1",
|
||||
"@strudel.cycles/tone": "^0.1.1",
|
||||
"@strudel.cycles/core": "^0.1.2",
|
||||
"@strudel.cycles/eval": "^0.1.3",
|
||||
"@strudel.cycles/tone": "^0.1.3",
|
||||
"react-codemirror6": "^1.1.0",
|
||||
"react-hook-inview": "^4.5.0"
|
||||
},
|
||||
|
||||
@@ -57,14 +57,7 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawP
|
||||
/* console.warn('no instrument chosen', event);
|
||||
throw new Error(`no instrument chosen for ${JSON.stringify(event)}`); */
|
||||
} else {
|
||||
onTrigger(
|
||||
time,
|
||||
event,
|
||||
currentTime,
|
||||
1 /* cps */,
|
||||
event.wholeOrPart().begin.valueOf(),
|
||||
event.duration.valueOf(),
|
||||
);
|
||||
onTrigger(time, event, currentTime, 1 /* cps */);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/serial",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.3",
|
||||
"description": "Webserial API for strudel",
|
||||
"main": "serial.mjs",
|
||||
"repository": {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Pattern, isPattern } from '@strudel.cycles/core';
|
||||
var serialWriter;
|
||||
var choosing = false;
|
||||
|
||||
export async function getWriter(br=115200) {
|
||||
export async function getWriter(br=38400) {
|
||||
if (choosing) {
|
||||
return;
|
||||
}
|
||||
@@ -34,8 +34,7 @@ export async function getWriter(br=115200) {
|
||||
|
||||
const latency = 0.1;
|
||||
|
||||
// Pattern.prototype.midi = function (output: string | number, channel = 1) {
|
||||
Pattern.prototype.serial = async function (...args) {
|
||||
Pattern.prototype.serial = function (...args) {
|
||||
return this._withHap((hap) => {
|
||||
if (!serialWriter) {
|
||||
getWriter(...args);
|
||||
@@ -43,8 +42,27 @@ Pattern.prototype.serial = async function (...args) {
|
||||
const onTrigger = (time, hap, currentTime) => {
|
||||
var message = "";
|
||||
if (typeof hap.value === 'object') {
|
||||
for (const [key, val] of Object.entries(hap.value).flat()) {
|
||||
message += `${key}:${val};`
|
||||
if ('action' in hap.value) {
|
||||
message += hap.value['action'] + '(';
|
||||
var first = true;
|
||||
for (const [key, val] of Object.entries(hap.value)) {
|
||||
if (key === 'action') {
|
||||
continue;
|
||||
}
|
||||
if (first) {
|
||||
first = false;
|
||||
}
|
||||
else {
|
||||
message +=',';
|
||||
}
|
||||
message += `${key}:${val}`
|
||||
}
|
||||
message += ')';
|
||||
}
|
||||
else {
|
||||
for (const [key, val] of Object.entries(hap.value)) {
|
||||
message += `${key}:${val};`
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// this script converts a soundfont into a json file, it has not been not used yet
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
const name = '0000_JCLive';
|
||||
|
||||
const js = await fetch(`https://felixroos.github.io/webaudiofontdata/sound/${name}_sf2_file.js`).then((res) =>
|
||||
res.text(),
|
||||
);
|
||||
// console.log(js);
|
||||
|
||||
let [_, data] = js.split('_sf2_file=');
|
||||
data = eval(data);
|
||||
console.log(JSON.stringify(data));
|
||||
@@ -0,0 +1,111 @@
|
||||
let loadCache = {};
|
||||
async function loadFont(name) {
|
||||
if (loadCache[name]) {
|
||||
return loadCache[name];
|
||||
}
|
||||
const load = async () => {
|
||||
// TODO: make soundfont source configurable
|
||||
const url = `https://felixroos.github.io/webaudiofontdata/sound/${name}.js`;
|
||||
console.log('load font', name, url);
|
||||
const preset = await fetch(url).then((res) => res.text());
|
||||
let [_, data] = preset.split('={');
|
||||
return eval('{' + data);
|
||||
};
|
||||
loadCache[name] = load();
|
||||
return loadCache[name];
|
||||
}
|
||||
|
||||
export async function getFontBufferSource(name, pitch, ac) {
|
||||
const { buffer, zone } = await getFontPitch(name, pitch, ac);
|
||||
const src = ac.createBufferSource();
|
||||
src.buffer = buffer;
|
||||
const baseDetune = zone.originalPitch - 100.0 * zone.coarseTune - zone.fineTune;
|
||||
const playbackRate = 1.0 * Math.pow(2, (100.0 * pitch - baseDetune) / 1200.0);
|
||||
// src detune?
|
||||
src.playbackRate.value = playbackRate;
|
||||
const loop = zone.loopStart > 1 && zone.loopStart < zone.loopEnd;
|
||||
if (!loop) {
|
||||
/* const waveDuration = duration + this.afterTime;
|
||||
if (waveDuration > zone.buffer.duration / playbackRate) {
|
||||
waveDuration = zone.buffer.duration / playbackRate;
|
||||
// TODO: do sth with waveduration
|
||||
} */
|
||||
} else {
|
||||
src.loop = true;
|
||||
src.loopStart = zone.loopStart / zone.sampleRate;
|
||||
src.loopEnd = zone.loopEnd / zone.sampleRate;
|
||||
//+ (zone.delay ? zone.delay : 0);
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
let bufferCache = {};
|
||||
export async function getFontPitch(name, pitch, ac) {
|
||||
const key = `${name}:::${pitch}`;
|
||||
if (bufferCache[key]) {
|
||||
return bufferCache[key];
|
||||
}
|
||||
// console.log('load buffer', key);
|
||||
const load = async () => {
|
||||
const preset = await loadFont(name);
|
||||
if (!preset) {
|
||||
throw new Error(`Could not load soundfont ${name}`);
|
||||
}
|
||||
const zone = findZone(preset, pitch);
|
||||
if (!zone) {
|
||||
throw new Error('no soundfont zone found for preset ', name, 'pitch', pitch);
|
||||
}
|
||||
const buffer = await getBuffer(zone, ac);
|
||||
if (!buffer) {
|
||||
throw new Error(`no soundfont buffer found for preset ${name}, pitch: ${pitch}`);
|
||||
}
|
||||
return { buffer, zone };
|
||||
};
|
||||
bufferCache[key] = load(); // dont await here to cache promise immediately!
|
||||
return bufferCache[key];
|
||||
}
|
||||
|
||||
function findZone(preset, pitch) {
|
||||
return preset.find((zone) => {
|
||||
return zone.keyRangeLow <= pitch && zone.keyRangeHigh + 1 >= pitch;
|
||||
});
|
||||
}
|
||||
|
||||
// promisified version of https://github.com/felixroos/webaudiofont/blob/c6f97249b60dcfafc20fca5bb381294a6b2f8f51/npm/dist/WebAudioFontPlayer.js#L740
|
||||
async function getBuffer(zone, audioContext) {
|
||||
if (zone.sample) {
|
||||
console.warn('zone.sample untested!');
|
||||
const decoded = atob(zone.sample);
|
||||
zone.buffer = audioContext.createBuffer(1, decoded.length / 2, zone.sampleRate);
|
||||
const float32Array = zone.buffer.getChannelData(0);
|
||||
let b1, b2, n;
|
||||
for (var i = 0; i < decoded.length / 2; i++) {
|
||||
b1 = decoded.charCodeAt(i * 2);
|
||||
b2 = decoded.charCodeAt(i * 2 + 1);
|
||||
if (b1 < 0) {
|
||||
b1 = 256 + b1;
|
||||
}
|
||||
if (b2 < 0) {
|
||||
b2 = 256 + b2;
|
||||
}
|
||||
n = b2 * 256 + b1;
|
||||
if (n >= 65536 / 2) {
|
||||
n = n - 65536;
|
||||
}
|
||||
float32Array[i] = n / 65536.0;
|
||||
}
|
||||
} else {
|
||||
if (zone.file) {
|
||||
const datalen = zone.file.length;
|
||||
const arraybuffer = new ArrayBuffer(datalen);
|
||||
const view = new Uint8Array(arraybuffer);
|
||||
const decoded = atob(zone.file);
|
||||
let b;
|
||||
for (let i = 0; i < decoded.length; i++) {
|
||||
b = decoded.charCodeAt(i);
|
||||
view[i] = b;
|
||||
}
|
||||
return new Promise((resolve) => audioContext.decodeAudioData(arraybuffer, resolve));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { getFontBufferSource } from './fontloader.mjs';
|
||||
import * as soundfontList from './list.mjs';
|
||||
|
||||
globalThis.getFontBufferSource = getFontBufferSource;
|
||||
globalThis.soundfontList = soundfontList;
|
||||
globalThis.soundfontList = soundfontList;
|
||||
File diff suppressed because it is too large
Load Diff
Generated
+301
@@ -0,0 +1,301 @@
|
||||
{
|
||||
"name": "@strudel.cycles/soundfonts",
|
||||
"version": "0.1.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@strudel.cycles/soundfonts",
|
||||
"version": "0.1.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"webaudiofont": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "8.0.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz",
|
||||
"integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^5.0.1",
|
||||
"once": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/jsonc-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA=="
|
||||
},
|
||||
"node_modules/lunr": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
|
||||
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "4.0.17",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.0.17.tgz",
|
||||
"integrity": "sha512-Wfk0ATOK5iPxM4ptrORkFemqroz0ZDxp5MWfYA7H/F+wO17NRWV5Ypxi6p3g2Xmw2bKeiYOl6oVnLHKxBA0VhA==",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
||||
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/shiki": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz",
|
||||
"integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==",
|
||||
"dependencies": {
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"vscode-oniguruma": "^1.6.1",
|
||||
"vscode-textmate": "5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc": {
|
||||
"version": "0.22.17",
|
||||
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.17.tgz",
|
||||
"integrity": "sha512-h6+uXHVVCPDaANzjwzdsj9aePBjZiBTpiMpBBeyh1zcN2odVsDCNajz8zyKnixF93HJeGpl34j/70yoEE5BfNg==",
|
||||
"dependencies": {
|
||||
"glob": "^8.0.3",
|
||||
"lunr": "^2.3.9",
|
||||
"marked": "^4.0.16",
|
||||
"minimatch": "^5.1.0",
|
||||
"shiki": "^0.10.1"
|
||||
},
|
||||
"bin": {
|
||||
"typedoc": "bin/typedoc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x || 4.7.x"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
||||
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-oniguruma": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz",
|
||||
"integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA=="
|
||||
},
|
||||
"node_modules/vscode-textmate": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz",
|
||||
"integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ=="
|
||||
},
|
||||
"node_modules/webaudiofont": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webaudiofont/-/webaudiofont-3.0.1.tgz",
|
||||
"integrity": "sha512-PwqbR4OdvzbW8/TLTitSU/rw4mxMKyAGTKV42mePCgMoUGLJoQj8HDLWVqtNOpUZ0ghPNhNE6cRFEv6HFFEBQA==",
|
||||
"dependencies": {
|
||||
"typedoc": "^0.22.14"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
|
||||
},
|
||||
"glob": {
|
||||
"version": "8.0.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz",
|
||||
"integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==",
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^5.0.1",
|
||||
"once": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"jsonc-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA=="
|
||||
},
|
||||
"lunr": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
|
||||
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="
|
||||
},
|
||||
"marked": {
|
||||
"version": "4.0.17",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.0.17.tgz",
|
||||
"integrity": "sha512-Wfk0ATOK5iPxM4ptrORkFemqroz0ZDxp5MWfYA7H/F+wO17NRWV5Ypxi6p3g2Xmw2bKeiYOl6oVnLHKxBA0VhA=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
||||
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
|
||||
"requires": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"shiki": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz",
|
||||
"integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==",
|
||||
"requires": {
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"vscode-oniguruma": "^1.6.1",
|
||||
"vscode-textmate": "5.2.0"
|
||||
}
|
||||
},
|
||||
"typedoc": {
|
||||
"version": "0.22.17",
|
||||
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.17.tgz",
|
||||
"integrity": "sha512-h6+uXHVVCPDaANzjwzdsj9aePBjZiBTpiMpBBeyh1zcN2odVsDCNajz8zyKnixF93HJeGpl34j/70yoEE5BfNg==",
|
||||
"requires": {
|
||||
"glob": "^8.0.3",
|
||||
"lunr": "^2.3.9",
|
||||
"marked": "^4.0.16",
|
||||
"minimatch": "^5.1.0",
|
||||
"shiki": "^0.10.1"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
||||
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
|
||||
"peer": true
|
||||
},
|
||||
"vscode-oniguruma": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz",
|
||||
"integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA=="
|
||||
},
|
||||
"vscode-textmate": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz",
|
||||
"integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ=="
|
||||
},
|
||||
"webaudiofont": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webaudiofont/-/webaudiofont-3.0.1.tgz",
|
||||
"integrity": "sha512-PwqbR4OdvzbW8/TLTitSU/rw4mxMKyAGTKV42mePCgMoUGLJoQj8HDLWVqtNOpUZ0ghPNhNE6cRFEv6HFFEBQA==",
|
||||
"requires": {
|
||||
"typedoc": "^0.22.14"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@strudel.cycles/soundfonts",
|
||||
"version": "0.1.1",
|
||||
"description": "Soundsfont support 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": "*",
|
||||
"@strudel.cycles/webaudio": "^0.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"node-fetch": "^3.2.6"
|
||||
}
|
||||
}
|
||||
Generated
+3
-3
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@strudel.cycles/tonal",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@strudel.cycles/tonal",
|
||||
"version": "0.0.3",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"version": "0.1.1",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@tonaljs/tonal": "^4.6.5",
|
||||
"webmidi": "^3.0.15"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/tonal",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"description": "Tonal functions for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "^0.1.0",
|
||||
"@strudel.cycles/core": "^0.1.2",
|
||||
"@tonaljs/tonal": "^4.6.5",
|
||||
"webmidi": "^3.0.15"
|
||||
}
|
||||
|
||||
Generated
+3
-3
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@strudel.cycles/tone",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@strudel.cycles/tone",
|
||||
"version": "0.0.4",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"version": "0.1.1",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@tonejs/piano": "^0.2.1",
|
||||
"chord-voicings": "^0.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/tone",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"description": "Tone.js API for strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "^0.1.0",
|
||||
"@strudel.cycles/core": "^0.1.2",
|
||||
"@tonejs/piano": "^0.2.1",
|
||||
"chord-voicings": "^0.0.1",
|
||||
"tone": "^14.7.77"
|
||||
|
||||
@@ -7,3 +7,4 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
export * from './clockworker.mjs';
|
||||
export * from './scheduler.mjs';
|
||||
export * from './webaudio.mjs';
|
||||
export * from './sampler.mjs';
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/webaudio",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/webaudio",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.4",
|
||||
"description": "Web Audio helpers for Strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
@@ -28,6 +28,6 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "^0.1.0"
|
||||
"@strudel.cycles/core": "^0.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
||||
const loadCache = {}; // string: Promise<ArrayBuffer>
|
||||
|
||||
export const getCachedBuffer = (url) => bufferCache[url];
|
||||
|
||||
export const loadBuffer = (url, ac) => {
|
||||
if (!loadCache[url]) {
|
||||
loadCache[url] = fetch(url)
|
||||
@@ -91,7 +93,7 @@ export const loadGithubSamples = async (path, nameFn) => {
|
||||
*
|
||||
*/
|
||||
|
||||
export const samples = (sampleMap, baseUrl = '') => {
|
||||
export const samples = (sampleMap, baseUrl = sampleMap._base) => {
|
||||
sampleCache.current = {
|
||||
...sampleCache.current,
|
||||
...Object.fromEntries(
|
||||
@@ -10,18 +10,20 @@ import { State, TimeSpan } from '@strudel.cycles/core';
|
||||
export class Scheduler {
|
||||
worker;
|
||||
pattern;
|
||||
constructor({ audioContext, interval = 0.2, onEvent }) {
|
||||
constructor({ audioContext, interval = 0.2, onEvent, latency = 0.2 }) {
|
||||
this.worker = new ClockWorker(
|
||||
audioContext,
|
||||
(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)) {
|
||||
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) {
|
||||
onEvent?.(e);
|
||||
} else {
|
||||
console.warn('unplayable event: no audio node nor onEvent callback', e);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
+176
-56
@@ -6,8 +6,11 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
// import { Pattern, getFrequency, patternify2 } from '@strudel.cycles/core';
|
||||
import * as strudel from '@strudel.cycles/core';
|
||||
import { Tone } from '@strudel.cycles/tone';
|
||||
const { Pattern, getFrequency, patternify2 } = strudel;
|
||||
import { fromMidi } from '@strudel.cycles/core';
|
||||
import { loadBuffer } from './sampler.mjs';
|
||||
const { Pattern } = strudel;
|
||||
|
||||
// export const getAudioContext = () => Tone.getContext().rawContext;
|
||||
|
||||
let audioContext;
|
||||
export const getAudioContext = () => {
|
||||
@@ -17,9 +20,15 @@ export const getAudioContext = () => {
|
||||
return audioContext;
|
||||
};
|
||||
|
||||
const lookahead = 0.2;
|
||||
const getFilter = (type, frequency, Q) => {
|
||||
const filter = getAudioContext().createBiquadFilter();
|
||||
filter.type = type;
|
||||
filter.frequency.value = frequency;
|
||||
filter.Q.value = Q;
|
||||
return filter;
|
||||
};
|
||||
|
||||
const adsr = (attack, decay, sustain, release, velocity, begin, end) => {
|
||||
const getADSR = (attack, decay, sustain, release, velocity, begin, end) => {
|
||||
const gainNode = getAudioContext().createGain();
|
||||
gainNode.gain.setValueAtTime(0, begin);
|
||||
gainNode.gain.linearRampToValueAtTime(velocity, begin + attack); // attack
|
||||
@@ -30,65 +39,176 @@ const adsr = (attack, decay, sustain, release, velocity, begin, end) => {
|
||||
return gainNode;
|
||||
};
|
||||
|
||||
Pattern.prototype.withAudioNode = function (createAudioNode) {
|
||||
return this._withHap((hap) => {
|
||||
return hap.setContext({
|
||||
...hap.context,
|
||||
createAudioNode: (t, e) => createAudioNode(t, e, hap.context.createAudioNode?.(t, hap)),
|
||||
});
|
||||
});
|
||||
const getOscillator = ({ s, freq, t, duration, release }) => {
|
||||
// make oscillator
|
||||
const o = getAudioContext().createOscillator();
|
||||
o.type = s || 'triangle';
|
||||
o.frequency.value = Number(freq);
|
||||
o.start(t);
|
||||
o.stop(t + duration + release);
|
||||
return o;
|
||||
};
|
||||
|
||||
Pattern.prototype._wave = function (type) {
|
||||
return this.withAudioNode((t, e) => {
|
||||
const osc = getAudioContext().createOscillator();
|
||||
osc.type = type;
|
||||
const f = getFrequency(e);
|
||||
osc.frequency.value = f; // expects frequency..
|
||||
const begin = t ?? e.whole.begin.valueOf() + lookahead;
|
||||
const end = begin + e.duration.valueOf();
|
||||
osc.start(begin);
|
||||
osc.stop(end); // release?
|
||||
return osc;
|
||||
});
|
||||
};
|
||||
Pattern.prototype.adsr = function (a = 0.01, d = 0.05, s = 1, r = 0.01) {
|
||||
return this.withAudioNode((t, e, node) => {
|
||||
const velocity = e.context?.velocity || 1;
|
||||
const begin = t ?? e.whole.begin.valueOf() + lookahead;
|
||||
const end = begin + e.duration.valueOf() + lookahead;
|
||||
const envelope = adsr(a, d, s, r, velocity, begin, end);
|
||||
node?.connect(envelope);
|
||||
return envelope;
|
||||
});
|
||||
};
|
||||
Pattern.prototype._filter = function (type = 'lowpass', frequency = 1000) {
|
||||
return this.withAudioNode((t, e, node) => {
|
||||
const filter = getAudioContext().createBiquadFilter();
|
||||
filter.type = type;
|
||||
filter.frequency.value = frequency;
|
||||
node?.connect(filter);
|
||||
return filter;
|
||||
});
|
||||
const getSoundfontKey = (s) => {
|
||||
if (!globalThis.soundfontList) {
|
||||
// soundfont package not loaded
|
||||
return false;
|
||||
}
|
||||
if (globalThis.soundfontList?.instruments?.includes(s)) {
|
||||
return s;
|
||||
}
|
||||
// check if s is one of the soundfonts, which are loaded into globalThis, to avoid coupling both packages
|
||||
const nameIndex = globalThis.soundfontList?.instrumentNames?.indexOf(s);
|
||||
// convert number nameIndex (0-128) to 3 digit string (001-128)
|
||||
const name = nameIndex < 10 ? `00${nameIndex}` : nameIndex < 100 ? `0${nameIndex}` : nameIndex;
|
||||
if (nameIndex !== -1) {
|
||||
// TODO: indices of instrumentNames do not seem to match instruments
|
||||
return globalThis.soundfontList.instruments.find((instrument) => instrument.startsWith(name));
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
Pattern.prototype.filter = function (type, frequency) {
|
||||
return patternify2(Pattern.prototype._filter)(reify(type), reify(frequency), this);
|
||||
const getSampleBufferSource = async (s, n) => {
|
||||
const ac = getAudioContext();
|
||||
// is sample from loaded samples(..)
|
||||
const samples = getLoadedSamples();
|
||||
if (!samples) {
|
||||
throw new Error('no samples loaded');
|
||||
}
|
||||
const bank = samples?.[s];
|
||||
if (!bank) {
|
||||
throw new Error('sample not found:', s, 'try one of ' + Object.keys(samples));
|
||||
}
|
||||
const sampleUrl = bank[n % bank.length];
|
||||
const buffer = await loadBuffer(sampleUrl, ac);
|
||||
const bufferSource = ac.createBufferSource();
|
||||
bufferSource.buffer = buffer;
|
||||
return bufferSource;
|
||||
};
|
||||
|
||||
Pattern.prototype.out = function () {
|
||||
const master = getAudioContext().createGain();
|
||||
master.gain.value = 0.1;
|
||||
master.connect(getAudioContext().destination);
|
||||
return this.withAudioNode((t, e, node) => {
|
||||
if (!node) {
|
||||
console.warn('out: no source! call .osc() first');
|
||||
return this.onTrigger(async (t, hap, ct) => {
|
||||
const ac = getAudioContext();
|
||||
// calculate correct time (tone.js workaround)
|
||||
t = ac.currentTime + t - ct;
|
||||
// destructure value
|
||||
let {
|
||||
freq,
|
||||
s,
|
||||
sf,
|
||||
n = 0,
|
||||
gain = 1,
|
||||
cutoff,
|
||||
resonance = 1,
|
||||
hcutoff,
|
||||
hresonance = 1,
|
||||
bandf,
|
||||
bandq = 1,
|
||||
pan,
|
||||
attack = 0.001,
|
||||
decay = 0,
|
||||
sustain = 1,
|
||||
release = 0.001,
|
||||
speed = 1, // sample playback speed
|
||||
begin = 0,
|
||||
end = 1,
|
||||
} = hap.value;
|
||||
// the chain will hold all audio nodes that connect to each other
|
||||
const chain = [];
|
||||
if (typeof n === 'string') {
|
||||
n = toMidi(n); // e.g. c3 => 48
|
||||
}
|
||||
node?.connect(master);
|
||||
})._withHap((hap) => {
|
||||
const onTrigger = (time, e) => e.context?.createAudioNode?.(time, e);
|
||||
return hap.setContext({ ...hap.context, onTrigger });
|
||||
if (!s || ['sine', 'square', 'triangle', 'sawtooth'].includes(s)) {
|
||||
// get frequency
|
||||
if (!freq && typeof n === 'number') {
|
||||
freq = fromMidi(n); // + 48);
|
||||
}
|
||||
// make oscillator
|
||||
const o = getOscillator({ t, s, freq, duration: hap.duration, release });
|
||||
chain.push(o);
|
||||
// level down oscillators as they are really loud compared to samples i've tested
|
||||
const g = ac.createGain();
|
||||
g.gain.value = 0.5;
|
||||
chain.push(g);
|
||||
// TODO: make adsr work with samples without pops
|
||||
// envelope
|
||||
const adsr = getADSR(attack, decay, sustain, release, 1, t, t + hap.duration);
|
||||
chain.push(adsr);
|
||||
} else {
|
||||
// load sample
|
||||
if (speed === 0) {
|
||||
// no playback
|
||||
return;
|
||||
}
|
||||
if (!s) {
|
||||
console.warn('no sample specified');
|
||||
return;
|
||||
}
|
||||
const soundfont = getSoundfontKey(s);
|
||||
let bufferSource;
|
||||
|
||||
try {
|
||||
if (soundfont) {
|
||||
// is soundfont
|
||||
bufferSource = await globalThis.getFontBufferSource(soundfont, n, ac);
|
||||
} else {
|
||||
// is sample from loaded samples(..)
|
||||
bufferSource = await getSampleBufferSource(s, n);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
return;
|
||||
}
|
||||
// asny stuff above took too long?
|
||||
if (ac.currentTime > t) {
|
||||
console.warn('sample still loading:', s, n);
|
||||
return;
|
||||
}
|
||||
if (!bufferSource) {
|
||||
console.warn('no buffer source');
|
||||
return;
|
||||
}
|
||||
bufferSource.playbackRate.value = Math.abs(speed) * bufferSource.playbackRate.value;
|
||||
// TODO: nudge, unit, cut, loop
|
||||
let duration = soundfont ? hap.duration : bufferSource.buffer.duration;
|
||||
// let duration = bufferSource.buffer.duration;
|
||||
const offset = begin * duration;
|
||||
duration = ((end - begin) * duration) / Math.abs(speed);
|
||||
if (soundfont) {
|
||||
bufferSource.start(t, offset); // duration does not work here for some reason
|
||||
} else {
|
||||
bufferSource.start(t, offset, duration);
|
||||
}
|
||||
bufferSource.stop(t + duration);
|
||||
chain.push(bufferSource);
|
||||
if (soundfont) {
|
||||
const env = ac.createGain();
|
||||
env.gain.value = 1;
|
||||
const fadeLength = 0.1;
|
||||
env.gain.value = 0.5;
|
||||
env.gain.setValueAtTime(0.5, t + duration - fadeLength);
|
||||
env.gain.linearRampToValueAtTime(0, t + duration);
|
||||
chain.push(env);
|
||||
}
|
||||
}
|
||||
// filters
|
||||
cutoff !== undefined && chain.push(getFilter('lowpass', cutoff, resonance));
|
||||
hcutoff !== undefined && chain.push(getFilter('highpass', hcutoff, hresonance));
|
||||
bandf !== undefined && chain.push(getFilter('bandpass', bandf, bandq));
|
||||
// TODO vowel
|
||||
// TODO delay / delaytime / delayfeedback
|
||||
// panning
|
||||
if (pan !== undefined) {
|
||||
const panner = ac.createStereoPanner();
|
||||
panner.pan.value = 2 * pan - 1;
|
||||
chain.push(panner);
|
||||
}
|
||||
// master out
|
||||
const master = ac.createGain();
|
||||
master.gain.value = 0.8 * gain;
|
||||
chain.push(master);
|
||||
chain.push(ac.destination);
|
||||
// connect chain elements together
|
||||
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
|
||||
});
|
||||
};
|
||||
|
||||
Pattern.prototype.define('wave', (type, pat) => pat.wave(type), { patternified: true });
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from './webdirt.mjs';
|
||||
export * from './sampler.mjs';
|
||||
|
||||
Generated
+28
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@strudel.cycles/webdirt",
|
||||
"version": "0.1.2",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/webdirt",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.2",
|
||||
"description": "WebDirt integration for Strudel",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "^0.1.0",
|
||||
"@strudel.cycles/core": "^0.1.2",
|
||||
"WebDirt": "github:dktr0/WebDirt"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as strudel from '@strudel.cycles/core';
|
||||
const { Pattern } = strudel;
|
||||
import * as WebDirt from 'WebDirt';
|
||||
import { getLoadedSamples, loadBuffer, getLoadedBuffer } from './sampler.mjs';
|
||||
import { getLoadedSamples, loadBuffer, getLoadedBuffer } from '@strudel.cycles/webaudio';
|
||||
|
||||
let webDirt;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strudel.cycles/xen",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"description": "Xenharmonic API for strudel",
|
||||
"main": "index.mjs",
|
||||
"scripts": {
|
||||
@@ -24,6 +24,6 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "^0.1.0"
|
||||
"@strudel.cycles/core": "^0.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,3 +22,5 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
oldtunes.mjs
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev": "vite --host",
|
||||
"start": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
|
||||
+10
-3
@@ -13,8 +13,8 @@ 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';
|
||||
|
||||
import { loadWebDirt } from '@strudel.cycles/webdirt';
|
||||
import { resetLoadedSamples, getAudioContext } from '@strudel.cycles/webaudio';
|
||||
evalScope(
|
||||
Tone,
|
||||
controls,
|
||||
@@ -29,6 +29,7 @@ evalScope(
|
||||
import('@strudel.cycles/osc'),
|
||||
import('@strudel.cycles/webdirt'),
|
||||
import('@strudel.cycles/serial'),
|
||||
import('@strudel.cycles/soundfonts'),
|
||||
);
|
||||
|
||||
loadWebDirt({
|
||||
@@ -139,7 +140,13 @@ function App() {
|
||||
<h1 className={isEmbedded ? 'text-l' : 'text-xl'}>Strudel {isEmbedded ? 'Mini ' : ''}REPL</h1>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<button onClick={() => togglePlay()} className={cx('hover:bg-gray-300', !isEmbedded ? 'p-2' : 'px-2')}>
|
||||
<button
|
||||
onClick={() => {
|
||||
getAudioContext().resume(); // fixes no sound in ios webkit
|
||||
togglePlay();
|
||||
}}
|
||||
className={cx('hover:bg-gray-300', !isEmbedded ? 'p-2' : 'px-2')}
|
||||
>
|
||||
{!pending ? (
|
||||
<span className={cx('flex items-center', isEmbedded ? 'w-16' : 'w-16')}>
|
||||
{cycle.started ? (
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
import { evaluate } from '@strudel.cycles/eval';
|
||||
import { extend } from '@strudel.cycles/eval';
|
||||
import * as strudel from '@strudel.cycles/core';
|
||||
import * as webaudio from '@strudel.cycles/webaudio';
|
||||
import controls from '@strudel.cycles/core/controls.mjs';
|
||||
// import gist from '@strudel.cycles/core/gist.js';
|
||||
import { mini } from '@strudel.cycles/mini/mini.mjs';
|
||||
// import { Tone } from '@strudel.cycles/tone';
|
||||
@@ -83,6 +85,9 @@ const toneHelpersMocked = {
|
||||
strudel.Pattern.prototype.tone = function () {
|
||||
return this;
|
||||
};
|
||||
strudel.Pattern.prototype.webdirt = function () {
|
||||
return this;
|
||||
};
|
||||
|
||||
// draw mock
|
||||
strudel.Pattern.prototype.pianoroll = function () {
|
||||
@@ -116,6 +121,7 @@ const uiHelpersMocked = {
|
||||
backgroundImage: id,
|
||||
};
|
||||
|
||||
|
||||
// TODO: refactor to evalScope
|
||||
extend(
|
||||
// Tone,
|
||||
@@ -123,6 +129,8 @@ extend(
|
||||
strudel.Pattern.prototype.bootstrap(),
|
||||
toneHelpersMocked,
|
||||
uiHelpersMocked,
|
||||
controls,
|
||||
webaudio,
|
||||
/* controls,
|
||||
toneHelpers,
|
||||
voicingHelpers,
|
||||
@@ -181,6 +189,6 @@ export const testCycles = {
|
||||
randomBells: 24,
|
||||
waa: 16,
|
||||
waar: 16,
|
||||
hyperpop: 60,
|
||||
hyperpop: 10,
|
||||
festivalOfFingers3: 16,
|
||||
};
|
||||
|
||||
+175
-24
@@ -728,7 +728,7 @@ bell = bell.chain(vol(0.6).connect(delay),out());
|
||||
.slow(6)
|
||||
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})`;
|
||||
|
||||
export const waa = `"a4 [a3 c3] a3 c3"
|
||||
/* export const waa = `n("a4 [a3 c3] a3 c3")
|
||||
.sub("<7 12>/2")
|
||||
.off(1/8, add("12"))
|
||||
.off(1/4, add("7"))
|
||||
@@ -736,23 +736,37 @@ export const waa = `"a4 [a3 c3] a3 c3"
|
||||
.slow(2)
|
||||
.wave("sawtooth square")
|
||||
.filter('lowpass', "<2000 1000 500>")
|
||||
.out()`;
|
||||
.out()`; */
|
||||
|
||||
export const waar = `"a4 [a3 c3] a3 c3".color('#F9D649')
|
||||
.sub("<7 12 5 12>".slow(2))
|
||||
.off(1/4,x=>x.add(7).color("#FFFFFF #0C3AA1 #C63928"))
|
||||
.off(1/8,x=>x.add(12).color('#215CB6'))
|
||||
.slow(2)
|
||||
.legato(sine.range(0.3, 2).slow(28))
|
||||
.wave("sawtooth square".fast(2))
|
||||
.filter('lowpass', cosine.range(500,4000).slow(16))
|
||||
.out()
|
||||
.pianoroll({minMidi:20,maxMidi:120,background:'#202124'})`;
|
||||
export const waa = `n(
|
||||
"a4 [a3 c3] a3 c3"
|
||||
.sub("<7 12>/2")
|
||||
.off(1/8, add("12"))
|
||||
.off(1/4, add("7"))
|
||||
)
|
||||
.legato(.5)
|
||||
.slow(2)
|
||||
.s("sawtooth square")
|
||||
.cutoff("<2000 1000 500>")
|
||||
.out()
|
||||
`;
|
||||
|
||||
export const waa2 = `n(
|
||||
"a4 [a3 c3] a3 c3"
|
||||
.sub("<7 12 5 12>".slow(2))
|
||||
.off(1/4,x=>x.add(7))
|
||||
.off(1/8,x=>x.add(12))
|
||||
)
|
||||
.slow(2)
|
||||
.legato(sine.range(0.3, 2).slow(28))
|
||||
.s("sawtooth square".fast(2))
|
||||
.cutoff(cosine.range(500,4000).slow(16))
|
||||
.out()`;
|
||||
|
||||
export const hyperpop = `const lfo = cosine.slow(15);
|
||||
const lfo2 = sine.slow(16);
|
||||
const filter1 = x=>x.filter('lowpass', lfo2.range(300,3000));
|
||||
const filter2 = x=>x.filter('highpass', lfo.range(1000,6000)).filter('lowpass',4000)
|
||||
const filter1 = x=>x.cutoff(lfo2.range(300,3000));
|
||||
const filter2 = x=>x.hcutoff(lfo.range(1000,6000)).cutoff(4000)
|
||||
const scales = cat('D3 major', 'G3 major').slow(8)
|
||||
|
||||
const drums = await players({
|
||||
@@ -765,24 +779,30 @@ const drums = await players({
|
||||
|
||||
stack(
|
||||
"-7 0 -7 7".struct("x(5,8,2)").fast(2).sub(7)
|
||||
.scale(scales).wave("sawtooth,square").velocity(.3).adsr(0.01,0.1,.5,0)
|
||||
.scale(scales)
|
||||
.n()
|
||||
.s("sawtooth,square")
|
||||
.gain(.3).attack(0.01).decay(0.1).sustain(.5)
|
||||
.apply(filter1),
|
||||
"~@3 [<2 3>,<4 5>]"
|
||||
.echo(8,1/16,.7)
|
||||
.echo(4,1/16,.7)
|
||||
.scale(scales)
|
||||
.wave('square').velocity(.7).adsr(0.01,0.1,0).apply(filter1),
|
||||
"6 5 4".add(14)
|
||||
.n()
|
||||
.s('square').gain(.7)
|
||||
.attack(0.01).decay(0.1).sustain(0)
|
||||
.apply(filter1),
|
||||
"6 4 2".add(14)
|
||||
.superimpose(sub("5"))
|
||||
.fast(1).euclidLegato(3,8)
|
||||
.mask("<1 0@7>")
|
||||
.fast(2)
|
||||
.echo(32, 1/8, .9)
|
||||
.echo(32, 1/8, .8)
|
||||
.scale(scales)
|
||||
.wave("sawtooth")
|
||||
.velocity(.2)
|
||||
.adsr(.01,.5,0)
|
||||
.n()
|
||||
.s("sawtooth")
|
||||
.gain(sine.range(.1,.4).slow(8))
|
||||
.attack(.001).decay(.2).sustain(0)
|
||||
.apply(filter2)
|
||||
//.echo(4,1/16,.5)
|
||||
).out().stack(
|
||||
stack(
|
||||
"bd <~@7 [~ bd]>".fast(2),
|
||||
@@ -790,7 +810,7 @@ stack(
|
||||
"[~ hh3]*2"
|
||||
).tone(drums.chain(vol(.18),out())).fast(2)
|
||||
).slow(2)
|
||||
|
||||
|
||||
//.pianoroll({minMidi:20, maxMidi:160})
|
||||
// strudel disable-highlighting`;
|
||||
|
||||
@@ -816,3 +836,134 @@ stack(
|
||||
"<Cm7 [Dm7b5 G7b9] Bbm7 [Cm7b5 F7b9]>".fast(2).struct("x ~ x@3 x ~ x ~ ~ ~ x ~ x@3".late(1/8)).early(1/8).slow(2).voicings(),
|
||||
"[~ [0 ~]] 0 [~ [4 ~]] 4".sub(7).restart(scales).scale(scales).early(.25)
|
||||
).tone((await piano()).toDestination()).slow(2)`;
|
||||
|
||||
export const customTrigger = `stack(
|
||||
freq("55 [110,165] 110 [220,275]".mul("<1 <3/4 2/3>>").struct("x(3,8)").layer(x=>x.mul("1.006,.995"))),
|
||||
freq("440(5,8)".legato(.18).mul("<1 3/4 2 2/3>")).gain(perlin.range(.2,.8))
|
||||
).s("<sawtooth square>/2")
|
||||
.onTrigger((t,hap,ct)=>{
|
||||
const ac = Tone.getContext().rawContext;
|
||||
t = ac.currentTime + t - ct;
|
||||
const { freq, s, gain = 1 } = hap.value;
|
||||
const master = ac.createGain();
|
||||
master.gain.value = 0.1 * gain;
|
||||
master.connect(ac.destination);
|
||||
const o = ac.createOscillator();
|
||||
o.type = s || 'triangle';
|
||||
o.frequency.value = Number(freq);
|
||||
o.connect(master);
|
||||
o.start(t);
|
||||
o.stop(t + hap.duration);
|
||||
}).stack(s("bd(3,8),hh*4,~ sd").webdirt())`;
|
||||
|
||||
export const bornagain = `stack(
|
||||
freq("55 [110,165] 110 [220,275]".mul("<1 <3/4 2/3>>").struct("x(3,8)")
|
||||
.layer(x=>x.mul("1.006,.995"))), // detune
|
||||
freq("440(5,8)".legato(.18).mul("<1 3/4 2 2/3>")).gain(perlin.range(.2,.8))
|
||||
).s("<sawtooth square>/2")
|
||||
.cutoff(perlin.range(100,4000).slow(4))
|
||||
.jux(rev)
|
||||
.out()
|
||||
.stack(s("bd(3,8),hh*4,~ sd").webdirt())`;
|
||||
|
||||
export const meltingsubmarine = `samples({
|
||||
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
|
||||
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
|
||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||
stack(
|
||||
s("bd,[~ <sd!3 sd(3,4,2)>],hh(3,4)") // drums
|
||||
.speed(perlin.range(.7,.9)) // random sample speed variation
|
||||
//.hush()
|
||||
,"<a1 b1*2 a1(3,8) e2>" // bassline
|
||||
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
|
||||
.add(perlin.range(0,.5)) // random pitch variation
|
||||
.superimpose(add(.05)) // add second, slightly detuned voice
|
||||
.n() // wrap in "n"
|
||||
.decay(.15).sustain(0) // make each note of equal length
|
||||
.s('sawtooth') // waveform
|
||||
.gain(.4) // turn down
|
||||
.cutoff(sine.slow(7).range(300,5000)) // automate cutoff
|
||||
//.hush()
|
||||
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings() // chords
|
||||
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
||||
.add(perlin.range(0,.5)) // random pitch variation
|
||||
.n() // wrap in "n"
|
||||
.s('sawtooth') // waveform
|
||||
.gain(.16) // turn down
|
||||
.cutoff(500) // fixed cutoff
|
||||
.attack(1) // slowly fade in
|
||||
//.hush()
|
||||
,"a4 c5 <e6 a6>".struct("x(5,8)")
|
||||
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
||||
.add(perlin.range(0,.5)) // random pitch variation
|
||||
.n() // wrap in "n"
|
||||
.decay(.1).sustain(0) // make notes short
|
||||
.s('triangle') // waveform
|
||||
.degradeBy(perlin.range(0,.5)) // randomly controlled random removal :)
|
||||
.echoWith(4,.125,(x,n)=>x.gain(.15*1/(n+1))) // echo notes
|
||||
//.hush()
|
||||
)
|
||||
.out()
|
||||
.slow(3/2)`;
|
||||
|
||||
export const swimmingWithSoundfonts = `stack(
|
||||
n(
|
||||
"~",
|
||||
"~",
|
||||
"~",
|
||||
"A5 [F5@2 C5] [D5@2 F5] F5",
|
||||
"[C5@2 F5] [F5@2 C6] A5 G5",
|
||||
"A5 [F5@2 C5] [D5@2 F5] F5",
|
||||
"[C5@2 F5] [Bb5 A5 G5] F5@2",
|
||||
"A5 [F5@2 C5] [D5@2 F5] F5",
|
||||
"[C5@2 F5] [F5@2 C6] A5 G5",
|
||||
"A5 [F5@2 C5] [D5@2 F5] F5",
|
||||
"[C5@2 F5] [Bb5 A5 G5] F5@2",
|
||||
"A5 [F5@2 C5] A5 F5",
|
||||
"Ab5 [F5@2 Ab5] G5@2",
|
||||
"A5 [F5@2 C5] A5 F5",
|
||||
"Ab5 [F5@2 C5] C6@2",
|
||||
"A5 [F5@2 C5] [D5@2 F5] F5",
|
||||
"[C5@2 F5] [Bb5 A5 G5] F5@2"
|
||||
).s('Sitar: Ethnic'),
|
||||
n(
|
||||
"[F4,Bb4,D5] [[D4,G4,Bb4]@2 [Bb3,D4,F4]] [[G3,C4,E4]@2 [[Ab3,F4] [A3,Gb4]]] [Bb3,E4,G4]",
|
||||
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, Bb3, Db3] [F3, Bb3, Db3]]",
|
||||
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
|
||||
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
|
||||
"[~ [A3, C4, E4] [A3, C4, E4]] [~ [Ab3, C4, Eb4] [Ab3, C4, Eb4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [G3, C4, E4] [G3, C4, E4]]",
|
||||
"[~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
|
||||
"[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]",
|
||||
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
|
||||
"[~ [A3, C4, E4] [A3, C4, E4]] [~ [Ab3, C4, Eb4] [Ab3, C4, Eb4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [G3, C4, E4] [G3, C4, E4]]",
|
||||
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
|
||||
"[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]",
|
||||
"[~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [A3, C4, F4] [A3, C4, F4]] [~ [A3, C4, F4] [A3, C4, F4]]",
|
||||
"[~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [G3, Bb3, F4] [G3, Bb3, F4]] [~ [G3, Bb3, E4] [G3, Bb3, E4]]",
|
||||
"[~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [A3, C4, F4] [A3, C4, F4]] [~ [A3, C4, F4] [A3, C4, F4]]",
|
||||
"[~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [G3, Bb3, F4] [G3, Bb3, F4]] [~ [G3, Bb3, E4] [G3, Bb3, E4]]",
|
||||
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
|
||||
"[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]"
|
||||
).s('Kalimba: Ethnic'),
|
||||
n(
|
||||
"[G3 G3 C3 E3]",
|
||||
"[F2 D2 G2 C2]",
|
||||
"[F2 D2 G2 C2]",
|
||||
"[F2 A2 Bb2 B2]",
|
||||
"[A2 Ab2 G2 C2]",
|
||||
"[F2 A2 Bb2 B2]",
|
||||
"[G2 C2 F2 F2]",
|
||||
"[F2 A2 Bb2 B2]",
|
||||
"[A2 Ab2 G2 C2]",
|
||||
"[F2 A2 Bb2 B2]",
|
||||
"[G2 C2 F2 F2]",
|
||||
"[Bb2 Bb2 A2 A2]",
|
||||
"[Ab2 Ab2 G2 [C2 D2 E2]]",
|
||||
"[Bb2 Bb2 A2 A2]",
|
||||
"[Ab2 Ab2 G2 [C2 D2 E2]]",
|
||||
"[F2 A2 Bb2 B2]",
|
||||
"[G2 C2 F2 F2]"
|
||||
).s('Acoustic Bass: Bass')
|
||||
).slow(51)
|
||||
.out()`;
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user