mirror of
https://codeberg.org/uzu/strudel
synced 2026-08-19 18:41:58 -04:00
Merge pull request 'Feat: Make default alignment configurable' (#1941) from glossing/alignment into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1941 Reviewed-by: Switch Angel AKA Jade Rose <daslyfe@noreply.codeberg.org>
This commit is contained in:
+117
-89
@@ -1055,91 +1055,88 @@ function _composeOp(a, b, func) {
|
||||
return func(a, b);
|
||||
}
|
||||
|
||||
// Make composers
|
||||
(function () {
|
||||
// pattern composers
|
||||
const composers = {
|
||||
set: [(a, b) => b],
|
||||
keep: [(a) => a],
|
||||
keepif: [(a, b) => (b ? a : undefined)],
|
||||
// pattern composers
|
||||
const COMPOSERS = {
|
||||
set: [(a, b) => b],
|
||||
keep: [(a) => a],
|
||||
keepif: [(a, b) => (b ? a : undefined)],
|
||||
|
||||
// numerical functions
|
||||
/**
|
||||
*
|
||||
* Assumes a pattern of numbers. Adds the given number to each item in the pattern.
|
||||
* @name add
|
||||
* @memberof Pattern
|
||||
* @tags math
|
||||
* @example
|
||||
* // Here, the triad 0, 2, 4 is shifted by different amounts
|
||||
* n("0 2 4".add("<0 3 4 0>")).scale("C:major")
|
||||
* // Without add, the equivalent would be:
|
||||
* // n("<[0 2 4] [3 5 7] [4 6 8] [0 2 4]>").scale("C:major")
|
||||
* @example
|
||||
* // You can also use add with notes:
|
||||
* note("c3 e3 g3".add("<0 5 7 0>"))
|
||||
* // Behind the scenes, the notes are converted to midi numbers:
|
||||
* // note("48 52 55".add("<0 5 7 0>"))
|
||||
*/
|
||||
add: [numeralArgs((a, b) => a + b)], // support string concatenation
|
||||
/**
|
||||
*
|
||||
* Like add, but the given numbers are subtracted.
|
||||
* @name sub
|
||||
* @memberof Pattern
|
||||
* @tags math
|
||||
* @example
|
||||
* n("0 2 4".sub("<0 1 2 3>")).scale("C4:minor")
|
||||
* // See add for more information.
|
||||
*/
|
||||
sub: [numeralArgs((a, b) => a - b)],
|
||||
/**
|
||||
*
|
||||
* Multiplies each number by the given factor.
|
||||
* @name mul
|
||||
* @memberof Pattern
|
||||
* @tags math
|
||||
* @example
|
||||
* "<1 1.5 [1.66, <2 2.33>]>*4".mul(150).freq()
|
||||
*/
|
||||
mul: [numeralArgs((a, b) => a * b)],
|
||||
/**
|
||||
*
|
||||
* Divides each number by the given factor.
|
||||
* @name div
|
||||
* @memberof Pattern
|
||||
* @tags math
|
||||
*/
|
||||
div: [numeralArgs((a, b) => a / b)],
|
||||
mod: [numeralArgs(_mod)],
|
||||
pow: [numeralArgs(Math.pow)],
|
||||
log2: [numeralArgs(Math.log2)],
|
||||
band: [numeralArgs((a, b) => a & b)],
|
||||
bor: [numeralArgs((a, b) => a | b)],
|
||||
bxor: [numeralArgs((a, b) => a ^ b)],
|
||||
blshift: [numeralArgs((a, b) => a << b)],
|
||||
brshift: [numeralArgs((a, b) => a >> b)],
|
||||
// numerical functions
|
||||
/**
|
||||
*
|
||||
* Assumes a pattern of numbers. Adds the given number to each item in the pattern.
|
||||
* @name add
|
||||
* @memberof Pattern
|
||||
* @tags math
|
||||
* @example
|
||||
* // Here, the triad 0, 2, 4 is shifted by different amounts
|
||||
* n("0 2 4".add("<0 3 4 0>")).scale("C:major")
|
||||
* // Without add, the equivalent would be:
|
||||
* // n("<[0 2 4] [3 5 7] [4 6 8] [0 2 4]>").scale("C:major")
|
||||
* @example
|
||||
* // You can also use add with notes:
|
||||
* note("c3 e3 g3".add("<0 5 7 0>"))
|
||||
* // Behind the scenes, the notes are converted to midi numbers:
|
||||
* // note("48 52 55".add("<0 5 7 0>"))
|
||||
*/
|
||||
add: [numeralArgs((a, b) => a + b)], // support string concatenation
|
||||
/**
|
||||
*
|
||||
* Like add, but the given numbers are subtracted.
|
||||
* @name sub
|
||||
* @memberof Pattern
|
||||
* @tags math
|
||||
* @example
|
||||
* n("0 2 4".sub("<0 1 2 3>")).scale("C4:minor")
|
||||
* // See add for more information.
|
||||
*/
|
||||
sub: [numeralArgs((a, b) => a - b)],
|
||||
/**
|
||||
*
|
||||
* Multiplies each number by the given factor.
|
||||
* @name mul
|
||||
* @memberof Pattern
|
||||
* @tags math
|
||||
* @example
|
||||
* "<1 1.5 [1.66, <2 2.33>]>*4".mul(150).freq()
|
||||
*/
|
||||
mul: [numeralArgs((a, b) => a * b)],
|
||||
/**
|
||||
*
|
||||
* Divides each number by the given factor.
|
||||
* @name div
|
||||
* @memberof Pattern
|
||||
* @tags math
|
||||
*/
|
||||
div: [numeralArgs((a, b) => a / b)],
|
||||
mod: [numeralArgs(_mod)],
|
||||
pow: [numeralArgs(Math.pow)],
|
||||
log2: [numeralArgs(Math.log2)],
|
||||
band: [numeralArgs((a, b) => a & b)],
|
||||
bor: [numeralArgs((a, b) => a | b)],
|
||||
bxor: [numeralArgs((a, b) => a ^ b)],
|
||||
blshift: [numeralArgs((a, b) => a << b)],
|
||||
brshift: [numeralArgs((a, b) => a >> b)],
|
||||
|
||||
// TODO - force numerical comparison if both look like numbers?
|
||||
lt: [(a, b) => a < b],
|
||||
gt: [(a, b) => a > b],
|
||||
lte: [(a, b) => a <= b],
|
||||
gte: [(a, b) => a >= b],
|
||||
eq: [(a, b) => a == b],
|
||||
eqt: [(a, b) => a === b],
|
||||
ne: [(a, b) => a != b],
|
||||
net: [(a, b) => a !== b],
|
||||
and: [(a, b) => a && b],
|
||||
or: [(a, b) => a || b],
|
||||
// TODO - force numerical comparison if both look like numbers?
|
||||
lt: [(a, b) => a < b],
|
||||
gt: [(a, b) => a > b],
|
||||
lte: [(a, b) => a <= b],
|
||||
gte: [(a, b) => a >= b],
|
||||
eq: [(a, b) => a == b],
|
||||
eqt: [(a, b) => a === b],
|
||||
ne: [(a, b) => a != b],
|
||||
net: [(a, b) => a !== b],
|
||||
and: [(a, b) => a && b],
|
||||
or: [(a, b) => a || b],
|
||||
|
||||
// bitwise ops
|
||||
func: [(a, b) => b(a)],
|
||||
};
|
||||
|
||||
const hows = ['In', 'Out', 'Mix', 'Squeeze', 'SqueezeOut', 'Reset', 'Restart', 'Poly'];
|
||||
// bitwise ops
|
||||
func: [(a, b) => b(a)],
|
||||
};
|
||||
|
||||
const _setupAlignments = () => {
|
||||
// generate methods to do what and how
|
||||
for (const [what, [op, preprocess]] of Object.entries(composers)) {
|
||||
for (const [what, [op, preprocess]] of Object.entries(COMPOSERS)) {
|
||||
// make plain version, e.g. pat._add(value) adds that plain value
|
||||
// to all the values in pat
|
||||
Pattern.prototype['_' + what] = function (value) {
|
||||
@@ -1148,16 +1145,18 @@ function _composeOp(a, b, func) {
|
||||
|
||||
// make patternified monster version
|
||||
Object.defineProperty(Pattern.prototype, what, {
|
||||
// Set to configurable so we can update if the default alignment changes
|
||||
configurable: true,
|
||||
// a getter that returns a function, so 'pat' can be
|
||||
// accessed by closures that are methods of that function..
|
||||
get: function () {
|
||||
const pat = this;
|
||||
|
||||
// wrap the 'in' function as default behaviour
|
||||
const wrapper = (...other) => pat[what]['in'](...other);
|
||||
const wrapper = (...other) => pat[what][DEFAULT_ALIGNMENT](...other);
|
||||
|
||||
// add methods to that function for each behaviour
|
||||
for (const how of hows) {
|
||||
for (const how of ALIGNMENTS) {
|
||||
wrapper[how.toLowerCase()] = function (...other) {
|
||||
var howpat = pat;
|
||||
other = sequence(other);
|
||||
@@ -1182,15 +1181,23 @@ function _composeOp(a, b, func) {
|
||||
return wrapper;
|
||||
},
|
||||
});
|
||||
|
||||
// Default op to 'set', e.g. pat.squeeze(pat2) = pat.set.squeeze(pat2)
|
||||
for (const how of hows) {
|
||||
Pattern.prototype[how.toLowerCase()] = function (...args) {
|
||||
return this.set[how.toLowerCase()](args);
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let DEFAULT_ALIGNMENT = 'in';
|
||||
const ALIGNMENTS = ['In', 'Out', 'Mix', 'Squeeze', 'SqueezeOut', 'Reset', 'Restart', 'Poly'];
|
||||
const ALIGNMENT_KEYS = ALIGNMENTS.map((how) => how.toLowerCase());
|
||||
|
||||
// Make composers
|
||||
(function () {
|
||||
_setupAlignments();
|
||||
|
||||
// Default op to 'set', e.g. pat.squeeze(pat2) = pat.set.squeeze(pat2)
|
||||
for (const how of ALIGNMENTS) {
|
||||
Pattern.prototype[how.toLowerCase()] = function (...args) {
|
||||
return this.set[how.toLowerCase()](args);
|
||||
};
|
||||
}
|
||||
// binary composers
|
||||
/**
|
||||
* Applies the given structure to the pattern:
|
||||
@@ -1249,6 +1256,27 @@ function _composeOp(a, b, func) {
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Sets the default method of combining events from two patterns (aka [alignment](https://strudel.cc/technical-manual/alignment/)) in Strudel.
|
||||
* The default method is 'in', meaning that patterns to the left will (typically) dictate the event timings when combined with patterns to the right.
|
||||
* By changing alignment to 'out', the opposite will happen. With 'mix', they will combine their event timings.
|
||||
*
|
||||
* Note that we say the _default_ method, because alignments can also be set explicitly with calls like
|
||||
* 'add.mix', 'set.squeeze', etc.
|
||||
*
|
||||
* @param {string} method Default join method to use. Options: 'in', 'out', 'mix', 'squeeze', 'squeezeout', 'reset', 'restart', 'poly'
|
||||
* @example
|
||||
* setDefaultJoin('mix') // also try 'in', 'out', 'squeeze', etc.
|
||||
* s("saw").vel("1 0.5").note("F A C E").delay("0 0.2 0.3")
|
||||
*/
|
||||
export const setDefaultJoin = (alignment) => {
|
||||
alignment = alignment?.toLowerCase();
|
||||
if (DEFAULT_ALIGNMENT !== alignment && ALIGNMENT_KEYS.includes(alignment)) {
|
||||
DEFAULT_ALIGNMENT = alignment;
|
||||
_setupAlignments();
|
||||
}
|
||||
};
|
||||
|
||||
// aliases
|
||||
export const polyrhythm = stack;
|
||||
export const pr = stack;
|
||||
|
||||
@@ -10874,6 +10874,35 @@ exports[`runs examples > example "seqPLoop" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "setDefaultJoin" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | s:saw velocity:1 note:F delay:0 ]",
|
||||
"[ 1/4 → 1/3 | s:saw velocity:1 note:A delay:0 ]",
|
||||
"[ 1/3 → 1/2 | s:saw velocity:1 note:A delay:0.2 ]",
|
||||
"[ 1/2 → 2/3 | s:saw velocity:0.5 note:C delay:0.2 ]",
|
||||
"[ 2/3 → 3/4 | s:saw velocity:0.5 note:C delay:0.3 ]",
|
||||
"[ 3/4 → 1/1 | s:saw velocity:0.5 note:E delay:0.3 ]",
|
||||
"[ 1/1 → 5/4 | s:saw velocity:1 note:F delay:0 ]",
|
||||
"[ 5/4 → 4/3 | s:saw velocity:1 note:A delay:0 ]",
|
||||
"[ 4/3 → 3/2 | s:saw velocity:1 note:A delay:0.2 ]",
|
||||
"[ 3/2 → 5/3 | s:saw velocity:0.5 note:C delay:0.2 ]",
|
||||
"[ 5/3 → 7/4 | s:saw velocity:0.5 note:C delay:0.3 ]",
|
||||
"[ 7/4 → 2/1 | s:saw velocity:0.5 note:E delay:0.3 ]",
|
||||
"[ 2/1 → 9/4 | s:saw velocity:1 note:F delay:0 ]",
|
||||
"[ 9/4 → 7/3 | s:saw velocity:1 note:A delay:0 ]",
|
||||
"[ 7/3 → 5/2 | s:saw velocity:1 note:A delay:0.2 ]",
|
||||
"[ 5/2 → 8/3 | s:saw velocity:0.5 note:C delay:0.2 ]",
|
||||
"[ 8/3 → 11/4 | s:saw velocity:0.5 note:C delay:0.3 ]",
|
||||
"[ 11/4 → 3/1 | s:saw velocity:0.5 note:E delay:0.3 ]",
|
||||
"[ 3/1 → 13/4 | s:saw velocity:1 note:F delay:0 ]",
|
||||
"[ 13/4 → 10/3 | s:saw velocity:1 note:A delay:0 ]",
|
||||
"[ 10/3 → 7/2 | s:saw velocity:1 note:A delay:0.2 ]",
|
||||
"[ 7/2 → 11/3 | s:saw velocity:0.5 note:C delay:0.2 ]",
|
||||
"[ 11/3 → 15/4 | s:saw velocity:0.5 note:C delay:0.3 ]",
|
||||
"[ 15/4 → 4/1 | s:saw velocity:0.5 note:E delay:0.3 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "setGainCurve" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | s:bd gain:0.5 ]",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { afterEach } from 'vitest';
|
||||
import { useRNG } from './packages/core/signal.mjs';
|
||||
import { setDefaultJoin } from './packages/core/pattern.mjs';
|
||||
|
||||
afterEach(() => {
|
||||
// Avoid bleed between tests
|
||||
useRNG('legacy');
|
||||
setDefaultJoin('in');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user