mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-14 06:43:47 -04:00
Compare commits
3 Commits
fix-onpaint
...
node
| Author | SHA1 | Date | |
|---|---|---|---|
| 35b9c82bdd | |||
| 0fcfc308fd | |||
| 931016143c |
+2
-1
@@ -21,4 +21,5 @@ vite.config.js
|
||||
/src-tauri/target/**/*
|
||||
reverbGen.mjs
|
||||
hydra.mjs
|
||||
jsdoc-synonyms.js
|
||||
jsdoc-synonyms.js
|
||||
packages/node/pattern.mjs
|
||||
@@ -11,3 +11,4 @@ pnpm-lock.yaml
|
||||
pnpm-workspace.yaml
|
||||
**/dev-dist
|
||||
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
|
||||
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:
|
||||
dependencies:
|
||||
'@strudel/core':
|
||||
@@ -3467,6 +3494,22 @@ packages:
|
||||
react: 18.2.0
|
||||
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:
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -10717,6 +10760,20 @@ packages:
|
||||
'@babel/parser': 7.23.6
|
||||
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:
|
||||
resolution: {integrity: sha512-ZFPLe9Iu0tnx7oWhFxAo4s7QTn8+NNDDxYNaKLjE7Dp0tbakQ3M1QhQzsnzXHQBTUO3K9BmwaxnyO8Ayn2I95Q==}
|
||||
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==}
|
||||
dev: true
|
||||
|
||||
/webidl-conversions@7.0.0:
|
||||
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/webmidi@3.1.8:
|
||||
resolution: {integrity: sha512-PCRic1iTpKxeheb888G0mDe6MBdz/u+s/xfdV6+1ZhZnS6dKtV9gMBth5cpmMip2Livv5lL+ep4/S9DCzHfumg==}
|
||||
engines: {node: '>=8.5'}
|
||||
|
||||
Reference in New Issue
Block a user