Skip to content

Commit

Permalink
Update uniform method to take an option object
Browse files Browse the repository at this point in the history
  • Loading branch information
TristanCacqueray committed Oct 20, 2024
1 parent 9e0b840 commit 1baa6da
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 148 deletions.
1 change: 0 additions & 1 deletion packages/shader/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"main": "dist/index.mjs"
},
"scripts": {
"test": "vitest run",
"build": "vite build",
"prepublishOnly": "npm run build"
},
Expand Down
3 changes: 0 additions & 3 deletions packages/shader/testSetup.mjs

This file was deleted.

178 changes: 75 additions & 103 deletions packages/shader/uniform.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
} 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;
});
});
22 changes: 0 additions & 22 deletions packages/shader/uniform.test.mjs

This file was deleted.

3 changes: 0 additions & 3 deletions packages/shader/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
31 changes: 15 additions & 16 deletions test/__snapshots__/examples.test.mjs.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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 ]",
]
`;

Expand Down

0 comments on commit 1baa6da

Please sign in to comment.