mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-17 08:06:50 -04:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 35b9c82bdd | |||
| 0fcfc308fd | |||
| 931016143c |
+2
-1
@@ -21,4 +21,5 @@ vite.config.js
|
|||||||
/src-tauri/target/**/*
|
/src-tauri/target/**/*
|
||||||
reverbGen.mjs
|
reverbGen.mjs
|
||||||
hydra.mjs
|
hydra.mjs
|
||||||
jsdoc-synonyms.js
|
jsdoc-synonyms.js
|
||||||
|
packages/node/pattern.mjs
|
||||||
@@ -11,3 +11,4 @@ pnpm-lock.yaml
|
|||||||
pnpm-workspace.yaml
|
pnpm-workspace.yaml
|
||||||
**/dev-dist
|
**/dev-dist
|
||||||
website/.astro
|
website/.astro
|
||||||
|
packages/node/pattern.mjs
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# @strudel/node
|
||||||
|
|
||||||
|
This is an experiment to run strudel in node.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Setup
|
||||||
|
# - make sure node >= 20 is installed
|
||||||
|
# - make sure pnpm is installed
|
||||||
|
cd packages/node
|
||||||
|
pnpm i
|
||||||
|
# Usage
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run `sclang` with superdirt in another terminal.
|
||||||
|
|
||||||
|
You can now edit and save the file `pattern.mjs` to update your pattern!
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { createClock, evalScope } from '@strudel/core';
|
||||||
|
import { evaluate } from '@strudel/transpiler';
|
||||||
|
import watch from 'node-watch';
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import { AudioContext, OscillatorNode, GainNode } from 'node-web-audio-api';
|
||||||
|
|
||||||
|
const audioContext = new AudioContext();
|
||||||
|
|
||||||
|
let file = 'pattern.mjs';
|
||||||
|
let pattern;
|
||||||
|
async function evaluateFile() {
|
||||||
|
try {
|
||||||
|
console.log('// file evaluated:');
|
||||||
|
const code = await fs.readFile(file, { encoding: 'utf8' });
|
||||||
|
console.log(code);
|
||||||
|
const res = await evaluate(code);
|
||||||
|
pattern = res.pattern;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const getTime = () => performance.now() / 1000;
|
||||||
|
const getTime = () => audioContext.currentTime;
|
||||||
|
let minLatency = 50;
|
||||||
|
async function main() {
|
||||||
|
await evalScope(import('@strudel/core'), import('@strudel/mini'), import('@strudel/tonal'));
|
||||||
|
await evaluateFile();
|
||||||
|
watch(file, { recursive: true }, () => evaluateFile());
|
||||||
|
let lastEnd;
|
||||||
|
const clock = createClock(getTime, (phase) => {
|
||||||
|
if (!lastEnd) {
|
||||||
|
lastEnd = phase;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const haps = pattern.queryArc(lastEnd, phase);
|
||||||
|
lastEnd = phase;
|
||||||
|
const cps = 1;
|
||||||
|
const cycle = Math.floor(phase);
|
||||||
|
haps
|
||||||
|
.filter((h) => h.hasOnset())
|
||||||
|
.forEach((hap) => {
|
||||||
|
const env = new GainNode(audioContext, { gain: 0 });
|
||||||
|
const { attack = 0.01, gain = 1 } = hap.value;
|
||||||
|
env.connect(audioContext.destination);
|
||||||
|
const now = hap.whole.begin;
|
||||||
|
const duration = hap.duration;
|
||||||
|
env.gain
|
||||||
|
.setValueAtTime(0, now)
|
||||||
|
.linearRampToValueAtTime(gain * 0.2, now + attack)
|
||||||
|
.exponentialRampToValueAtTime(0.0001, now + duration);
|
||||||
|
const frequency = hap.value.freq;
|
||||||
|
|
||||||
|
const osc = new OscillatorNode(audioContext, { frequency });
|
||||||
|
osc.connect(env);
|
||||||
|
osc.start(now);
|
||||||
|
osc.stop(now + duration);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
clock.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { createClock, evalScope } from '@strudel/core';
|
||||||
|
import { evaluate } from '@strudel/transpiler';
|
||||||
|
import OSC from 'osc-js';
|
||||||
|
import watch from 'node-watch';
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
receiver: 'udp', // @param {string} Where messages sent via 'send' method will be delivered to, 'ws' for Websocket clients, 'udp' for udp client
|
||||||
|
open: {
|
||||||
|
host: 'localhost', // @param {string} Hostname of udp server to bind to
|
||||||
|
port: 57121, // @param {number} Port of udp client for messaging
|
||||||
|
// enabling the following line will receive tidal messages:
|
||||||
|
// port: 57120, // @param {number} Port of udp client for messaging
|
||||||
|
exclusive: false, // @param {boolean} Exclusive flag
|
||||||
|
},
|
||||||
|
send: {
|
||||||
|
host: 'localhost', // @param {string} Hostname of udp client for messaging
|
||||||
|
port: 57120, // @param {number} Port of udp client for messaging
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const osc = new OSC({ plugin: new OSC.DatagramPlugin(config) });
|
||||||
|
|
||||||
|
osc.open(); // start a WebSocket server on port 8080
|
||||||
|
|
||||||
|
console.log('osc client running on port', config.open.port);
|
||||||
|
console.log('osc server running on port', config.send.port);
|
||||||
|
|
||||||
|
let file = 'pattern.mjs';
|
||||||
|
let pattern;
|
||||||
|
async function evaluateFile() {
|
||||||
|
try {
|
||||||
|
console.log('// file evaluated:');
|
||||||
|
const code = await fs.readFile(file, { encoding: 'utf8' });
|
||||||
|
console.log(code);
|
||||||
|
const res = await evaluate(code);
|
||||||
|
pattern = res.pattern;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTime = () => performance.now() / 1000;
|
||||||
|
async function main() {
|
||||||
|
await evalScope(import('@strudel/core'), import('@strudel/mini'), import('@strudel/tonal'));
|
||||||
|
await evaluateFile();
|
||||||
|
watch(file, { recursive: true }, () => evaluateFile());
|
||||||
|
let lastEnd;
|
||||||
|
let minLatency = 50;
|
||||||
|
const clock = createClock(getTime, (phase) => {
|
||||||
|
if (!lastEnd) {
|
||||||
|
lastEnd = phase;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const haps = pattern.queryArc(lastEnd, phase);
|
||||||
|
lastEnd = phase;
|
||||||
|
const cps = 1;
|
||||||
|
const cycle = Math.floor(phase);
|
||||||
|
haps
|
||||||
|
.filter((h) => h.hasOnset())
|
||||||
|
.forEach((hap) => {
|
||||||
|
const delta = hap.duration.valueOf();
|
||||||
|
const controls = Object.assign({}, { cps, cycle, delta }, hap.value);
|
||||||
|
const keyvals = Object.entries(controls).flat();
|
||||||
|
const ts = Math.floor(performance.timeOrigin + hap.whole.begin * 1000 + minLatency);
|
||||||
|
const message = new OSC.Message('/dirt/play', ...keyvals);
|
||||||
|
const bundle = new OSC.Bundle([message], ts);
|
||||||
|
bundle.timestamp(ts); // workaround for https://github.com/adzialocha/osc-js/issues/60
|
||||||
|
osc.send(bundle);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
clock.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "@strudel/node",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"description": "Strudel running in node",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||||
|
},
|
||||||
|
"main": "index.mjs",
|
||||||
|
"scripts": {
|
||||||
|
"osc": "node osc-superdirt.mjs",
|
||||||
|
"waa": "node node-web-audio-api.mjs"
|
||||||
|
},
|
||||||
|
"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/core": "workspace:*",
|
||||||
|
"@strudel/mini": "workspace:*",
|
||||||
|
"@strudel/osc": "workspace:*",
|
||||||
|
"@strudel/tonal": "workspace:*",
|
||||||
|
"@strudel/transpiler": "workspace:*",
|
||||||
|
"node-watch": "^0.7.4",
|
||||||
|
"node-web-audio-api": "^0.20.0",
|
||||||
|
"osc-js": "^2.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
freq("110 220 [330 440,550,660]")
|
||||||
|
.attack(.1).gain(.4)
|
||||||
Generated
+62
@@ -302,6 +302,33 @@ importers:
|
|||||||
specifier: ^1.1.0
|
specifier: ^1.1.0
|
||||||
version: 1.1.0(@vitest/ui@1.1.0)
|
version: 1.1.0(@vitest/ui@1.1.0)
|
||||||
|
|
||||||
|
packages/node:
|
||||||
|
dependencies:
|
||||||
|
'@strudel/core':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../core
|
||||||
|
'@strudel/mini':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../mini
|
||||||
|
'@strudel/osc':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../osc
|
||||||
|
'@strudel/tonal':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../tonal
|
||||||
|
'@strudel/transpiler':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../transpiler
|
||||||
|
node-watch:
|
||||||
|
specifier: ^0.7.4
|
||||||
|
version: 0.7.4
|
||||||
|
node-web-audio-api:
|
||||||
|
specifier: ^0.20.0
|
||||||
|
version: 0.20.0
|
||||||
|
osc-js:
|
||||||
|
specifier: ^2.4.0
|
||||||
|
version: 2.4.0
|
||||||
|
|
||||||
packages/osc:
|
packages/osc:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@strudel/core':
|
'@strudel/core':
|
||||||
@@ -3467,6 +3494,22 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@napi-rs/cli@2.18.2:
|
||||||
|
resolution: {integrity: sha512-IXQji3IF5eStxTHe/PxDSRjPrFymYAQ5FbIvqurxzxyWR8nJql9mtvmCP8y2g8tSoW5xhaToLQW0+mO3lUZq4w==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@napi-rs/triples@1.2.0:
|
||||||
|
resolution: {integrity: sha512-HAPjR3bnCsdXBsATpDIP5WCrw0JcACwhhrwIAQhiR46n+jm+a2F8kBsfseAuWtSyQ+H3Yebt2k43B5dy+04yMA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@node-rs/helper@1.6.0:
|
||||||
|
resolution: {integrity: sha512-2OTh/tokcLA1qom1zuCJm2gQzaZljCCbtX1YCrwRVd/toz7KxaDRFeLTAPwhs8m9hWgzrBn5rShRm6IaZofCPw==}
|
||||||
|
dependencies:
|
||||||
|
'@napi-rs/triples': 1.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@nodelib/fs.scandir@2.1.5:
|
/@nodelib/fs.scandir@2.1.5:
|
||||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -10717,6 +10760,20 @@ packages:
|
|||||||
'@babel/parser': 7.23.6
|
'@babel/parser': 7.23.6
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/node-watch@0.7.4:
|
||||||
|
resolution: {integrity: sha512-RinNxoz4W1cep1b928fuFhvAQ5ag/+1UlMDV7rbyGthBIgsiEouS4kvRayvvboxii4m8eolKOIBo3OjDqbc+uQ==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/node-web-audio-api@0.20.0:
|
||||||
|
resolution: {integrity: sha512-DPsRSG3IsI8cLCdejpruir+XgSwUFFJBSfilrtoT+BU/uFcXv/eFyKkulnKIE2642j5b00z9aOHxTHbzdUvTtw==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
dependencies:
|
||||||
|
'@napi-rs/cli': 2.18.2
|
||||||
|
'@node-rs/helper': 1.6.0
|
||||||
|
webidl-conversions: 7.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/nopt@7.1.0:
|
/nopt@7.1.0:
|
||||||
resolution: {integrity: sha512-ZFPLe9Iu0tnx7oWhFxAo4s7QTn8+NNDDxYNaKLjE7Dp0tbakQ3M1QhQzsnzXHQBTUO3K9BmwaxnyO8Ayn2I95Q==}
|
resolution: {integrity: sha512-ZFPLe9Iu0tnx7oWhFxAo4s7QTn8+NNDDxYNaKLjE7Dp0tbakQ3M1QhQzsnzXHQBTUO3K9BmwaxnyO8Ayn2I95Q==}
|
||||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||||
@@ -14193,6 +14250,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
|
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/webidl-conversions@7.0.0:
|
||||||
|
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/webmidi@3.1.8:
|
/webmidi@3.1.8:
|
||||||
resolution: {integrity: sha512-PCRic1iTpKxeheb888G0mDe6MBdz/u+s/xfdV6+1ZhZnS6dKtV9gMBth5cpmMip2Livv5lL+ep4/S9DCzHfumg==}
|
resolution: {integrity: sha512-PCRic1iTpKxeheb888G0mDe6MBdz/u+s/xfdV6+1ZhZnS6dKtV9gMBth5cpmMip2Livv5lL+ep4/S9DCzHfumg==}
|
||||||
engines: {node: '>=8.5'}
|
engines: {node: '>=8.5'}
|
||||||
|
|||||||
Reference in New Issue
Block a user