Compare commits

...

9 Commits

Author SHA1 Message Date
Felix Roos 76bdc5fd13 add missing deps 2023-03-01 11:56:56 +01:00
Felix Roos 0ea5097a2c Merge branch 'main' into ableton-link 2023-03-01 11:52:22 +01:00
Felix Roos e616891606 Merge branch 'main' into ableton-link 2023-02-08 14:25:03 +01:00
Felix Roos 97b01b2891 begin link-client 2023-02-08 14:22:02 +01:00
Felix Roos b4fcc359cf Merge remote-tracking branch 'origin/main' into ableton-link 2023-02-02 10:37:22 +01:00
Felix Roos 1ee6962b85 add package.json to link folder 2023-01-27 09:52:04 +01:00
Felix Roos 8d936b93c6 add some notes 2023-01-26 22:26:21 +01:00
Felix Roos 5bd1c0fd7a simplify link server 2023-01-26 22:09:29 +01:00
Felix Roos eeaead66b5 ableton link mvp 2023-01-26 21:46:57 +01:00
6 changed files with 219 additions and 0 deletions
+25
View File
@@ -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..
+1
View File
@@ -0,0 +1 @@
export * from './link-client.mjs';
+26
View File
@@ -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;
};
+63
View File
@@ -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;
});
+38
View File
@@ -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"
}
}
+66
View File
@@ -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