diff --git a/packages/node/node-web-audio-api.mjs b/packages/node/node-web-audio-api.mjs new file mode 100644 index 000000000..6953550df --- /dev/null +++ b/packages/node/node-web-audio-api.mjs @@ -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(); diff --git a/packages/node/index.mjs b/packages/node/osc-superdirt.mjs similarity index 100% rename from packages/node/index.mjs rename to packages/node/osc-superdirt.mjs diff --git a/packages/node/package.json b/packages/node/package.json index 679fe5a29..dd77de723 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -8,7 +8,8 @@ }, "main": "index.mjs", "scripts": { - "start": "node index.mjs" + "osc": "node osc-superdirt.mjs", + "waa": "node node-web-audio-api.mjs" }, "keywords": [ "tidalcycles", @@ -25,11 +26,12 @@ "homepage": "https://github.com/tidalcycles/strudel#readme", "dependencies": { "@strudel/core": "workspace:*", + "@strudel/mini": "workspace:*", "@strudel/osc": "workspace:*", "@strudel/tonal": "workspace:*", - "@strudel/mini": "workspace:*", "@strudel/transpiler": "workspace:*", "node-watch": "^0.7.4", + "node-web-audio-api": "^0.20.0", "osc-js": "^2.4.0" } } diff --git a/packages/node/pattern.mjs b/packages/node/pattern.mjs index ebffb0e13..358fe0c3d 100644 --- a/packages/node/pattern.mjs +++ b/packages/node/pattern.mjs @@ -1,5 +1,2 @@ -stack( - s("bd*2"), - s("jvbass(3,8)"), - n(run(8)).s("feel").jux(rev).lpf(800) -).hush() \ No newline at end of file +freq("110 220 [330 440,550,660]") +.attack(.1).gain(.4) \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dcacd5941..09ef98b22 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -322,6 +322,9 @@ importers: 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 @@ -3491,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'} @@ -10746,6 +10765,15 @@ packages: 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} @@ -14222,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'}