Compare commits

...

22 Commits

Author SHA1 Message Date
Felix Roos b312ff63a9 Publish
- @strudel/codemirror@1.2.2
 - @strudel/core@1.2.2
 - @strudel/csound@1.2.3
 - @strudel/draw@1.2.2
 - @strudel/gamepad@1.2.2
 - @strudel/hydra@1.2.2
 - @strudel/midi@1.2.3
 - @strudel/mini@1.2.2
 - @strudel/motion@1.2.2
 - @strudel/mqtt@1.2.2
 - @strudel/osc@1.2.2
 - @strudel/repl@1.2.3
 - @strudel/serial@1.2.2
 - @strudel/soundfonts@1.2.3
 - superdough@1.2.3
 - @strudel/tonal@1.2.2
 - @strudel/transpiler@1.2.2
 - @strudel/web@1.2.3
 - @strudel/webaudio@1.2.3
 - @strudel/xen@1.2.2
2025-05-29 23:58:35 +02:00
Jade (Rose) Rowland 93bc353b6b Merge pull request #1359 from tidalcycles/bytebeat_fixes_3
Fix bytebeat sample offset
2025-05-29 14:36:43 +02:00
Jade (Rose) Rowland 14a0dea826 fixtest 2025-05-29 14:35:37 +02:00
Jade (Rose) Rowland 009f5445be codeformat 2025-05-29 14:33:25 +02:00
Jade (Rose) Rowland 3d37c73103 fixed 2025-05-29 14:32:52 +02:00
Alex McLean 0b0f9fe13b preserve stepcount in chunks (#1358)
* preserve stepcount in chunks
* format
2025-05-29 12:14:18 +02:00
Jade (Rose) Rowland 1d189763b2 Merge pull request #1357 from tidalcycles/bbfixes_2
Byte Beat improvements -> 2
2025-05-29 02:34:30 +02:00
Jade (Rose) Rowland a4b55776d2 test 2025-05-29 02:33:06 +02:00
Jade (Rose) Rowland 0287be547a bbstart 2025-05-29 02:27:55 +02:00
Jade (Rose) Rowland 6a83ede33d Merge pull request #1356 from tidalcycles/bb_fixes
Bytebeat improvements
2025-05-28 16:40:32 +02:00
Jade (Rose) Rowland 1e3f546918 bb waveforms 2025-05-28 16:36:53 +02:00
Jade (Rose) Rowland faa60be211 Merge pull request #1354 from tidalcycles/jade/bytebeats
feat: Byte Beats!
2025-05-27 17:42:38 +02:00
Jade (Rose) Rowland dde2b9c6f8 working 2025-05-27 17:41:32 +02:00
Jade (Rose) Rowland 382dce65df working 2025-05-27 17:39:55 +02:00
Jade (Rose) Rowland 63f4f202f1 working 2025-05-27 17:35:35 +02:00
Jade (Rose) Rowland 366637026b bbs 2025-05-27 17:34:53 +02:00
Jade (Rose) Rowland 608dfd515e WIP oscillations 2025-05-27 14:37:55 +02:00
Jade (Rose) Rowland 5e4eb7fc53 gain 2025-05-20 21:53:10 +01:00
Jade (Rose) Rowland 45bccf13ff add test 2025-05-20 21:47:43 +01:00
Jade (Rose) Rowland 65f1760fad format 2025-05-20 21:45:14 +01:00
Jade (Rose) Rowland b8d2d02466 bb 2025-05-20 21:41:40 +01:00
Jade (Rose) Rowland 058cb36640 structure 2025-05-20 20:28:52 +01:00
26 changed files with 330 additions and 82 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/codemirror",
"version": "1.2.1",
"version": "1.2.2",
"description": "Codemirror Extensions for Strudel",
"main": "index.mjs",
"publishConfig": {
+26
View File
@@ -445,6 +445,32 @@ export const { coarse } = registerControl('coarse');
*/
export const { drive } = registerControl('drive');
/**
* Create byte beats with custom expressions
*
* @name byteBeatExpression
* @synonyms bbexpr
*
* @param {number | Pattern} byteBeatExpression bitwise expression for creating bytebeat
* @example
* s("bytebeat").bbexpr('t*(t>>15^t>>66)')
*
*/
export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr');
/**
* Create byte beats with custom expressions
*
* @name byteBeatStartTime
* @synonyms bbst
*
* @param {number | Pattern} byteBeatStartTime in samples (t)
* @example
* note("c3!8".add("{0 0 12 0 7 5 3}%8")).s("bytebeat:5").bbst("<3 1>".mul(10000))._scope()
*
*/
export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime', 'bbst');
/**
* Allows you to set the output channels on the interface
*
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/core",
"version": "1.2.1",
"version": "1.2.2",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"type": "module",
+24 -9
View File
@@ -2441,9 +2441,14 @@ const _chunk = function (n, func, pat, back = false, fast = false) {
return pat.when(binary_pat, func);
};
export const { chunk, slowchunk, slowChunk } = register(['chunk', 'slowchunk', 'slowChunk'], function (n, func, pat) {
return _chunk(n, func, pat, false, false);
});
export const { chunk, slowchunk, slowChunk } = register(
['chunk', 'slowchunk', 'slowChunk'],
function (n, func, pat) {
return _chunk(n, func, pat, false, false);
},
true,
true,
);
/**
* Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles
@@ -2455,9 +2460,14 @@ export const { chunk, slowchunk, slowChunk } = register(['chunk', 'slowchunk', '
* "0 1 2 3".chunkBack(4, x=>x.add(7))
* .scale("A:minor").note()
*/
export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], function (n, func, pat) {
return _chunk(n, func, pat, true);
});
export const { chunkBack, chunkback } = register(
['chunkBack', 'chunkback'],
function (n, func, pat) {
return _chunk(n, func, pat, true);
},
true,
true,
);
/**
* Like `chunk`, but the cycles of the source pattern aren't repeated
@@ -2471,9 +2481,14 @@ export const { chunkBack, chunkback } = register(['chunkBack', 'chunkback'], fun
* .fastChunk(4, x => x.color('red')).slow(2)
* .scale("C2:major").note()
*/
export const { fastchunk, fastChunk } = register(['fastchunk', 'fastChunk'], function (n, func, pat) {
return _chunk(n, func, pat, false, true);
});
export const { fastchunk, fastChunk } = register(
['fastchunk', 'fastChunk'],
function (n, func, pat) {
return _chunk(n, func, pat, false, true);
},
true,
true,
);
// TODO - redefine elsewhere in terms of mask
export const bypass = register(
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/csound",
"version": "1.2.2",
"version": "1.2.3",
"description": "csound bindings for strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/draw",
"version": "1.2.1",
"version": "1.2.2",
"description": "Helpers for drawing with Strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/gamepad",
"version": "1.2.1",
"version": "1.2.2",
"description": "Gamepad Inputs for strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/hydra",
"version": "1.2.1",
"version": "1.2.2",
"description": "Hydra integration for strudel",
"main": "hydra.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/midi",
"version": "1.2.2",
"version": "1.2.3",
"description": "Midi API for strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/mini",
"version": "1.2.1",
"version": "1.2.2",
"description": "Mini notation for strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/motion",
"version": "1.2.1",
"version": "1.2.2",
"description": "DeviceMotion API for strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/mqtt",
"version": "1.2.1",
"version": "1.2.2",
"description": "MQTT API for strudel",
"main": "mqtt.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/osc",
"version": "1.2.1",
"version": "1.2.2",
"description": "OSC messaging for strudel",
"main": "osc.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/repl",
"version": "1.2.2",
"version": "1.2.3",
"description": "Strudel REPL as a Web Component",
"module": "index.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/serial",
"version": "1.2.1",
"version": "1.2.2",
"description": "Webserial API for strudel",
"main": "serial.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/soundfonts",
"version": "1.2.2",
"version": "1.2.3",
"description": "Soundsfont support for strudel",
"main": "index.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "superdough",
"version": "1.2.2",
"version": "1.2.3",
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
"main": "index.mjs",
"type": "module",
+3 -2
View File
@@ -137,6 +137,7 @@ const defaultDefaultValues = {
shapevol: 1,
distortvol: 1,
delay: 0,
byteBeatExpression: '0',
delayfeedback: 0.5,
delaytime: 0.25,
orbit: 1,
@@ -445,7 +446,7 @@ function mapChannelNumbers(channels) {
return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
}
export const superdough = async (value, t, hapDuration) => {
export const superdough = async (value, t, hapDuration, cps) => {
const ac = getAudioContext();
t = typeof t === 'string' && t.startsWith('=') ? Number(t.slice(1)) : ac.currentTime + t;
let { stretch } = value;
@@ -578,7 +579,7 @@ export const superdough = async (value, t, hapDuration) => {
// get source AudioNode
let sourceNode;
if (source) {
sourceNode = source(t, value, hapDuration);
sourceNode = source(t, value, hapDuration, cps);
} else if (getSound(s)) {
const { onTrigger } = getSound(s);
const onEnded = () => {
+76
View File
@@ -144,6 +144,82 @@ export function registerSynthSounds() {
{ prebake: true, type: 'synth' },
);
registerSound(
'bytebeat',
(begin, value, onended) => {
const defaultBeats = [
'(t%255 >= t/255%255)*255',
'(t*(t*8%60 <= 300)|(-t)*(t*4%512 < 256))+t/400',
't',
't*(t >> 10^t)',
't&128',
't&t>>8',
'((t%255+t%128+t%64+t%32+t%16+t%127.8+t%64.8+t%32.8+t%16.8)/3)',
'((t%64+t%63.8+t%64.15+t%64.35+t%63.5)/1.25)',
'(t&(t>>7)-t)',
'(sin(t*PI/128)*127+127)',
'((t^t/2+t+64*(sin((t*PI/64)+(t*PI/32768))+64))%128*2)',
'((t^t/2+t+64*(cos >> 0))%127.85*2)',
'((t^t/2+t+64)%128*2)',
'(((t * .25)^(t * .25)/100+(t * .25))%128)*2',
'((t^t/2+t+64)%7 * 24)',
];
const { n = 0 } = value;
const frequency = getFrequencyFromValue(value);
const { byteBeatExpression = defaultBeats[n % defaultBeats.length], byteBeatStartTime } = value;
const ac = getAudioContext();
let { duration } = value;
const [attack, decay, sustain, release] = getADSRValues(
[value.attack, value.decay, value.sustain, value.release],
'linear',
[0.001, 0.05, 0.6, 0.01],
);
const holdend = begin + duration;
const end = holdend + release + 0.01;
let o = getWorklet(
ac,
'byte-beat-processor',
{
frequency,
begin,
end,
},
{
outputChannelCount: [2],
},
);
o.port.postMessage({ codeText: byteBeatExpression, byteBeatStartTime, frequency });
let envGain = gainNode(1);
envGain = o.connect(envGain);
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear');
let timeoutNode = webAudioTimeout(
ac,
() => {
destroyAudioWorkletNode(o);
envGain.disconnect();
onended();
},
begin,
end,
);
return {
node: envGain,
stop: (time) => {
timeoutNode.stop(time);
},
};
},
{ prebake: true, type: 'synth' },
);
registerSound(
'pulse',
(begin, value, onended) => {
+135 -1
View File
@@ -295,7 +295,7 @@ class LadderProcessor extends AudioWorkletProcessor {
cutoff = (cutoff * 2 * _PI) / sampleRate;
cutoff = cutoff > 1 ? 1 : cutoff;
const k = Math.min(8, resonance * 0.4);
const k = Math.min(8, resonance * 0.13);
// drive makeup * resonance volume loss makeup
let makeupgain = (1 / drive) * Math.min(1.75, 1 + k);
@@ -761,3 +761,137 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor {
}
registerProcessor('pulse-oscillator', PulseOscillatorProcessor);
/** BYTE BEATS */
const chyx = {
/*bit*/ bitC: function (x, y, z) {
return x & y ? z : 0;
},
/*bit reverse*/ br: function (x, size = 8) {
if (size > 32) {
throw new Error('br() Size cannot be greater than 32');
} else {
let result = 0;
for (let idx = 0; idx < size - 0; idx++) {
result += chyx.bitC(x, 2 ** idx, 2 ** (size - (idx + 1)));
}
return result;
}
},
/*sin that loops every 128 "steps", instead of every pi steps*/ sinf: function (x) {
return Math.sin(x / (128 / Math.PI));
},
/*cos that loops every 128 "steps", instead of every pi steps*/ cosf: function (x) {
return Math.cos(x / (128 / Math.PI));
},
/*tan that loops every 128 "steps", instead of every pi steps*/ tanf: function (x) {
return Math.tan(x / (128 / Math.PI));
},
/*converts t into a string composed of it's bits, regex's that*/ regG: function (t, X) {
return X.test(t.toString(2));
},
};
// Create shortened Math functions
let mathParams, byteBeatHelperFuncs;
function getByteBeatFunc(codetext) {
if ((mathParams || byteBeatHelperFuncs) == null) {
mathParams = Object.getOwnPropertyNames(Math);
byteBeatHelperFuncs = mathParams.map((k) => Math[k]);
const chyxNames = Object.getOwnPropertyNames(chyx);
const chyxFuncs = chyxNames.map((k) => chyx[k]);
mathParams.push('int', 'window', ...chyxNames);
byteBeatHelperFuncs.push(Math.floor, globalThis, ...chyxFuncs);
}
return new Function(...mathParams, 't', `return 0,\n${codetext || 0};`).bind(globalThis, ...byteBeatHelperFuncs);
}
class ByteBeatProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.port.onmessage = (event) => {
let { codeText } = event.data;
const { byteBeatStartTime } = event.data;
if (byteBeatStartTime != null) {
this.t = 0;
this.initialOffset = Math.floor(byteBeatStartTime);
}
//Optimization pulled from dollchan.net: https://github.com/Chasyxx/EnBeat_NEW, it seemed important
//Optimize code like eval(unescape(escape`XXXX`.replace(/u(..)/g,"$1%")))
codeText = codeText
.trim()
.replace(
/^eval\(unescape\(escape(?:`|\('|\("|\(`)(.*?)(?:`|'\)|"\)|`\)).replace\(\/u\(\.\.\)\/g,["'`]\$1%["'`]\)\)\)$/,
(match, m1) => unescape(escape(m1).replace(/u(..)/g, '$1%')),
);
this.func = getByteBeatFunc(codeText);
};
this.initialOffset = null;
this.t = null;
this.func = null;
}
static get parameterDescriptors() {
return [
{
name: 'begin',
defaultValue: 0,
max: Number.POSITIVE_INFINITY,
min: 0,
},
{
name: 'frequency',
defaultValue: 440,
min: Number.EPSILON,
},
{
name: 'detune',
defaultValue: 0,
min: Number.NEGATIVE_INFINITY,
max: Number.POSITIVE_INFINITY,
},
{
name: 'end',
defaultValue: 0,
max: Number.POSITIVE_INFINITY,
min: 0,
},
];
}
process(inputs, outputs, params) {
if (this.disconnected) {
return false;
}
if (currentTime <= params.begin[0]) {
return true;
}
if (currentTime >= params.end[0]) {
return false;
}
if (this.t == null) {
this.t = params.begin[0] * sampleRate;
}
const output = outputs[0];
for (let i = 0; i < output[0].length; i++) {
const detune = getParamValue(i, params.detune);
const freq = applySemitoneDetuneToFrequency(getParamValue(i, params.frequency), detune / 100);
let local_t = (this.t / (sampleRate / 256)) * freq + this.initialOffset;
const funcValue = this.func(local_t);
let signal = (funcValue & 255) / 127.5 - 1;
const out = signal * 0.2;
for (let c = 0; c < output.length; c++) {
//prevent speaker blowout via clipping if threshold exceeds
output[c][i] = clamp(out, -0.4, 0.4);
}
this.t = this.t + 1;
}
return true; // keep the audio processing going
}
}
registerProcessor('byte-beat-processor', ByteBeatProcessor);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/tonal",
"version": "1.2.1",
"version": "1.2.2",
"description": "Tonal functions for strudel",
"main": "index.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/transpiler",
"version": "1.2.1",
"version": "1.2.2",
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/web",
"version": "1.2.2",
"version": "1.2.3",
"description": "Easy to setup, opiniated bundle of Strudel for the browser.",
"module": "web.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/webaudio",
"version": "1.2.2",
"version": "1.2.3",
"description": "Web Audio helpers for Strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/xen",
"version": "1.2.1",
"version": "1.2.2",
"description": "Xenharmonic API for strudel",
"main": "index.mjs",
"type": "module",
+46 -50
View File
@@ -1516,6 +1516,52 @@ exports[`runs examples > example "brandBy" example index 0 1`] = `
]
`;
exports[`runs examples > example "byteBeatExpression" example index 0 1`] = `
[
"[ 0/1 → 1/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]",
"[ 1/1 → 2/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]",
"[ 2/1 → 3/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]",
"[ 3/1 → 4/1 | s:bytebeat byteBeatExpression:t*(t>>15^t>>66) ]",
]
`;
exports[`runs examples > example "byteBeatStartTime" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 1/8 → 1/4 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 1/4 → 3/8 | note:60 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 3/8 → 1/2 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 1/2 → 5/8 | note:55 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 5/8 → 3/4 | note:53 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 3/4 → 7/8 | note:51 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 7/8 → 1/1 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 1/1 → 9/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
"[ 9/8 → 5/4 | note:60 s:bytebeat n:5 byteBeatStartTime:10000 ]",
"[ 5/4 → 11/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
"[ 11/8 → 3/2 | note:55 s:bytebeat n:5 byteBeatStartTime:10000 ]",
"[ 3/2 → 13/8 | note:53 s:bytebeat n:5 byteBeatStartTime:10000 ]",
"[ 13/8 → 7/4 | note:51 s:bytebeat n:5 byteBeatStartTime:10000 ]",
"[ 7/4 → 15/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
"[ 15/8 → 2/1 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
"[ 2/1 → 17/8 | note:60 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 17/8 → 9/4 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 9/4 → 19/8 | note:55 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 19/8 → 5/2 | note:53 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 5/2 → 21/8 | note:51 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 21/8 → 11/4 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 11/4 → 23/8 | note:48 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 23/8 → 3/1 | note:60 s:bytebeat n:5 byteBeatStartTime:30000 ]",
"[ 3/1 → 25/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
"[ 25/8 → 13/4 | note:55 s:bytebeat n:5 byteBeatStartTime:10000 ]",
"[ 13/4 → 27/8 | note:53 s:bytebeat n:5 byteBeatStartTime:10000 ]",
"[ 27/8 → 7/2 | note:51 s:bytebeat n:5 byteBeatStartTime:10000 ]",
"[ 7/2 → 29/8 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
"[ 29/8 → 15/4 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
"[ 15/4 → 31/8 | note:60 s:bytebeat n:5 byteBeatStartTime:10000 ]",
"[ 31/8 → 4/1 | note:48 s:bytebeat n:5 byteBeatStartTime:10000 ]",
]
`;
exports[`runs examples > example "cat" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:e5 ]",
@@ -8022,56 +8068,6 @@ exports[`runs examples > example "scope" example index 0 1`] = `
]
`;
exports[`runs examples > example "scramble
Slices a pattern into the given number of parts, then plays those parts at random. Similar to \`shuffle\`,
but parts might be played more than once, or not at all, per cycle." example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c s:piano ]",
"[ 1/4 → 1/2 | note:d s:piano ]",
"[ 1/2 → 3/4 | note:d s:piano ]",
"[ 3/4 → 1/1 | note:c s:piano ]",
"[ 1/1 → 5/4 | note:e s:piano ]",
"[ 5/4 → 3/2 | note:e s:piano ]",
"[ 3/2 → 7/4 | note:e s:piano ]",
"[ 7/4 → 2/1 | note:c s:piano ]",
"[ 2/1 → 9/4 | note:f s:piano ]",
"[ 9/4 → 5/2 | note:d s:piano ]",
"[ 5/2 → 11/4 | note:d s:piano ]",
"[ 11/4 → 3/1 | note:e s:piano ]",
"[ 3/1 → 13/4 | note:c s:piano ]",
"[ 13/4 → 7/2 | note:f s:piano ]",
"[ 7/2 → 15/4 | note:d s:piano ]",
"[ 15/4 → 4/1 | note:f s:piano ]",
]
`;
exports[`runs examples > example "scramble
Slices a pattern into the given number of parts, then plays those parts at random. Similar to \`shuffle\`,
but parts might be played more than once, or not at all, per cycle." example index 1 1`] = `
[
"[ 0/1 → 1/8 | note:c s:piano ]",
"[ 1/8 → 1/4 | note:d s:piano ]",
"[ 1/4 → 3/8 | note:d s:piano ]",
"[ 3/8 → 1/2 | note:c s:piano ]",
"[ 1/2 → 1/1 | note:g s:piano ]",
"[ 1/1 → 9/8 | note:e s:piano ]",
"[ 9/8 → 5/4 | note:e s:piano ]",
"[ 5/4 → 11/8 | note:e s:piano ]",
"[ 11/8 → 3/2 | note:c s:piano ]",
"[ 3/2 → 2/1 | note:g s:piano ]",
"[ 2/1 → 17/8 | note:f s:piano ]",
"[ 17/8 → 9/4 | note:d s:piano ]",
"[ 9/4 → 19/8 | note:d s:piano ]",
"[ 19/8 → 5/2 | note:e s:piano ]",
"[ 5/2 → 3/1 | note:g s:piano ]",
"[ 3/1 → 25/8 | note:c s:piano ]",
"[ 25/8 → 13/4 | note:f s:piano ]",
"[ 13/4 → 27/8 | note:d s:piano ]",
"[ 27/8 → 7/2 | note:f s:piano ]",
"[ 7/2 → 4/1 | note:g s:piano ]",
]
`;
exports[`runs examples > example "scramble" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c s:piano ]",