mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-24 13:59:07 -04:00
Merge branch 'main' into add-program-change
This commit is contained in:
+27
-41
@@ -388,7 +388,7 @@ export class Pattern {
|
||||
|
||||
polyJoin = function () {
|
||||
const pp = this;
|
||||
return pp.fmap((p) => p.repeat(pp._steps.div(p._steps))).outerJoin();
|
||||
return pp.fmap((p) => p.extend(pp._steps.div(p._steps))).outerJoin();
|
||||
};
|
||||
|
||||
polyBind(func) {
|
||||
@@ -1244,6 +1244,16 @@ export function reify(thing) {
|
||||
return pure(thing);
|
||||
}
|
||||
|
||||
/** Takes a list of patterns, and returns a pattern of lists.
|
||||
*/
|
||||
export function sequenceP(pats) {
|
||||
let result = pure([]);
|
||||
for (const pat of pats) {
|
||||
result = result.bind((list) => pat.fmap((v) => list.concat([v])));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** The given items are played at the same time at the same length.
|
||||
*
|
||||
* @return {Pattern}
|
||||
@@ -1318,7 +1328,7 @@ export function stackBy(by, ...pats) {
|
||||
left: stackLeft,
|
||||
right: stackRight,
|
||||
expand: stack,
|
||||
repeat: (...args) => polymeterSteps(steps, ...args),
|
||||
repeat: (...args) => polymeter(...args).steps(steps),
|
||||
};
|
||||
return by
|
||||
.inhabit(lookup)
|
||||
@@ -1820,7 +1830,10 @@ export const { fastGap, fastgap } = register(['fastGap', 'fastgap'], function (f
|
||||
export const focus = register('focus', function (b, e, pat) {
|
||||
b = Fraction(b);
|
||||
e = Fraction(e);
|
||||
return pat._fast(Fraction(1).div(e.sub(b))).late(b.cyclePos());
|
||||
return pat
|
||||
._early(b.sam())
|
||||
._fast(Fraction(1).div(e.sub(b)))
|
||||
._late(b);
|
||||
});
|
||||
|
||||
export const { focusSpan, focusspan } = register(['focusSpan', 'focusspan'], function (span, pat) {
|
||||
@@ -2030,7 +2043,7 @@ export const zoom = register('zoom', function (s, e, pat) {
|
||||
return nothing;
|
||||
}
|
||||
const d = e.sub(s);
|
||||
const steps = __steps ? pat._steps.mulmaybe(d) : undefined;
|
||||
const steps = __steps ? pat._steps?.mulmaybe(d) : undefined;
|
||||
return pat
|
||||
.withQuerySpan((span) => span.withCycle((t) => t.mul(d).add(s)))
|
||||
.withHapSpan((span) => span.withCycle((t) => t.sub(s).div(d)))
|
||||
@@ -2635,34 +2648,10 @@ export function _polymeterListSteps(steps, ...args) {
|
||||
/**
|
||||
* *Experimental*
|
||||
*
|
||||
* Aligns the steps of the patterns, to match the given number of steps per cycle, creating polymeters.
|
||||
*
|
||||
* @name polymeterSteps
|
||||
* @param {number} steps how many items are placed in one cycle
|
||||
* @param {any[]} patterns one or more patterns
|
||||
* @example
|
||||
* // the same as "{c d, e f g}%4"
|
||||
* polymeterSteps(4, "c d", "e f g").note()
|
||||
*/
|
||||
export function polymeterSteps(steps, ...args) {
|
||||
if (args.length == 0) {
|
||||
return silence;
|
||||
}
|
||||
if (Array.isArray(args[0])) {
|
||||
// Support old behaviour
|
||||
return _polymeterListSteps(steps, ...args);
|
||||
}
|
||||
|
||||
return polymeter(...args).pace(steps);
|
||||
}
|
||||
|
||||
/**
|
||||
* *Experimental*
|
||||
*
|
||||
* Aligns the steps of the patterns, to match the steps per cycle of the first pattern, creating polymeters. See `polymeterSteps` to set the target steps explicitly.
|
||||
* Aligns the steps of the patterns, creating polymeters. The patterns are repeated until they all fit the cycle. For example, in the below the first pattern is repeated twice, and the second is repeated three times, to fit the lowest common multiple of six steps.
|
||||
* @synonyms pm
|
||||
* @example
|
||||
* // The same as note("{c eb g, c2 g2}")
|
||||
* // The same as note("{c eb g, c2 g2}%6")
|
||||
* polymeter("c eb g", "c2 g2").note()
|
||||
*
|
||||
*/
|
||||
@@ -2678,13 +2667,12 @@ export function polymeter(...args) {
|
||||
if (args.length == 0) {
|
||||
return silence;
|
||||
}
|
||||
const steps = args[0]._steps;
|
||||
const steps = lcm(...args.map((x) => x._steps));
|
||||
if (steps.eq(Fraction(0))) {
|
||||
return nothing;
|
||||
}
|
||||
const [head, ...tail] = args;
|
||||
|
||||
const result = stack(head, ...tail.map((pat) => pat._slow(pat._steps.div(steps))));
|
||||
const result = stack(...args.map((x) => x.pace(steps)));
|
||||
result._steps = steps;
|
||||
return result;
|
||||
}
|
||||
@@ -2845,16 +2833,16 @@ export const drop = stepRegister('drop', function (i, pat) {
|
||||
/**
|
||||
* *Experimental*
|
||||
*
|
||||
* `repeat` is similar to `fast` in that it 'speeds up' the pattern, but it also increases the step count
|
||||
* accordingly. So `stepcat("a b".repeat(2), "c d")` would be the same as `"a b a b c d"`, whereas
|
||||
* `extend` is similar to `fast` in that it increases its density, but it also increases the step count
|
||||
* accordingly. So `stepcat("a b".extend(2), "c d")` would be the same as `"a b a b c d"`, whereas
|
||||
* `stepcat("a b".fast(2), "c d")` would be the same as `"[a b] [a b] c d"`.
|
||||
* @example
|
||||
* stepcat(
|
||||
* sound("bd bd - cp").repeat(2),
|
||||
* sound("bd bd - cp").extend(2),
|
||||
* sound("bd - sd -")
|
||||
* ).pace(8)
|
||||
*/
|
||||
export const repeat = stepRegister('repeat', function (factor, pat) {
|
||||
export const extend = stepRegister('extend', function (factor, pat) {
|
||||
return pat.fast(factor).expand(factor);
|
||||
});
|
||||
|
||||
@@ -3052,8 +3040,6 @@ export const timeCat = stepcat;
|
||||
// Deprecated stepwise aliases
|
||||
export const s_cat = stepcat;
|
||||
export const s_alt = stepalt;
|
||||
export const s_polymeterSteps = polymeterSteps;
|
||||
Pattern.prototype.s_polymeterSteps = Pattern.prototype.polymeterSteps;
|
||||
export const s_polymeter = polymeter;
|
||||
Pattern.prototype.s_polymeter = Pattern.prototype.polymeter;
|
||||
export const s_taper = shrink;
|
||||
@@ -3066,8 +3052,8 @@ export const s_sub = drop;
|
||||
Pattern.prototype.s_sub = Pattern.prototype.drop;
|
||||
export const s_expand = expand;
|
||||
Pattern.prototype.s_expand = Pattern.prototype.expand;
|
||||
export const s_extend = repeat;
|
||||
Pattern.prototype.s_extend = Pattern.prototype.repeat;
|
||||
export const s_extend = extend;
|
||||
Pattern.prototype.s_extend = Pattern.prototype.extend;
|
||||
export const s_contract = contract;
|
||||
Pattern.prototype.s_contract = Pattern.prototype.contract;
|
||||
export const s_tour = tour;
|
||||
|
||||
+96
-13
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
*/
|
||||
|
||||
import { Hap } from './hap.mjs';
|
||||
import { Pattern, fastcat, pure, register, reify, silence, stack } from './pattern.mjs';
|
||||
import { Pattern, fastcat, pure, register, reify, silence, stack, sequenceP } from './pattern.mjs';
|
||||
import Fraction from './fraction.mjs';
|
||||
|
||||
import { id, keyAlias, getCurrentKeyboardState } from './util.mjs';
|
||||
@@ -20,9 +20,6 @@ export const signal = (func) => {
|
||||
return new Pattern(query);
|
||||
};
|
||||
|
||||
export const isaw = signal((t) => 1 - (t % 1));
|
||||
export const isaw2 = isaw.toBipolar();
|
||||
|
||||
/**
|
||||
* A sawtooth signal between 0 and 1.
|
||||
*
|
||||
@@ -36,8 +33,40 @@ export const isaw2 = isaw.toBipolar();
|
||||
*
|
||||
*/
|
||||
export const saw = signal((t) => t % 1);
|
||||
|
||||
/**
|
||||
* A sawtooth signal between -1 and 1 (like `saw`, but bipolar).
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const saw2 = saw.toBipolar();
|
||||
|
||||
/**
|
||||
* A sawtooth signal between 1 and 0 (like `saw`, but flipped).
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* note("<c3 [eb3,g3] g2 [g3,bb3]>*8")
|
||||
* .clip(isaw.slow(2))
|
||||
* @example
|
||||
* n(isaw.range(0,8).segment(8))
|
||||
* .scale('C major')
|
||||
*
|
||||
*/
|
||||
export const isaw = signal((t) => 1 - (t % 1));
|
||||
|
||||
/**
|
||||
* A sawtooth signal between 1 and -1 (like `saw2`, but flipped).
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const isaw2 = isaw.toBipolar();
|
||||
|
||||
/**
|
||||
* A sine signal between -1 and 1 (like `sine`, but bipolar).
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
|
||||
|
||||
/**
|
||||
@@ -61,6 +90,12 @@ export const sine = sine2.fromBipolar();
|
||||
*
|
||||
*/
|
||||
export const cosine = sine._early(Fraction(1).div(4));
|
||||
|
||||
/**
|
||||
* A cosine signal between -1 and 1 (like `cosine`, but bipolar).
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const cosine2 = sine2._early(Fraction(1).div(4));
|
||||
|
||||
/**
|
||||
@@ -72,6 +107,12 @@ export const cosine2 = sine2._early(Fraction(1).div(4));
|
||||
*
|
||||
*/
|
||||
export const square = signal((t) => Math.floor((t * 2) % 2));
|
||||
|
||||
/**
|
||||
* A square signal between -1 and 1 (like `square`, but bipolar).
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const square2 = square.toBipolar();
|
||||
|
||||
/**
|
||||
@@ -82,9 +123,37 @@ export const square2 = square.toBipolar();
|
||||
* n(tri.segment(8).range(0,7)).scale("C:minor")
|
||||
*
|
||||
*/
|
||||
export const tri = fastcat(isaw, saw);
|
||||
export const tri2 = fastcat(isaw2, saw2);
|
||||
export const tri = fastcat(saw, isaw);
|
||||
|
||||
/**
|
||||
* A triangle signal between -1 and 1 (like `tri`, but bipolar).
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const tri2 = fastcat(saw2, isaw2);
|
||||
|
||||
/**
|
||||
* An inverted triangle signal between 1 and 0 (like `tri`, but flipped).
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @example
|
||||
* n(itri.segment(8).range(0,7)).scale("C:minor")
|
||||
*
|
||||
*/
|
||||
export const itri = fastcat(isaw, saw);
|
||||
|
||||
/**
|
||||
* An inverted triangle signal between -1 and 1 (like `itri`, but bipolar).
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const itri2 = fastcat(isaw2, saw2);
|
||||
|
||||
/**
|
||||
* A signal representing the cycle time.
|
||||
*
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export const time = signal(id);
|
||||
|
||||
/**
|
||||
@@ -364,19 +433,30 @@ export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs);
|
||||
export const randcat = chooseCycles;
|
||||
|
||||
const _wchooseWith = function (pat, ...pairs) {
|
||||
// A list of patterns of values
|
||||
const values = pairs.map((pair) => reify(pair[0]));
|
||||
|
||||
// A list of weight patterns
|
||||
const weights = [];
|
||||
let accum = 0;
|
||||
|
||||
let total = pure(0);
|
||||
for (const pair of pairs) {
|
||||
accum += pair[1];
|
||||
weights.push(accum);
|
||||
// 'add' accepts either values or patterns of values here, so no need
|
||||
// to explicitly reify
|
||||
total = total.add(pair[1]);
|
||||
// accumulate our list of weight patterns
|
||||
weights.push(total);
|
||||
}
|
||||
const total = accum;
|
||||
// a pattern of lists of weights
|
||||
const weightspat = sequenceP(weights);
|
||||
|
||||
// Takes a number from 0-1, returns a pattern of patterns of values
|
||||
const match = function (r) {
|
||||
const find = r * total;
|
||||
return values[weights.findIndex((x) => x > find, weights)];
|
||||
const findpat = total.mul(r);
|
||||
return weightspat.fmap((weights) => (find) => values[weights.findIndex((x) => x > find, weights)]).appLeft(findpat);
|
||||
};
|
||||
return pat.fmap(match);
|
||||
// This returns a pattern of patterns.. The innerJoin is in wchooseCycles
|
||||
return pat.bind(match);
|
||||
};
|
||||
|
||||
const wchooseWith = (...args) => _wchooseWith(...args).outerJoin();
|
||||
@@ -398,6 +478,9 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
|
||||
* wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8)
|
||||
* @example
|
||||
* wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s()
|
||||
* @example
|
||||
* // The probability can itself be a pattern
|
||||
* wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s()
|
||||
*/
|
||||
export const wchooseCycles = (...pairs) => _wchooseWith(rand.segment(1), ...pairs).innerJoin();
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
sequence,
|
||||
palindrome,
|
||||
polymeter,
|
||||
polymeterSteps,
|
||||
polyrhythm,
|
||||
silence,
|
||||
fast,
|
||||
@@ -611,17 +610,10 @@ describe('Pattern', () => {
|
||||
});
|
||||
describe('polymeter()', () => {
|
||||
it('Can layer up cycles, stepwise, with lists', () => {
|
||||
expect(polymeterSteps(3, ['d', 'e']).firstCycle()).toStrictEqual(
|
||||
fastcat(pure('d'), pure('e'), pure('d')).firstCycle(),
|
||||
);
|
||||
|
||||
expect(polymeter(['a', 'b', 'c'], ['d', 'e']).fast(2).firstCycle()).toStrictEqual(
|
||||
stack(sequence('a', 'b', 'c', 'a', 'b', 'c'), sequence('d', 'e', 'd', 'e', 'd', 'e')).firstCycle(),
|
||||
);
|
||||
});
|
||||
it('Can layer up cycles, stepwise, with weighted patterns', () => {
|
||||
sameFirst(polymeterSteps(3, sequence('a', 'b')).fast(2), sequence('a', 'b', 'a', 'b', 'a', 'b'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('firstOf()', () => {
|
||||
@@ -1265,4 +1257,14 @@ describe('Pattern', () => {
|
||||
expect(s('bev').chop(8).loopAt(2)._steps).toStrictEqual(Fraction(4));
|
||||
});
|
||||
});
|
||||
describe('bite', () => {
|
||||
it('works with uneven patterns', () => {
|
||||
sameFirst(
|
||||
fastcat(slowcat('a', 'b', 'c', 'd', 'e'), slowcat(1, 2, 3, 4, 5))
|
||||
.bite(2, stepcat(pure(0), pure(1).expand(2)))
|
||||
.fast(5),
|
||||
stepcat(slowcat('a', 'b', 'c', 'd', 'e'), slowcat(1, 2, 3, 4, 5).expand(2)).fast(5),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user