diff --git a/packages/shader/package.json b/packages/shader/package.json
index 64496b424..45ea5c11c 100644
--- a/packages/shader/package.json
+++ b/packages/shader/package.json
@@ -8,7 +8,6 @@
"main": "dist/index.mjs"
},
"scripts": {
- "test": "vitest run",
"build": "vite build",
"prepublishOnly": "npm run build"
},
diff --git a/packages/shader/testSetup.mjs b/packages/shader/testSetup.mjs
deleted file mode 100644
index fb6963834..000000000
--- a/packages/shader/testSetup.mjs
+++ /dev/null
@@ -1,3 +0,0 @@
-// Fix `ReferenceError: self is not defined`
-// when importing picogl in tests
-globalThis.self = {};
diff --git a/packages/shader/uniform.mjs b/packages/shader/uniform.mjs
index 0ce7896a2..3e33b68da 100644
--- a/packages/shader/uniform.mjs
+++ b/packages/shader/uniform.mjs
@@ -4,122 +4,94 @@ Copyright (C) 2024 Strudel contributors
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 .
*/
-import { register, logger } from '@strudel/core';
+import { Pattern, reify, register, logger } from '@strudel/core';
import { setUniform } from './shader.mjs';
-// Parse a destination from the mini notation, e.g. `name` or `name:attr:value`
-export function parseUniformDest(dest) {
- let result = {};
- if (typeof dest === 'string') result.name = dest;
- else if (dest.length >= 2) {
- result.name = dest[0];
- // Parse the attr:value pairs
- for (let i = 1; i < dest.length; i += 2) {
- const k = dest[i];
- const v = dest[i + 1];
- const isNum = typeof v === 'number';
- if (k == 'index' && isNum) result.position = v;
- else if (k == 'index' && v == 'random') result.position = Math.floor(Math.random() * 1024);
- else if (k == 'index' && v == 'seq') result.position = null;
- else if (k == 'gain' && isNum) result.gain = v;
- else if (k == 'slow' && isNum) result.slow = v;
- else throw 'Bad uniform param ' + k + ':' + v;
- }
- }
- return result;
-}
-
-// Keep track of the last uniforms' array position
-let _uniforms = {};
-function getNextPosition(name, value) {
- // Initialize uniform state
- if (!_uniforms[name]) _uniforms[name] = { _count: 0 };
- const uniform = _uniforms[name];
-
- // Set a new position when the value changes
- if (uniform._last != value) {
- uniform._last = value;
- uniform._count++;
- }
- return uniform._count;
-}
-
/**
- * Update a shader. The destination name consist of
- *
- * - the uniform name
- * - optional 'index' to set array position, either a number or an assignment mode ('seq' or 'random')
- * - optional 'gain' to adjust the value: 0 to silence, 2 to double
- * - optional 'slow' to adjust the change speed: 1 for instant, 50 for slow changes, default to 10
+ * Update a shader.
*
* @name uniform
* @example
* pan(sine.uniform("iColor"))
* @example
- * gain("<.5 .3>".uniform("rotations:index:seq"))
+ * gain("<.5 .3>".uniform("rotations:seq"))
+ * @example
+ * s("bd sd").uniform({onTrigger: true, dest: "moveFWD"})
*/
-export const uniform = register('uniform', (target, pat) => {
- // TODO: support multiple shader instance
- const instance = 'default';
+export const uniform = register('uniform', (options, pat) => {
+ // The shader instance name
+ const instance = options.instance || 'default';
- // Decode the uniform target defintion
- const uniformDest = parseUniformDest(target);
- // Set the first value by default
- if (uniformDest.position === undefined) uniformDest.position = 0;
+ // Are the uniform updated on trigger
+ const onTrigger = options.onTrigger || false;
- return pat.withValue((v) => {
- // TODO: figure out why this is called repeatedly when changing values. For example, on first call, the last value is passed.
- if (typeof v === 'number') {
- const position = uniformDest.position === null ? getNextPosition(uniformDest.name, v) : uniformDest.position;
- const value = v * (uniformDest.gain || 1);
- setUniform(instance, uniformDest.name, value, false, position, uniformDest.slow || 10);
- } else {
- console.error('Uniform applied to a non number pattern');
- }
- return v;
+ const setCtx = (uniformParam) => (ctx) => ({
+ uniformParam,
+ onTrigger: () => {},
+ dominantTrigger: true,
+ ...ctx,
});
-});
-function getNotePosition(name, value) {
- // Initialize uniform state
- if (!_uniforms[name]) _uniforms[name] = {};
- const uniform = _uniforms[name];
+ const pitches = { _count: 0 };
+ const getPosition = (value, dest) => {
+ if (typeof dest === 'number') return dest;
+ else if (dest == 'seq') return pitches._count++;
+ else if (dest == 'random') return Math.floor(Math.random() * 1024);
+ else if (onTrigger) {
+ const note = value.note || value.n || value.sound || value.s;
+ if (pitches[note] === undefined) {
+ // Assign new value, the first note gets 0, then 1, then 2, ...
+ pitches[note] = Object.keys(pitches).length - 1;
+ }
+ return pitches[note];
+ } else {
+ throw 'Invalid position' + dest;
+ }
+ };
+ const getUniformPosition = (value, dest) => {
+ if (typeof dest === 'string') {
+ return [dest, 0];
+ } else {
+ return [dest[0], getPosition(value, dest[1])];
+ }
+ };
- const note = value.note || value.n || value.sound || value.s;
- if (uniform[note] === undefined) {
- // Assign new value, the first note gets 0, then 1, then 2, ...
- uniform[note] = Object.keys(uniform).length;
+ const optionsPats = [];
+ if (Array.isArray(options) || typeof options === 'string')
+ optionsPats.push(reify(options).withContext(setCtx('dest')));
+ else {
+ if (options.dest) optionsPats.push(reify(options.dest).withContext(setCtx('dest')));
+ if (options.gain) optionsPats.push(reify(options.gain).withContext(setCtx('gain')));
+ if (options.slow) optionsPats.push(reify(options.slow).withContext(setCtx('slow')));
}
- return uniform[note];
-}
-
-/**
- * Update a shader with note-on event. See the 'uniform' doc.
- *
- * @name uniformTrigger
- * @example
- * s("bd sd").uniformTrigger("iColors:gain:2"))
- */
-export const uniformTrigger = register('uniformTrigger', (target, pat) => {
- // TODO: support multiple shader instance
- const instance = 'default';
-
- // Decode the uniform target defintion
- const uniformDest = parseUniformDest(target);
-
- return pat.onTrigger((time_deprecate, hap, currentTime, cps, targetTime) => {
- const position =
- uniformDest.position === undefined
- ? // Set the position based on the note by default
- getNotePosition(uniformDest.name, hap.value)
- : uniformDest.position === null
- ? // The index is set to `seq`
- getNextPosition(uniformDest.name, currentTime)
- : uniformDest.position;
-
- const value = (hap.value.gain || 1) * (uniformDest.gain || 1);
-
- // Update the uniform
- setUniform(instance, uniformDest.name, value, true, position, uniformDest.slow || 10);
- }, false);
+ return stack(pat, ...optionsPats).withHaps((haps) => {
+ let dest;
+ let gain = 1;
+ let slow = 10;
+ let source;
+ haps.forEach((hap) => {
+ if (hap.context.uniformParam == 'dest') {
+ dest = hap.value;
+ } else if (hap.context.uniformParam == 'gain') {
+ gain = hap.value;
+ } else if (hap.context.uniformParam == 'slow') {
+ slow = hap.value;
+ } else {
+ source = hap;
+ }
+ });
+ if (dest && source) {
+ if (onTrigger) {
+ source.context.onTrigger = (_, hap) => {
+ const [uniform, position] = getUniformPosition(hap.value, dest);
+ setUniform(instance, uniform, (hap.value.gain || 1) * gain, true, position, slow);
+ };
+ source.context.dominantTrigger = false;
+ } else {
+ const [uniform, position] = getUniformPosition(source.value, dest);
+ setUniform(instance, uniform, source.value * gain, false, position, slow);
+ }
+ }
+ return haps;
+ });
});
diff --git a/packages/shader/uniform.test.mjs b/packages/shader/uniform.test.mjs
deleted file mode 100644
index 7751ebd56..000000000
--- a/packages/shader/uniform.test.mjs
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
-uniform.test.mjs -
-Copyright (C) 2024 Strudel contributors - see
-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 .
-*/
-
-import { describe, it, expect } from 'vitest';
-import { parseUniformDest } from './uniform.mjs';
-
-describe('Uniform', () => {
- it('Parse simple', () => {
- expect(parseUniformDest('iColor')).toStrictEqual({ name: 'iColor' });
- });
- it('Parse param', () => {
- expect(parseUniformDest(['iColor', 'index', 2])).toStrictEqual({ name: 'iColor', position: 2 });
- expect(parseUniformDest(['iColor', 'index', 'seq'])).toStrictEqual({ name: 'iColor', position: null });
- expect(parseUniformDest(['iColor', 'gain', 3])).toStrictEqual({ name: 'iColor', gain: 3 });
- });
- it('Parse multi param', () => {
- expect(parseUniformDest(['iColor', 'index', 2, 'gain', 3])).toStrictEqual({ name: 'iColor', position: 2, gain: 3 });
- });
-});
diff --git a/packages/shader/vite.config.js b/packages/shader/vite.config.js
index b0e119c95..5df3edc1b 100644
--- a/packages/shader/vite.config.js
+++ b/packages/shader/vite.config.js
@@ -5,9 +5,6 @@ import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
- test: {
- setupFiles: './testSetup.mjs',
- },
build: {
lib: {
entry: resolve(__dirname, 'index.mjs'),
diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap
index 4905690c6..d06011d35 100644
--- a/test/__snapshots__/examples.test.mjs.snap
+++ b/test/__snapshots__/examples.test.mjs.snap
@@ -8345,32 +8345,31 @@ exports[`runs examples > example "undegradeBy" example index 1 1`] = `
exports[`runs examples > example "uniform" example index 0 1`] = `
[
- "[ 0/1 → 1/1 | s:bd ]",
- "[ 1/1 → 2/1 | s:bd ]",
- "[ 2/1 → 3/1 | s:bd ]",
- "[ 3/1 → 4/1 | s:bd ]",
+ "[ ~show() {
+ return this.begin.show() + ' → ' + this.end.show();
+ } | pan:0.4999999999999998 ]",
]
`;
exports[`runs examples > example "uniform" example index 1 1`] = `
[
- "[ 0/1 → 1/1 | s:bd ]",
- "[ 1/1 → 2/1 | s:bd ]",
- "[ 2/1 → 3/1 | s:bd ]",
- "[ 3/1 → 4/1 | s:bd ]",
+ "[ 0/1 → 1/1 | gain:0.5 ]",
+ "[ 1/1 → 2/1 | gain:0.3 ]",
+ "[ 2/1 → 3/1 | gain:0.5 ]",
+ "[ 3/1 → 4/1 | gain:0.3 ]",
]
`;
exports[`runs examples > example "uniform" example index 2 1`] = `
[
- "[ 0/1 → 1/2 | note:c3 ]",
- "[ 1/2 → 1/1 | note:e3 ]",
- "[ 1/1 → 3/2 | note:c3 ]",
- "[ 3/2 → 2/1 | note:e3 ]",
- "[ 2/1 → 5/2 | note:c3 ]",
- "[ 5/2 → 3/1 | note:e3 ]",
- "[ 3/1 → 7/2 | note:c3 ]",
- "[ 7/2 → 4/1 | note:e3 ]",
+ "[ 0/1 → 1/2 | s:bd ]",
+ "[ 1/2 → 1/1 | s:sd ]",
+ "[ 1/1 → 3/2 | s:bd ]",
+ "[ 3/2 → 2/1 | s:sd ]",
+ "[ 2/1 → 5/2 | s:bd ]",
+ "[ 5/2 → 3/1 | s:sd ]",
+ "[ 3/1 → 7/2 | s:bd ]",
+ "[ 7/2 → 4/1 | s:sd ]",
]
`;