mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-30 08:23:18 -04:00
Update uniform method to take an option object
This commit is contained in:
@@ -8,7 +8,6 @@
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"build": "vite build",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
// Fix `ReferenceError: self is not defined`
|
||||
// when importing picogl in tests
|
||||
globalThis.self = {};
|
||||
+76
-104
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
const setCtx = (uniformParam) => (ctx) => ({
|
||||
uniformParam,
|
||||
onTrigger: () => {},
|
||||
dominantTrigger: true,
|
||||
...ctx,
|
||||
});
|
||||
|
||||
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 {
|
||||
console.error('Uniform applied to a non number pattern');
|
||||
throw 'Invalid position' + dest;
|
||||
}
|
||||
return v;
|
||||
};
|
||||
const getUniformPosition = (value, dest) => {
|
||||
if (typeof dest === 'string') {
|
||||
return [dest, 0];
|
||||
} else {
|
||||
return [dest[0], getPosition(value, dest[1])];
|
||||
}
|
||||
};
|
||||
|
||||
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 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;
|
||||
});
|
||||
});
|
||||
|
||||
function getNotePosition(name, value) {
|
||||
// Initialize uniform state
|
||||
if (!_uniforms[name]) _uniforms[name] = {};
|
||||
const uniform = _uniforms[name];
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
uniform.test.mjs - <short description TODO>
|
||||
Copyright (C) 2024 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/test/pattern.test.mjs>
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 });
|
||||
});
|
||||
});
|
||||
@@ -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'),
|
||||
|
||||
@@ -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 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user