Compare commits

...

18 Commits

Author SHA1 Message Date
alex 912f755d03 try using bind rather than applicative in register 2025-06-29 23:18:50 +01:00
Jade Rose db413881db Merge pull request 'feat: delaytime in cycles' (#1341) from daslyfe/jade/delaycycle into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1341
2025-06-28 20:30:48 +02:00
Jade (Rose) Rowland 77bab433e0 cf 2025-06-28 14:06:16 -04:00
daslyfe f8cc1c2022 Merge branch 'main' into daslyfe/jade/delaycycle 2025-06-28 20:01:57 +02:00
froos e0bf9ea4d6 Merge pull request 'Case insensitive search in the reference tab' (#1420) from kdiab/strudel:main into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1420
2025-06-28 13:44:47 +02:00
Alex McLean 374661eb23 Merge pull request 'add unjoin, into and chunkinto' (#1418) from add-unjoin into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1418
2025-06-28 00:04:12 +02:00
alex df06248c54 snapshot 2025-06-27 22:59:01 +01:00
alex 27073d6c17 format 2025-06-27 22:56:19 +01:00
alex f44caf9096 tests and tweaks, and add chunkBackInto 2025-06-27 22:51:47 +01:00
alex ff5b11f5ed test + bugfix for chunkinto 2025-06-27 22:44:15 +01:00
Alex McLean 719c70a598 Merge branch 'main' into add-unjoin 2025-06-27 22:40:40 +02:00
Jade (Rose) Rowland 7a53f6c021 add test 2025-06-27 15:56:37 -04:00
Jade (Rose) Rowland 09d05abd70 delaysync 2025-06-27 15:54:39 -04:00
Jade (Rose) Rowland 3acc364a03 Merge branch 'main' into daslyfe/jade/delaycycle 2025-06-27 15:53:48 -04:00
Khalid e19fd24447 Case insensitive search in the reference tab 2025-06-25 08:44:12 -04:00
alex fe9a91d1e4 add unjoin, into and chunkinto 2025-06-24 23:22:42 +01:00
Jade (Rose) Rowland 94257e81ee lint 2025-05-02 23:44:55 -04:00
Jade (Rose) Rowland 006ce9d1da delaytime 2025-05-02 23:33:58 -04:00
8 changed files with 275 additions and 14 deletions
+17 -1
View File
@@ -985,15 +985,31 @@ export const { delayfeedback, delayfb, dfb } = registerControl('delayfeedback',
*
*/
export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', 'dt');
/* // TODO: test
/**
* Sets the time of the delay effect in cycles.
*
* @name delaysync
* @param {number | Pattern} cycles delay length in cycles
* @synonyms delayt, dt
* @example
* s("bd bd").delay(.25).delaysync("<1 2 3 5>".div(8))
*
*/
export const { delaysync } = registerControl('delaysync');
/**
* Specifies whether delaytime is calculated relative to cps.
*
* @name lock
* @param {number | Pattern} enable When set to 1, delaytime is a direct multiple of a cycle.
* @superdirtOnly
* @example
* s("sd").delay().lock(1).osc()
*
*
*/
export const { lock } = registerControl('lock');
/**
* Set detune for stacked voices of supported oscillators
+70 -3
View File
@@ -46,10 +46,11 @@ export class Pattern {
* @param {function} query - The function that maps a `State` to an array of `Hap`.
* @noAutocomplete
*/
constructor(query, steps = undefined) {
constructor(query, steps = undefined, alignment = undefined) {
this.query = query;
this._Pattern = true; // this property is used to detectinstance of another Pattern
this._steps = steps; // in terms of number of steps per cycle
this._alignment = alignment;
}
get _steps() {
@@ -76,6 +77,11 @@ export class Pattern {
return this._steps !== undefined;
}
setAlignment(alignment) {
this._alignment = alignment;
return this;
}
//////////////////////////////////////////////////////////////////////
// Haskell-style functor, applicative and monadic operations
@@ -869,6 +875,31 @@ export class Pattern {
console.log(drawLine(this));
return this;
}
//////////////////////////////////////////////////////////////////////
// methods relating to breaking patterns into subcycles
// Breaks a pattern into a pattern of patterns, according to the structure of the given binary pattern.
unjoin(pieces, func = id) {
return pieces.withHap((hap) =>
hap.withValue((v) => (v ? func(this.ribbon(hap.whole.begin, hap.whole.duration)) : this)),
);
}
/**
* Breaks a pattern into pieces according to the structure of a given pattern.
* True values in the given pattern cause the corresponding subcycle of the
* source pattern to be looped, and for an (optional) given function to be
* applied. False values result in the corresponding part of the source pattern
* to be played unchanged.
* @name into
* @memberof Pattern
* @example
* sound("bd sd ht lt").into("1 0", hurry(2))
*/
into(pieces, func) {
return this.unjoin(pieces, func).innerJoin();
}
}
//////////////////////////////////////////////////////////////////////
@@ -1585,8 +1616,13 @@ export function register(name, func, patternify = true, preserveSteps = false, j
let mapFn = (...args) => {
return func(...args, pat);
};
mapFn = curry(mapFn, null, arity - 1);
result = join(right.reduce((acc, p) => acc.appLeft(p), left.fmap(mapFn)));
// mapFn = curry(mapFn, null, arity - 1);
// result = join(right.reduce((acc, p) => acc.appLeft(p), left.fmap(mapFn)));
// patternify2 f pata patb patc = pata >>= \a -> patb >>= \b -> f a b patc
function bindArgs(argv, pat, ...pats) {
return pat.innerBind((x) => (pats.length ? bindArgs([...argv, x], ...pats) : mapFn(...argv, x)));
}
result = bindArgs([], ...firstArgs);
}
}
if (preserveSteps) {
@@ -2494,6 +2530,37 @@ export const { fastchunk, fastChunk } = register(
true,
);
/**
* Like `chunk`, but the function is applied to a looped subcycle of the source pattern.
* @name chunkInto
* @synonym chunkinto
* @memberof Pattern
* @example
* sound("bd sd ht lt bd - cp lt").chunkInto(4, hurry(2))
* .bank("tr909")
*/
export const { chunkinto, chunkInto } = register(['chunkinto', 'chunkInto'], function (n, func, pat) {
return pat.into(fastcat(true, ...Array(n - 1).fill(false))._iterback(n), func);
});
/**
* Like `chunkInto`, but moves backwards through the chunks.
* @name chunkBackInto
* @synonym chunkbackinto
* @memberof Pattern
* @example
* sound("bd sd ht lt bd - cp lt").chunkInto(4, hurry(2))
* .bank("tr909")
*/
export const { chunkbackinto, chunkBackInto } = register(['chunkbackinto', 'chunkBackInto'], function (n, func, pat) {
return pat.into(
fastcat(true, ...Array(n - 1).fill(false))
._iter(n)
._early(1),
func,
);
});
// TODO - redefine elsewhere in terms of mask
export const bypass = register(
'bypass',
+35
View File
@@ -1271,4 +1271,39 @@ describe('Pattern', () => {
);
});
});
describe('unjoin', () => {
it('destructures a pattern into subcycles', () => {
sameFirst(
fastcat('a', 'b', 'c', 'd')
.unjoin(fastcat(true, fastcat(true, true)))
.fmap(fast(2))
.join(),
fastcat('a', 'b', 'a', 'b', 'c', 'c', 'd', 'd'),
);
});
});
describe('into', () => {
it('applies a function to subcycles of a pattern', () => {
sameFirst(
fastcat('a', 'b', 'c', 'd').into(fastcat(fastcat('true', 'true'), 'true'), fast(2)),
fastcat('a', 'a', 'b', 'b', 'c', 'd', 'c', 'd'),
);
});
});
describe('chunkinto', () => {
it('chunks into subcycles', () => {
sameFirst(
fastcat('a', 'b', 'c').chunkInto(3, fast(2)).fast(3),
fastcat(fastcat('a', 'a'), 'b', 'c', 'a', fastcat('b', 'b'), 'c', 'a', 'b', fastcat('c', 'c')),
);
});
});
describe('chunkbackinto', () => {
it('chunks into subcycles backwards', () => {
sameFirst(
fastcat('a', 'b', 'c').chunkBackInto(3, fast(2)).fast(3),
fastcat('a', 'b', fastcat('c', 'c'), 'a', fastcat('b', 'b'), 'c', fastcat('a', 'a'), 'b', 'c'),
);
});
});
});
+9 -6
View File
@@ -7,7 +7,7 @@ This program is free software: you can redistribute it and/or modify it under th
import './feedbackdelay.mjs';
import './reverb.mjs';
import './vowel.mjs';
import { clamp, nanFallback, _mod } from './util.mjs';
import { clamp, nanFallback, _mod, cycleToSeconds } from './util.mjs';
import workletsUrl from './worklets.mjs?audioworklet';
import { createFilter, gainNode, getCompressor, getWorklet } from './helpers.mjs';
import { map } from 'nanostores';
@@ -143,7 +143,7 @@ const defaultDefaultValues = {
delay: 0,
byteBeatExpression: '0',
delayfeedback: 0.5,
delaytime: 0.25,
delaysync: 3 / 16,
orbit: 1,
i: 1,
velocity: 1,
@@ -450,7 +450,7 @@ function mapChannelNumbers(channels) {
return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
}
export const superdough = async (value, t, hapDuration, cps) => {
export const superdough = async (value, t, hapDuration, cps = 0.5) => {
const ac = getAudioContext();
t = typeof t === 'string' && t.startsWith('=') ? Number(t.slice(1)) : ac.currentTime + t;
let { stretch } = value;
@@ -528,7 +528,8 @@ export const superdough = async (value, t, hapDuration, cps) => {
vowel,
delay = getDefaultValue('delay'),
delayfeedback = getDefaultValue('delayfeedback'),
delaytime = getDefaultValue('delaytime'),
delaysync = getDefaultValue('delaysync'),
delaytime,
orbit = getDefaultValue('orbit'),
room,
roomfade,
@@ -547,6 +548,8 @@ export const superdough = async (value, t, hapDuration, cps) => {
compressorRelease,
} = value;
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
const orbitChannels = mapChannelNumbers(
multiChannelOrbits && orbit > 0 ? [orbit * 2 - 1, orbit * 2] : getDefaultValue('channels'),
);
@@ -725,8 +728,8 @@ export const superdough = async (value, t, hapDuration, cps) => {
// delay
let delaySend;
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
const delyNode = getDelay(orbit, delaytime, delayfeedback, t, orbitChannels);
delaySend = effectSend(post, delyNode, delay);
const delayNode = getDelay(orbit, delaytime, delayfeedback, t, orbitChannels);
delaySend = effectSend(post, delayNode, delay);
audioNodes.push(delaySend);
}
// reverb
+4
View File
@@ -68,3 +68,7 @@ export const _mod = (n, m) => ((n % m) + m) % m;
export const getSoundIndex = (n, numSounds) => {
return _mod(Math.round(nanFallback(n, 0)), numSounds);
};
export function cycleToSeconds(cycle, cps) {
return cycle / cps;
}
+4 -3
View File
@@ -16,9 +16,10 @@ const hap2value = (hap) => {
};
export const webaudioOutputTrigger = (t, hap, ct, cps) => superdough(hap2value(hap), t - ct, hap.duration / cps, cps);
// uses more precise, absolute t if available, see https://codeberg.org/uzu/strudel/pulls/1004
export const webaudioOutput = (hap, deadline, hapDuration, cps, t) =>
superdough(hap2value(hap), t ? `=${t}` : deadline, hapDuration);
// uses more precise, absolute t if available, see https://github.com/tidalcycles/strudel/pull/1004
export const webaudioOutput = (hap, deadline, hapDuration, cps, t) => {
return superdough(hap2value(hap), t ? `=${t}` : deadline, hapDuration, cps);
};
Pattern.prototype.webaudio = function () {
return this.onTrigger(webaudioOutputTrigger);
+131
View File
@@ -1889,6 +1889,86 @@ exports[`runs examples > example "chunkBack" example index 0 1`] = `
]
`;
exports[`runs examples > example "chunkBackInto" example index 0 1`] = `
[
"[ 0/1 → 1/16 | s:bd speed:2 bank:tr909 ]",
"[ 1/16 → 1/8 | s:sd speed:2 bank:tr909 ]",
"[ 1/8 → 3/16 | s:bd speed:2 bank:tr909 ]",
"[ 3/16 → 1/4 | s:sd speed:2 bank:tr909 ]",
"[ 1/4 → 3/8 | s:ht bank:tr909 ]",
"[ 3/8 → 1/2 | s:lt bank:tr909 ]",
"[ 1/2 → 5/8 | s:bd bank:tr909 ]",
"[ 3/4 → 7/8 | s:cp bank:tr909 ]",
"[ 7/8 → 1/1 | s:lt bank:tr909 ]",
"[ 1/1 → 9/8 | s:bd bank:tr909 ]",
"[ 9/8 → 5/4 | s:sd bank:tr909 ]",
"[ 5/4 → 21/16 | s:ht speed:2 bank:tr909 ]",
"[ 21/16 → 11/8 | s:lt speed:2 bank:tr909 ]",
"[ 11/8 → 23/16 | s:ht speed:2 bank:tr909 ]",
"[ 23/16 → 3/2 | s:lt speed:2 bank:tr909 ]",
"[ 3/2 → 13/8 | s:bd bank:tr909 ]",
"[ 7/4 → 15/8 | s:cp bank:tr909 ]",
"[ 15/8 → 2/1 | s:lt bank:tr909 ]",
"[ 2/1 → 17/8 | s:bd bank:tr909 ]",
"[ 17/8 → 9/4 | s:sd bank:tr909 ]",
"[ 9/4 → 19/8 | s:ht bank:tr909 ]",
"[ 19/8 → 5/2 | s:lt bank:tr909 ]",
"[ 5/2 → 41/16 | s:bd speed:2 bank:tr909 ]",
"[ 21/8 → 43/16 | s:bd speed:2 bank:tr909 ]",
"[ 11/4 → 23/8 | s:cp bank:tr909 ]",
"[ 23/8 → 3/1 | s:lt bank:tr909 ]",
"[ 3/1 → 25/8 | s:bd bank:tr909 ]",
"[ 25/8 → 13/4 | s:sd bank:tr909 ]",
"[ 13/4 → 27/8 | s:ht bank:tr909 ]",
"[ 27/8 → 7/2 | s:lt bank:tr909 ]",
"[ 7/2 → 29/8 | s:bd bank:tr909 ]",
"[ 15/4 → 61/16 | s:cp speed:2 bank:tr909 ]",
"[ 61/16 → 31/8 | s:lt speed:2 bank:tr909 ]",
"[ 31/8 → 63/16 | s:cp speed:2 bank:tr909 ]",
"[ 63/16 → 4/1 | s:lt speed:2 bank:tr909 ]",
]
`;
exports[`runs examples > example "chunkInto" example index 0 1`] = `
[
"[ 0/1 → 1/16 | s:bd speed:2 bank:tr909 ]",
"[ 1/16 → 1/8 | s:sd speed:2 bank:tr909 ]",
"[ 1/8 → 3/16 | s:bd speed:2 bank:tr909 ]",
"[ 3/16 → 1/4 | s:sd speed:2 bank:tr909 ]",
"[ 1/4 → 3/8 | s:ht bank:tr909 ]",
"[ 3/8 → 1/2 | s:lt bank:tr909 ]",
"[ 1/2 → 5/8 | s:bd bank:tr909 ]",
"[ 3/4 → 7/8 | s:cp bank:tr909 ]",
"[ 7/8 → 1/1 | s:lt bank:tr909 ]",
"[ 1/1 → 9/8 | s:bd bank:tr909 ]",
"[ 9/8 → 5/4 | s:sd bank:tr909 ]",
"[ 5/4 → 21/16 | s:ht speed:2 bank:tr909 ]",
"[ 21/16 → 11/8 | s:lt speed:2 bank:tr909 ]",
"[ 11/8 → 23/16 | s:ht speed:2 bank:tr909 ]",
"[ 23/16 → 3/2 | s:lt speed:2 bank:tr909 ]",
"[ 3/2 → 13/8 | s:bd bank:tr909 ]",
"[ 7/4 → 15/8 | s:cp bank:tr909 ]",
"[ 15/8 → 2/1 | s:lt bank:tr909 ]",
"[ 2/1 → 17/8 | s:bd bank:tr909 ]",
"[ 17/8 → 9/4 | s:sd bank:tr909 ]",
"[ 9/4 → 19/8 | s:ht bank:tr909 ]",
"[ 19/8 → 5/2 | s:lt bank:tr909 ]",
"[ 5/2 → 41/16 | s:bd speed:2 bank:tr909 ]",
"[ 21/8 → 43/16 | s:bd speed:2 bank:tr909 ]",
"[ 11/4 → 23/8 | s:cp bank:tr909 ]",
"[ 23/8 → 3/1 | s:lt bank:tr909 ]",
"[ 3/1 → 25/8 | s:bd bank:tr909 ]",
"[ 25/8 → 13/4 | s:sd bank:tr909 ]",
"[ 13/4 → 27/8 | s:ht bank:tr909 ]",
"[ 27/8 → 7/2 | s:lt bank:tr909 ]",
"[ 7/2 → 29/8 | s:bd bank:tr909 ]",
"[ 15/4 → 61/16 | s:cp speed:2 bank:tr909 ]",
"[ 61/16 → 31/8 | s:lt speed:2 bank:tr909 ]",
"[ 31/8 → 63/16 | s:cp speed:2 bank:tr909 ]",
"[ 63/16 → 4/1 | s:lt speed:2 bank:tr909 ]",
]
`;
exports[`runs examples > example "clip" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c s:piano clip:0.5 ]",
@@ -2505,6 +2585,19 @@ exports[`runs examples > example "delayfeedback" example index 0 1`] = `
]
`;
exports[`runs examples > example "delaysync" example index 0 1`] = `
[
"[ 0/1 → 1/2 | s:bd delay:0.25 delaysync:0.125 ]",
"[ 1/2 → 1/1 | s:bd delay:0.25 delaysync:0.125 ]",
"[ 1/1 → 3/2 | s:bd delay:0.25 delaysync:0.25 ]",
"[ 3/2 → 2/1 | s:bd delay:0.25 delaysync:0.25 ]",
"[ 2/1 → 5/2 | s:bd delay:0.25 delaysync:0.375 ]",
"[ 5/2 → 3/1 | s:bd delay:0.25 delaysync:0.375 ]",
"[ 3/1 → 7/2 | s:bd delay:0.25 delaysync:0.625 ]",
"[ 7/2 → 4/1 | s:bd delay:0.25 delaysync:0.625 ]",
]
`;
exports[`runs examples > example "delaytime" example index 0 1`] = `
[
"[ 0/1 → 1/2 | s:bd delay:0.25 delaytime:0.125 ]",
@@ -4391,6 +4484,35 @@ exports[`runs examples > example "inside" example index 0 1`] = `
]
`;
exports[`runs examples > example "into" example index 0 1`] = `
[
"[ 0/1 → 1/8 | s:bd speed:2 ]",
"[ 1/8 → 1/4 | s:sd speed:2 ]",
"[ 1/4 → 3/8 | s:bd speed:2 ]",
"[ 3/8 → 1/2 | s:sd speed:2 ]",
"[ 1/2 → 3/4 | s:ht ]",
"[ 3/4 → 1/1 | s:lt ]",
"[ 1/1 → 9/8 | s:bd speed:2 ]",
"[ 9/8 → 5/4 | s:sd speed:2 ]",
"[ 5/4 → 11/8 | s:bd speed:2 ]",
"[ 11/8 → 3/2 | s:sd speed:2 ]",
"[ 3/2 → 7/4 | s:ht ]",
"[ 7/4 → 2/1 | s:lt ]",
"[ 2/1 → 17/8 | s:bd speed:2 ]",
"[ 17/8 → 9/4 | s:sd speed:2 ]",
"[ 9/4 → 19/8 | s:bd speed:2 ]",
"[ 19/8 → 5/2 | s:sd speed:2 ]",
"[ 5/2 → 11/4 | s:ht ]",
"[ 11/4 → 3/1 | s:lt ]",
"[ 3/1 → 25/8 | s:bd speed:2 ]",
"[ 25/8 → 13/4 | s:sd speed:2 ]",
"[ 13/4 → 27/8 | s:bd speed:2 ]",
"[ 27/8 → 7/2 | s:sd speed:2 ]",
"[ 7/2 → 15/4 | s:ht ]",
"[ 15/4 → 4/1 | s:lt ]",
]
`;
exports[`runs examples > example "invert" example index 0 1`] = `
[
"[ 0/1 → 1/8 | s:bd ]",
@@ -5016,6 +5138,15 @@ exports[`runs examples > example "linger" example index 0 1`] = `
]
`;
exports[`runs examples > example "lock" example index 0 1`] = `
[
"[ 0/1 → 1/1 | delay:{s:sd} lock:1 ]",
"[ 1/1 → 2/1 | delay:{s:sd} lock:1 ]",
"[ 2/1 → 3/1 | delay:{s:sd} lock:1 ]",
"[ 3/1 → 4/1 | delay:{s:sd} lock:1 ]",
]
`;
exports[`runs examples > example "loop" example index 0 1`] = `
[
"[ 0/1 → 1/1 | s:casio loop:1 ]",
@@ -21,7 +21,11 @@ export function Reference() {
return true;
}
return entry.name.includes(search) || (entry.synonyms?.some((s) => s.includes(search)) ?? false);
const lowCaseSearch = search.toLowerCase();
return (
entry.name.toLowerCase().includes(lowCaseSearch) ||
(entry.synonyms?.some((s) => s.includes(lowCaseSearch)) ?? false)
);
});
}, [search]);