mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-13 22:35:15 -04:00
Compare commits
9 Commits
node
...
ableton-link
| Author | SHA1 | Date | |
|---|---|---|---|
| 76bdc5fd13 | |||
| 0ea5097a2c | |||
| e616891606 | |||
| 97b01b2891 | |||
| b4fcc359cf | |||
| 1ee6962b85 | |||
| 8d936b93c6 | |||
| 5bd1c0fd7a | |||
| eeaead66b5 |
@@ -0,0 +1,25 @@
|
||||
# @strudel.cycles/link
|
||||
|
||||
This is an experiment to find out how to hook up ableton link with strudel.
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
cd packages/link/ && npm i
|
||||
node link-server.mjs # tested with node 18
|
||||
```
|
||||
|
||||
This will play the pattern in `link-server.mjs`, which will be synced automatically via Ableton Link!
|
||||
|
||||
## Notes
|
||||
|
||||
- The sound generation uses [node-web-audio-api](https://github.com/audiojs/web-audio-api), supporting only oscillators with a fixed envelope
|
||||
- All functions from `@strudel.cycles/webaudio` could be supported when the [node-webaudio branch](https://github.com/tidalcycles/strudel/tree/node-webaudio) is further developed. This includes refactoring the webaudio stuff to allow passing an audio context + making sure it can be imported in node
|
||||
- To make this live codable, I see 2 options:
|
||||
- headless: add an express server (or similar) that allows sending `PUT` requests to change the pattern.
|
||||
- browser: find a way combine the REPL with the ableton clock.
|
||||
- the server could pass the callback from ableton link via http or websocket
|
||||
- the client could then use these messages to control the scheduler
|
||||
- could try implementing getTime as a LERP between received beat values
|
||||
- or do not use the scheduler and just query within the received beat values
|
||||
- highlighting is another thing..
|
||||
@@ -0,0 +1 @@
|
||||
export * from './link-client.mjs';
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
link-server.mjs - <short description TODO>
|
||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/osc/osc.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { connectOSC } from '@strudel.cycles/osc';
|
||||
import { Pattern, silence } from '@strudel.cycles/core';
|
||||
|
||||
/**
|
||||
*
|
||||
* Connects to Ableton Link
|
||||
*
|
||||
* @name link
|
||||
* @memberof Pattern
|
||||
* @returns Pattern
|
||||
*/
|
||||
console.log('link-client');
|
||||
Pattern.prototype.link = async function () {
|
||||
const osc = await connectOSC();
|
||||
osc.on('*', (msg) => {
|
||||
const [begin, end, cps] = msg.address.split('/');
|
||||
console.log(begin, end, cps);
|
||||
});
|
||||
return silence;
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import { evaluate } from '@strudel.cycles/transpiler';
|
||||
import { evalScope, controls, getFrequency } from '@strudel.cycles/core';
|
||||
import { AudioContext, OscillatorNode, GainNode } from 'node-web-audio-api';
|
||||
import abletonlink from 'abletonlink';
|
||||
|
||||
//// prepare pattern
|
||||
|
||||
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')).ply(8).gain(.0625).s('sawtooth').early(.06)`,
|
||||
);
|
||||
|
||||
//// web audio logic
|
||||
|
||||
const ac = new AudioContext();
|
||||
|
||||
export function playHaps(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 scheduling
|
||||
|
||||
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(haps, lastBeat, cps);
|
||||
}
|
||||
lastBeat = beat;
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "@strudel.cycles/link",
|
||||
"version": "0.6.0",
|
||||
"description": "Ableton Link Support for Strudel",
|
||||
"main": "link-server.mjs",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node link-server.mjs"
|
||||
},
|
||||
"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",
|
||||
"devDependencies": {
|
||||
"abletonlink": "^0.1.3",
|
||||
"node-web-audio-api": "^0.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strudel.cycles/core": "workspace:^0.6.8",
|
||||
"@strudel.cycles/mini": "workspace:^0.6.0",
|
||||
"@strudel.cycles/osc": "workspace:^0.6.0",
|
||||
"@strudel.cycles/tonal": "workspace:^0.6.0",
|
||||
"@strudel.cycles/transpiler": "workspace:^0.6.0"
|
||||
}
|
||||
}
|
||||
Generated
+66
@@ -120,6 +120,25 @@ importers:
|
||||
vite: 3.2.5
|
||||
vitest: 0.25.8
|
||||
|
||||
packages/link:
|
||||
specifiers:
|
||||
'@strudel.cycles/core': workspace:^0.6.8
|
||||
'@strudel.cycles/mini': workspace:^0.6.0
|
||||
'@strudel.cycles/osc': workspace:^0.6.0
|
||||
'@strudel.cycles/tonal': workspace:^0.6.0
|
||||
'@strudel.cycles/transpiler': workspace:^0.6.0
|
||||
abletonlink: ^0.1.3
|
||||
node-web-audio-api: ^0.5.0
|
||||
dependencies:
|
||||
'@strudel.cycles/core': link:../core
|
||||
'@strudel.cycles/mini': link:../mini
|
||||
'@strudel.cycles/osc': link:../osc
|
||||
'@strudel.cycles/tonal': link:../tonal
|
||||
'@strudel.cycles/transpiler': link:../transpiler
|
||||
devDependencies:
|
||||
abletonlink: 0.1.3
|
||||
node-web-audio-api: 0.5.0
|
||||
|
||||
packages/midi:
|
||||
specifiers:
|
||||
'@strudel.cycles/core': workspace:*
|
||||
@@ -3199,6 +3218,22 @@ packages:
|
||||
use-sync-external-store: 1.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/@napi-rs/cli/2.14.8:
|
||||
resolution: {integrity: sha512-IvA3s8BqohMdUbOkFn7+23u1dhIJZCkA8Xps7DD4SLdCMbcbUF6MUuKiqxuqmVHBFTaxU25sU56WdX3efqGgPw==}
|
||||
engines: {node: '>= 10'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/@napi-rs/triples/1.1.0:
|
||||
resolution: {integrity: sha512-XQr74QaLeMiqhStEhLn1im9EOMnkypp7MZOwQhGzqp2Weu5eQJbpPxWxixxlYRKWPOmJjsk6qYfYH9kq43yc2w==}
|
||||
dev: true
|
||||
|
||||
/@node-rs/helper/1.3.3:
|
||||
resolution: {integrity: sha512-p4OdfQObGN9YFy5WZaGwlPYICQSe7xZYyXB0sxREmvj1HzGKp5bPg2PlfgfMZEfnjIA882B9ZrnagYzZihIwjA==}
|
||||
dependencies:
|
||||
'@napi-rs/triples': 1.1.0
|
||||
dev: true
|
||||
|
||||
/@nodelib/fs.scandir/2.1.5:
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -4331,6 +4366,15 @@ packages:
|
||||
/abbrev/1.1.1:
|
||||
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
||||
|
||||
/abletonlink/0.1.3:
|
||||
resolution: {integrity: sha512-rPMS0X6+wcW607Lpr3XeqfMJ26oNDc/Yx6+5oEtkS9CYr2rKRh6dfiaHpDxhoEqKZtBrZY0JWOlkZhD49tesjQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
bindings: 1.5.0
|
||||
node-addon-api: 2.0.2
|
||||
dev: true
|
||||
|
||||
/acorn-jsx/5.3.2_acorn@8.8.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
@@ -4881,6 +4925,12 @@ packages:
|
||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
/bindings/1.5.0:
|
||||
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
|
||||
dependencies:
|
||||
file-uri-to-path: 1.0.0
|
||||
dev: true
|
||||
|
||||
/bl/4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
dependencies:
|
||||
@@ -6849,6 +6899,10 @@ packages:
|
||||
glob: 7.2.3
|
||||
dev: true
|
||||
|
||||
/file-uri-to-path/1.0.0:
|
||||
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
|
||||
dev: true
|
||||
|
||||
/filelist/1.0.4:
|
||||
resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
|
||||
dependencies:
|
||||
@@ -9713,6 +9767,10 @@ packages:
|
||||
semver: 5.7.1
|
||||
dev: true
|
||||
|
||||
/node-addon-api/2.0.2:
|
||||
resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==}
|
||||
dev: true
|
||||
|
||||
/node-domexception/1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
@@ -9805,6 +9863,14 @@ packages:
|
||||
'@babel/parser': 7.20.13
|
||||
dev: false
|
||||
|
||||
/node-web-audio-api/0.5.0:
|
||||
resolution: {integrity: sha512-HMFQHti2RnhgAzIrK6PSpDqgwKHMdeWsVILBwEQcCWzol9nGDK+QQQCJPWpMTfvTuGsfEletzRqz+p6fhcjDuA==}
|
||||
engines: {node: '>= 14'}
|
||||
dependencies:
|
||||
'@napi-rs/cli': 2.14.8
|
||||
'@node-rs/helper': 1.3.3
|
||||
dev: true
|
||||
|
||||
/nopt/4.0.3:
|
||||
resolution: {integrity: sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==}
|
||||
hasBin: true
|
||||
|
||||
Reference in New Issue
Block a user