diff --git a/index.mjs b/index.mjs index 79729e91c..138298ef6 100644 --- a/index.mjs +++ b/index.mjs @@ -15,3 +15,4 @@ export * from './packages/transpiler/index.mjs'; export * from './packages/webaudio/index.mjs'; export * from './packages/webdirt/index.mjs'; export * from './packages/xen/index.mjs'; +export * from './packages/wam/index.mjs'; \ No newline at end of file diff --git a/packages/wam/index.mjs b/packages/wam/index.mjs new file mode 100644 index 000000000..116268385 --- /dev/null +++ b/packages/wam/index.mjs @@ -0,0 +1,95 @@ +import { register, Pattern, toMidi, valueToMidi } from '@strudel.cycles/core'; +import { getAudioContext } from '@strudel.cycles/webaudio'; +import {initializeWamHost} from '@webaudiomodules/sdk' + +// this is a map of all loaded WebAudioModules +let wams = {}; + +// this is a map of all WebAudioModule instances (possibly more than one per WAM) +let instances = {}; + +// has the WAM host been initialized? +let initialized = false + +// host groups of WAMs can interact with one another, but not directly across groups +const hostGroupId = "strudel" + +export const loadWAM = async function (name, url, what) { + if (!initialized) { + await initializeWamHost(getAudioContext(), hostGroupId) + initialized = true + } + + if (!!instances[name]) { + return instances[name] + } + + if (!wams[url]) { + const { default: WAM } = await import( + /* @vite-ignore */ + url); + + wams[url] = WAM + } + + const instance = new wams[url](hostGroupId, getAudioContext()); + + await instance.initialize() + + instance.audioNode.connect(getAudioContext().destination); + + instances[name] = instance + + return instance +} + +export const loadwam = loadWAM; +export const loadWam = loadWAM; + +export const wam = register('wam', function (name, pat) { + return pat.onTrigger((time, hap) => { + let i = instances[name] + + if (!i) { + return + } + + let note = toMidi(hap.value.note); + let velocity = hap.context?.velocity ?? 0.75; + let endTime = time + hap.duration.valueOf(); + + i.audioNode.scheduleEvents({ + type: "wam-midi", + data: {bytes: [0x90, note, velocity]}, + time: time, + }) + + i.audioNode.scheduleEvents({ + type: "wam-midi", + data: {bytes: [0x80, note, 0]}, + time: endTime + }) + }); + }); + +export const param = register('param', function(wam, param, value, pat) { + return pat.onTrigger((time, hap) => { + let i = instances[wam] + + if (!i) { + return + } + + debugger + + i.audioNode.scheduleEvents({ + time: time, + type: "wam-automation", + data: { + id: param, + normalized: false, + value: value + }, + }) + }) +}) \ No newline at end of file diff --git a/packages/wam/package.json b/packages/wam/package.json new file mode 100644 index 000000000..3f259e41b --- /dev/null +++ b/packages/wam/package.json @@ -0,0 +1,41 @@ +{ + "name": "@strudel.cycles/wam", + "version": "0.1.0", + "description": "WAM2 API for strudel", + "main": "index.mjs", + "type": "module", + "publishConfig": { + "main": "dist/index.js", + "module": "dist/index.mjs" + }, + "scripts": { + "build": "vite build", + "prepublishOnly": "npm run build" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/tidalcycles/strudel.git" + }, + "keywords": [ + "tidalcycles", + "strudel", + "pattern", + "livecoding", + "algorave" + ], + "author": "Tom Burns ", + "license": "AGPL-3.0-or-later", + "bugs": { + "url": "https://github.com/tidalcycles/strudel/issues" + }, + "homepage": "https://github.com/tidalcycles/strudel#readme", + "dependencies": { + "@strudel.cycles/core": "workspace:*", + "@webaudiomodules/sdk": "^0.0.10", + "tone": "^14.7.77" + }, + "devDependencies": { + "vite": "^3.2.2", + "vitest": "^0.25.7" + } +} \ No newline at end of file diff --git a/packages/wam/vite.config.js b/packages/wam/vite.config.js new file mode 100644 index 000000000..0fc63a6b3 --- /dev/null +++ b/packages/wam/vite.config.js @@ -0,0 +1,19 @@ +import { defineConfig } from 'vite'; +import { dependencies } from './package.json'; +import { resolve } from 'path'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [], + build: { + lib: { + entry: resolve(__dirname, 'index.mjs'), + formats: ['es', 'cjs'], + fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]), + }, + rollupOptions: { + external: [...Object.keys(dependencies)], + }, + target: 'esnext', + }, +}); diff --git a/website/package.json b/website/package.json index f8c466d33..782e35803 100644 --- a/website/package.json +++ b/website/package.json @@ -31,6 +31,7 @@ "@strudel.cycles/soundfonts": "workspace:*", "@strudel.cycles/tonal": "workspace:*", "@strudel.cycles/transpiler": "workspace:*", + "@strudel.cycles/wam": "workspace:*", "@strudel.cycles/webaudio": "workspace:*", "@strudel.cycles/xen": "workspace:*", "@supabase/supabase-js": "^1.35.3", diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 818adea93..a1eea7fa2 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -46,6 +46,7 @@ const modules = [ import('@strudel.cycles/serial'), import('@strudel.cycles/soundfonts'), import('@strudel.cycles/csound'), + import('@strudel.cycles/wam') ]; evalScope(