mirror of
https://codeberg.org/uzu/strudel
synced 2026-08-15 09:38:28 -04:00
Merge pull request 'Fix multiple cases where % was used instead of _mod()' (#2092) from tzwaan/saw-modulo-bug into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/2092
This commit is contained in:
@@ -1002,7 +1002,7 @@ export const arpWith = register('arpWith', (func, pat) => {
|
||||
* */
|
||||
export const arp = register(
|
||||
'arp',
|
||||
(indices, pat) => pat.arpWith((haps) => reify(indices).fmap((i) => haps[i % haps.length])),
|
||||
(indices, pat) => pat.arpWith((haps) => reify(indices).fmap((i) => haps[_mod(i, haps.length)])),
|
||||
false,
|
||||
);
|
||||
|
||||
@@ -1529,7 +1529,9 @@ export function slowcat(...pats) {
|
||||
// Array test here is to avoid infinite recursions..
|
||||
pats = pats.map((pat) => (Array.isArray(pat) ? fastcat(...pat) : reify(pat)));
|
||||
|
||||
if (pats.length == 1) {
|
||||
if (!pats.length) {
|
||||
return silence;
|
||||
} else if (pats.length == 1) {
|
||||
return pats[0];
|
||||
}
|
||||
|
||||
@@ -1537,10 +1539,6 @@ export function slowcat(...pats) {
|
||||
const span = state.span;
|
||||
const pat_n = _mod(span.begin.sam(), pats.length);
|
||||
const pat = pats[pat_n];
|
||||
if (!pat) {
|
||||
// pat_n can be negative, if the span is in the past..
|
||||
return [];
|
||||
}
|
||||
// A bit of maths to make sure that cycles from constituent patterns aren't skipped.
|
||||
// For example if three patterns are slowcat-ed, the fourth cycle of the result should
|
||||
// be the second (rather than fourth) cycle from the first pattern.
|
||||
@@ -1557,11 +1555,14 @@ export function slowcat(...pats) {
|
||||
* @return {Pattern}
|
||||
*/
|
||||
export function slowcatPrime(...pats) {
|
||||
if (!pats.length) {
|
||||
return silence;
|
||||
}
|
||||
pats = pats.map(reify);
|
||||
const query = function (state) {
|
||||
const pat_n = Math.floor(state.span.begin) % pats.length;
|
||||
const pat = pats[pat_n]; // can be undefined for same cases e.g. /#cHVyZSg0MikKICAuZXZlcnkoMyxhZGQoNykpCiAgLmxhdGUoLjUp
|
||||
return pat?.query(state) || [];
|
||||
const pat_n = _mod(Math.floor(state.span.begin), pats.length);
|
||||
const pat = pats[pat_n];
|
||||
return pat.query(state);
|
||||
};
|
||||
return new Pattern(query).splitQueries();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,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, sequenceP } from './pattern.mjs';
|
||||
import { _mod } from './util.mjs';
|
||||
import Fraction from './fraction.mjs';
|
||||
|
||||
import { id, keyAlias, getCurrentKeyboardState } from './util.mjs';
|
||||
@@ -33,7 +34,7 @@ export const signal = (func) => {
|
||||
* .scale('C major')
|
||||
*
|
||||
*/
|
||||
export const saw = signal((t) => t % 1);
|
||||
export const saw = signal((t) => _mod(t, 1));
|
||||
|
||||
/**
|
||||
* A sawtooth signal between -1 and 1 (like `saw`, but bipolar).
|
||||
@@ -56,7 +57,7 @@ export const saw2 = saw.toBipolar();
|
||||
* .scale('C major')
|
||||
*
|
||||
*/
|
||||
export const isaw = signal((t) => 1 - (t % 1));
|
||||
export const isaw = signal((t) => 1 - _mod(t, 1));
|
||||
|
||||
/**
|
||||
* A sawtooth signal between 1 and -1 (like `saw2`, but flipped).
|
||||
@@ -113,7 +114,7 @@ export const cosine2 = sine2._early(Fraction(1).div(4));
|
||||
* n(square.segment(4).range(0,7)).scale("C:minor")
|
||||
*
|
||||
*/
|
||||
export const square = signal((t) => Math.floor((t * 2) % 2));
|
||||
export const square = signal((t) => Math.floor(_mod(t * 2, 2)));
|
||||
|
||||
/**
|
||||
* A square signal between -1 and 1 (like `square`, but bipolar).
|
||||
@@ -123,6 +124,22 @@ export const square = signal((t) => Math.floor((t * 2) % 2));
|
||||
*/
|
||||
export const square2 = square.toBipolar();
|
||||
|
||||
/**
|
||||
* A square signal between 1 and 0 (like `square` but flipped).
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @tags generators
|
||||
*/
|
||||
export const isquare = signal((t) => 1 - Math.floor(_mod(t * 2, 2)));
|
||||
|
||||
/**
|
||||
* A square signal between 1 and -1 (like `isquare`, but bipolar).
|
||||
*
|
||||
* @return {Pattern}
|
||||
* @tags generators
|
||||
*/
|
||||
export const isquare2 = isquare.toBipolar();
|
||||
|
||||
/**
|
||||
* A triangle signal between 0 and 1.
|
||||
*
|
||||
@@ -396,7 +413,7 @@ export const randrun = (n) => {
|
||||
.map((n, i) => [n, i])
|
||||
.sort((a, b) => (a[0] > b[0]) - (a[0] < b[0]))
|
||||
.map((x) => x[1]);
|
||||
const i = t.cyclePos().mul(n).floor() % n;
|
||||
const i = _mod(t.cyclePos().mul(n).floor(), n);
|
||||
return nums[i];
|
||||
})._segment(n);
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
fastcat,
|
||||
firstOf,
|
||||
slowcat,
|
||||
slowcatPrime,
|
||||
cat,
|
||||
sequence,
|
||||
palindrome,
|
||||
@@ -53,6 +54,7 @@ import {
|
||||
stepcat,
|
||||
sometimes,
|
||||
expand,
|
||||
arp,
|
||||
} from '../index.mjs';
|
||||
|
||||
import { log, logValues } from '../pattern.mjs';
|
||||
@@ -544,6 +546,9 @@ describe('Pattern', () => {
|
||||
});
|
||||
});
|
||||
describe('slowcat()', () => {
|
||||
it('Can be empty', () => {
|
||||
expect(slowcat().firstCycle()).toStrictEqual([]);
|
||||
});
|
||||
it('Can concatenate things slowly', () => {
|
||||
expect(
|
||||
slowcat('a', 'b')
|
||||
@@ -576,6 +581,38 @@ describe('Pattern', () => {
|
||||
sameFirst(slowcat('a', ['b', 'c']).fast(4), sequence('a', ['b', 'c']).fast(2));
|
||||
});
|
||||
});
|
||||
describe('slowcatPrime()', () => {
|
||||
it('Can be empty', () => {
|
||||
expect(slowcatPrime().firstCycle()).toStrictEqual([]);
|
||||
});
|
||||
it('Can slowcat patterns swapping back and forth skipping the expected notes', () => {
|
||||
expect(
|
||||
slowcatPrime(fastcat(0, 1, 2, 3).slow(2), fastcat(4, 5, 6, 7).slow(2))
|
||||
.fast(4)
|
||||
.firstCycle()
|
||||
.map((a) => a.value),
|
||||
).toStrictEqual([0, 1, 6, 7, 0, 1, 6, 7]);
|
||||
});
|
||||
it('Can go into negative time', () => {
|
||||
expect(
|
||||
slowcatPrime(fastcat(0, 1, 2, 3).slow(2), fastcat(4, 5, 6, 7).slow(2))
|
||||
.fast(4)
|
||||
.late(8)
|
||||
.firstCycle()
|
||||
.map((a) => a.value),
|
||||
).toStrictEqual([0, 1, 6, 7, 0, 1, 6, 7]);
|
||||
});
|
||||
});
|
||||
describe('arp()', () => {
|
||||
it('It wraps around with both positive and negative indices', () => {
|
||||
expect(
|
||||
stack('a', 'b', 'c')
|
||||
.arp(fastcat(-3, -2, -1, 0, 1, 2, 3, 4))
|
||||
.firstCycle()
|
||||
.map((a) => a.value),
|
||||
).toStrictEqual(['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b']);
|
||||
});
|
||||
});
|
||||
describe('rev()', () => {
|
||||
it('Can reverse things', () => {
|
||||
expect(
|
||||
|
||||
@@ -8,7 +8,23 @@ import Fraction from 'fraction.js';
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
|
||||
import { saw, saw2, isaw, isaw2, per, perx, cyclesPer } from '../signal.mjs';
|
||||
import {
|
||||
saw,
|
||||
saw2,
|
||||
isaw,
|
||||
isaw2,
|
||||
tri,
|
||||
tri2,
|
||||
itri,
|
||||
itri2,
|
||||
square,
|
||||
square2,
|
||||
isquare,
|
||||
isquare2,
|
||||
per,
|
||||
perx,
|
||||
cyclesPer,
|
||||
} from '../signal.mjs';
|
||||
import { fastcat, sequence, State, TimeSpan, Hap, note } from '../index.mjs';
|
||||
|
||||
const st = (begin, end) => new State(ts(begin, end));
|
||||
@@ -24,17 +40,61 @@ const sameFirst = (a, b) => {
|
||||
|
||||
describe('signal()', () => {
|
||||
it('Can make saw/saw2', () => {
|
||||
expect(saw.struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(0, 1 / 4, 1 / 2, 3 / 4).firstCycle(),
|
||||
);
|
||||
|
||||
expect(saw.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(0, 0.25, 0.5, 0.75).firstCycle());
|
||||
expect(saw2.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(-1, -0.5, 0, 0.5).firstCycle());
|
||||
});
|
||||
it('Can make isaw/isaw2', () => {
|
||||
expect(isaw.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(1, 0.75, 0.5, 0.25).firstCycle());
|
||||
|
||||
expect(isaw2.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(1, 0.5, 0, -0.5).firstCycle());
|
||||
});
|
||||
it('Can make tri/tri2', () => {
|
||||
expect(tri.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(0, 0.5, 1, 0.5).firstCycle());
|
||||
expect(tri2.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(-1, 0, 1, 0).firstCycle());
|
||||
});
|
||||
it('Can make itri/itri2', () => {
|
||||
expect(itri.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(1, 0.5, 0, 0.5).firstCycle());
|
||||
expect(itri2.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(1, 0, -1, 0).firstCycle());
|
||||
});
|
||||
it('Can make square/square2', () => {
|
||||
expect(square.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(0, 0, 1, 1).firstCycle());
|
||||
expect(square2.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(-1, -1, 1, 1).firstCycle());
|
||||
});
|
||||
it('Can make isquare/isquare2', () => {
|
||||
expect(isquare.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(1, 1, 0, 0).firstCycle());
|
||||
expect(isquare2.struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(1, 1, -1, -1).firstCycle());
|
||||
});
|
||||
it('Can go into negative time', () => {
|
||||
expect(saw.late(1).struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(0, 0.25, 0.5, 0.75).firstCycle(),
|
||||
);
|
||||
expect(saw2.late(1).struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(-1, -0.5, 0, 0.5).firstCycle(),
|
||||
);
|
||||
expect(isaw.late(1).struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(1, 0.75, 0.5, 0.25).firstCycle(),
|
||||
);
|
||||
expect(isaw2.late(1).struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(1, 0.5, 0, -0.5).firstCycle(),
|
||||
);
|
||||
expect(tri.late(1).struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(0, 0.5, 1, 0.5).firstCycle(),
|
||||
);
|
||||
expect(tri2.late(1).struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(-1, 0, 1, 0).firstCycle());
|
||||
expect(itri.late(1).struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(1, 0.5, 0, 0.5).firstCycle(),
|
||||
);
|
||||
expect(itri2.late(1).struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(1, 0, -1, 0).firstCycle());
|
||||
expect(square.late(1).struct(true, true, true, true).firstCycle()).toStrictEqual(sequence(0, 0, 1, 1).firstCycle());
|
||||
expect(square2.late(1).struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(-1, -1, 1, 1).firstCycle(),
|
||||
);
|
||||
expect(isquare.late(1).struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(1, 1, 0, 0).firstCycle(),
|
||||
);
|
||||
expect(isquare2.late(1).struct(true, true, true, true).firstCycle()).toStrictEqual(
|
||||
sequence(1, 1, -1, -1).firstCycle(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cyclesPer', () => {
|
||||
|
||||
@@ -7203,8 +7203,12 @@ exports[`renders tunes > tune: holyflute 1`] = `
|
||||
|
||||
exports[`renders tunes > tune: juxUndTollerei 1`] = `
|
||||
[
|
||||
"[ -99/200 ⇜ (0/1 → 1/200) | note:63 s:triangle pan:0 cutoff:758.852817928549 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||
"[ -99/200 ⇜ (0/1 → 1/200) | note:67 s:triangle pan:1 color:green cutoff:758.852817928549 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||
"[ 0/1 → 1/4 | note:c3 s:sawtooth pan:0 cutoff:1100 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||
"[ 0/1 → 1/4 | note:bb3 s:sawtooth pan:1 color:green cutoff:1100 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||
"[ 1/200 → 101/200 | note:55 s:triangle pan:0 cutoff:1103.534282651425 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||
"[ 1/200 → 101/200 | note:65 s:triangle pan:1 color:green cutoff:1103.534282651425 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||
"[ 1/4 → 1/2 | note:eb3 s:sawtooth pan:0 cutoff:1275.581289814515 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||
"[ 1/4 → 1/2 | note:g3 s:sawtooth pan:1 color:green cutoff:1275.581289814515 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||
"[ 1/2 → 3/4 | note:g3 s:sawtooth pan:0 cutoff:1444.415089128581 lpattack:0.2 lpenv:-2 decay:0.05 sustain:0 room:0.6 delay:0.5 delaytime:0.1 delayfeedback:0.4 ]",
|
||||
|
||||
Reference in New Issue
Block a user