ableton link mvp

This commit is contained in:
Felix Roos
2023-01-26 21:46:57 +01:00
parent ad917afba4
commit eeaead66b5
3 changed files with 203 additions and 0 deletions
+137
View File
@@ -11,8 +11,12 @@
"workspaces": [
"packages/*"
],
"dependencies": {
"node-web-audio-api": "^0.5.0"
},
"devDependencies": {
"@vitest/ui": "^0.25.7",
"abletonlink": "^0.1.3",
"c8": "^7.12.0",
"canvas": "^2.11.0",
"dependency-tree": "^9.0.0",
@@ -1822,6 +1826,34 @@
"node": ">= 6"
}
},
"node_modules/@napi-rs/cli": {
"version": "2.14.4",
"resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.14.4.tgz",
"integrity": "sha512-JvnJWFGhqjDV+D5MD9HCPH7Bc8uxwyPbpA8jsS5mtAp9+3IhVxpqhQc+eqmJrDAPO9z3a8qAdNnBf9+ZI9bIQg==",
"bin": {
"napi": "scripts/index.js"
},
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@napi-rs/triples": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.1.0.tgz",
"integrity": "sha512-XQr74QaLeMiqhStEhLn1im9EOMnkypp7MZOwQhGzqp2Weu5eQJbpPxWxixxlYRKWPOmJjsk6qYfYH9kq43yc2w=="
},
"node_modules/@node-rs/helper": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@node-rs/helper/-/helper-1.3.3.tgz",
"integrity": "sha512-p4OdfQObGN9YFy5WZaGwlPYICQSe7xZYyXB0sxREmvj1HzGKp5bPg2PlfgfMZEfnjIA882B9ZrnagYzZihIwjA==",
"dependencies": {
"@napi-rs/triples": "^1.1.0"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"license": "MIT",
@@ -2611,6 +2643,20 @@
"dev": true,
"license": "ISC"
},
"node_modules/abletonlink": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/abletonlink/-/abletonlink-0.1.3.tgz",
"integrity": "sha512-rPMS0X6+wcW607Lpr3XeqfMJ26oNDc/Yx6+5oEtkS9CYr2rKRh6dfiaHpDxhoEqKZtBrZY0JWOlkZhD49tesjQ==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"bindings": "^1.5.0",
"node-addon-api": "^2.0.0"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/acorn": {
"version": "7.4.1",
"license": "MIT",
@@ -3028,6 +3074,15 @@
"node": ">=8"
}
},
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dev": true,
"dependencies": {
"file-uri-to-path": "1.0.0"
}
},
"node_modules/bl": {
"version": "4.1.0",
"dev": true,
@@ -5399,6 +5454,12 @@
"node": ">=10"
}
},
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"dev": true
},
"node_modules/filename-reserved-regex": {
"version": "2.0.0",
"dev": true,
@@ -8509,6 +8570,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/node-addon-api": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
"integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==",
"dev": true
},
"node_modules/node-domexception": {
"version": "1.0.0",
"dev": true,
@@ -8699,6 +8766,18 @@
"node": ">=6.0"
}
},
"node_modules/node-web-audio-api": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/node-web-audio-api/-/node-web-audio-api-0.5.0.tgz",
"integrity": "sha512-HMFQHti2RnhgAzIrK6PSpDqgwKHMdeWsVILBwEQcCWzol9nGDK+QQQCJPWpMTfvTuGsfEletzRqz+p6fhcjDuA==",
"dependencies": {
"@napi-rs/cli": "^2.10.0",
"@node-rs/helper": "^1.3.3"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/nopt": {
"version": "4.0.3",
"dev": true,
@@ -14477,6 +14556,24 @@
}
}
},
"@napi-rs/cli": {
"version": "2.14.4",
"resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.14.4.tgz",
"integrity": "sha512-JvnJWFGhqjDV+D5MD9HCPH7Bc8uxwyPbpA8jsS5mtAp9+3IhVxpqhQc+eqmJrDAPO9z3a8qAdNnBf9+ZI9bIQg=="
},
"@napi-rs/triples": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.1.0.tgz",
"integrity": "sha512-XQr74QaLeMiqhStEhLn1im9EOMnkypp7MZOwQhGzqp2Weu5eQJbpPxWxixxlYRKWPOmJjsk6qYfYH9kq43yc2w=="
},
"@node-rs/helper": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@node-rs/helper/-/helper-1.3.3.tgz",
"integrity": "sha512-p4OdfQObGN9YFy5WZaGwlPYICQSe7xZYyXB0sxREmvj1HzGKp5bPg2PlfgfMZEfnjIA882B9ZrnagYzZihIwjA==",
"requires": {
"@napi-rs/triples": "^1.1.0"
}
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"requires": {
@@ -15236,6 +15333,16 @@
"version": "1.1.1",
"dev": true
},
"abletonlink": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/abletonlink/-/abletonlink-0.1.3.tgz",
"integrity": "sha512-rPMS0X6+wcW607Lpr3XeqfMJ26oNDc/Yx6+5oEtkS9CYr2rKRh6dfiaHpDxhoEqKZtBrZY0JWOlkZhD49tesjQ==",
"dev": true,
"requires": {
"bindings": "^1.5.0",
"node-addon-api": "^2.0.0"
}
},
"acorn": {
"version": "7.4.1"
},
@@ -15489,6 +15596,15 @@
"version": "2.2.0",
"dev": true
},
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dev": true,
"requires": {
"file-uri-to-path": "1.0.0"
}
},
"bl": {
"version": "4.1.0",
"dev": true,
@@ -17074,6 +17190,12 @@
}
}
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"dev": true
},
"filename-reserved-regex": {
"version": "2.0.0",
"dev": true
@@ -19107,6 +19229,12 @@
"version": "2.6.2",
"dev": true
},
"node-addon-api": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
"integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==",
"dev": true
},
"node-domexception": {
"version": "1.0.0",
"dev": true
@@ -19236,6 +19364,15 @@
"@babel/parser": "^7.0.0"
}
},
"node-web-audio-api": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/node-web-audio-api/-/node-web-audio-api-0.5.0.tgz",
"integrity": "sha512-HMFQHti2RnhgAzIrK6PSpDqgwKHMdeWsVILBwEQcCWzol9nGDK+QQQCJPWpMTfvTuGsfEletzRqz+p6fhcjDuA==",
"requires": {
"@napi-rs/cli": "^2.10.0",
"@node-rs/helper": "^1.3.3"
}
},
"nopt": {
"version": "4.0.3",
"dev": true,
+4
View File
@@ -48,6 +48,7 @@
"homepage": "https://strudel.tidalcycles.org",
"devDependencies": {
"@vitest/ui": "^0.25.7",
"abletonlink": "^0.1.3",
"c8": "^7.12.0",
"canvas": "^2.11.0",
"dependency-tree": "^9.0.0",
@@ -61,5 +62,8 @@
"prettier": "^2.8.1",
"rollup-plugin-visualizer": "^5.8.1",
"vitest": "^0.25.7"
},
"dependencies": {
"node-web-audio-api": "^0.5.0"
}
}
+62
View File
@@ -0,0 +1,62 @@
//// get pattern
import { evaluate } from '@strudel.cycles/transpiler';
import { evalScope, controls, getFrequency } from '@strudel.cycles/core';
await evalScope(
controls,
import('@strudel.cycles/core'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'),
// import('@strudel.cycles/xen'),
// import('@strudel.cycles/osc'),
);
const { pattern } = await evaluate(`note("<Dm7 G7 C^7!2>".voicings('lefthand')).gain(.25).s('sawtooth')`);
//// web audio
import { AudioContext, OscillatorNode, GainNode } from 'node-web-audio-api';
const ac = new AudioContext();
export function playHaps(ac, haps, beat, cps = 1) {
const ct = ac.currentTime;
haps
.filter((hap) => hap.hasOnset())
.forEach((hap) => {
const deadline = (hap.whole.begin - beat) / cps;
const t = ct + deadline;
const freq = getFrequency(hap) ?? 220;
const { gain = 1, s = 'triangle' } = hap.value;
const env = new GainNode(ac);
env.connect(ac.destination);
env.gain.value = 0;
env.gain.setValueAtTime(0, t);
env.gain.linearRampToValueAtTime(gain, t + 0.02);
env.gain.exponentialRampToValueAtTime(0.0001, t + hap.duration);
const osc = new OscillatorNode(ac);
osc.frequency.value = freq;
osc.type = s;
osc.connect(env);
osc.start(t);
osc.stop(t + hap.duration);
});
}
//// link
import abletonlink from 'abletonlink';
const link = new abletonlink();
let lastBeat;
link.startUpdate(60, (beat, phase, bpm) => {
if (lastBeat !== undefined) {
const cps = bpm / 60;
const haps = pattern.slow(2).queryArc(lastBeat, beat);
playHaps(ac, haps, lastBeat, cps);
}
lastBeat = beat;
});