Skip to content

Commit

Permalink
fix and expose the magical 'zip' function, more documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
yaxu committed Feb 2, 2025
1 parent adfd3b4 commit d5235ef
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 23 deletions.
38 changes: 30 additions & 8 deletions packages/core/pattern.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2970,8 +2970,22 @@ export const grow = register(
);

/**
* *EXPERIMENTAL*
* *Experimental*
*
* Inserts a pattern into a list of patterns. On the first repetition it will be inserted at the end of the list, then moved backwards through the list
* on successive repetitions. The patterns are added together stepwise, with all repetitions taking place over a single cycle. Using `pace` to set the
* number of steps per cycle is therefore usually recommended.
*
* @return {Pattern}
* @example
* "[c g]".tour("e f", "e f g", "g f e c").note()
.sound("folkharp")
.pace(8)
*/
export const tour = function (pat, ...many) {
return pat.tour(...many);
};

Pattern.prototype.tour = function (...many) {
return stepcat(
...[].concat(
Expand All @@ -2982,15 +2996,23 @@ Pattern.prototype.tour = function (...many) {
);
};

export const tour = function (pat, ...many) {
return pat.tour(...many);
};

const zip = function (...pats) {
/**
* *Experimental*
*
* 'zips' together the steps of the provided patterns. This can create a long repetition, taking place over a single, dense cycle.
* Using `pace` to set the number of steps per cycle is therefore usually recommended.
*
* @returns {Pattern}
* @example
* zip("e f", "e f g", "g [f e] a f4 c").note()
.sound("folkharp")
.pace(8)
*/
export const zip = function (...pats) {
pats = pats.filter((pat) => pat.hasTactus);
const zipped = slowcat(...pats.map((pat) => pat._slow(pat.tactus)));
// Should maybe use lcm or gcd for tactus?
return zipped._fast(pats[0].tactus).setTactus(pats[0].tactus);
const tactus = lcm(...pats.map((x) => x.tactus));
return zipped._fast(tactus).setTactus(tactus);
};

/** Aliases for `stepcat` */
Expand Down
80 changes: 80 additions & 0 deletions test/__snapshots__/examples.test.mjs.snap
Original file line number Diff line number Diff line change
Expand Up @@ -8746,6 +8746,47 @@ exports[`runs examples > example "take" example index 2 1`] = `
]
`;

exports[`runs examples > example "tour" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:e s:folkharp ]",
"[ 1/8 → 1/4 | note:f s:folkharp ]",
"[ 1/4 → 3/8 | note:e s:folkharp ]",
"[ 3/8 → 1/2 | note:f s:folkharp ]",
"[ 1/2 → 5/8 | note:g s:folkharp ]",
"[ 5/8 → 3/4 | note:g s:folkharp ]",
"[ 3/4 → 7/8 | note:f s:folkharp ]",
"[ 7/8 → 1/1 | note:e s:folkharp ]",
"[ 1/1 → 9/8 | note:c s:folkharp ]",
"[ 9/8 → 19/16 | note:c s:folkharp ]",
"[ 19/16 → 5/4 | note:g s:folkharp ]",
"[ 5/4 → 11/8 | note:e s:folkharp ]",
"[ 11/8 → 3/2 | note:f s:folkharp ]",
"[ 3/2 → 13/8 | note:e s:folkharp ]",
"[ 13/8 → 7/4 | note:f s:folkharp ]",
"[ 7/4 → 15/8 | note:g s:folkharp ]",
"[ 15/8 → 31/16 | note:c s:folkharp ]",
"[ 31/16 → 2/1 | note:g s:folkharp ]",
"[ 2/1 → 17/8 | note:g s:folkharp ]",
"[ 17/8 → 9/4 | note:f s:folkharp ]",
"[ 9/4 → 19/8 | note:e s:folkharp ]",
"[ 19/8 → 5/2 | note:c s:folkharp ]",
"[ 5/2 → 21/8 | note:e s:folkharp ]",
"[ 21/8 → 11/4 | note:f s:folkharp ]",
"[ 11/4 → 45/16 | note:c s:folkharp ]",
"[ 45/16 → 23/8 | note:g s:folkharp ]",
"[ 23/8 → 3/1 | note:e s:folkharp ]",
"[ 3/1 → 25/8 | note:f s:folkharp ]",
"[ 25/8 → 13/4 | note:g s:folkharp ]",
"[ 13/4 → 27/8 | note:g s:folkharp ]",
"[ 27/8 → 7/2 | note:f s:folkharp ]",
"[ 7/2 → 29/8 | note:e s:folkharp ]",
"[ 29/8 → 15/4 | note:c s:folkharp ]",
"[ 15/4 → 61/16 | note:c s:folkharp ]",
"[ 61/16 → 31/8 | note:g s:folkharp ]",
"[ 31/8 → 4/1 | note:e s:folkharp ]",
]
`;

exports[`runs examples > example "transpose" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:C2 ]",
Expand Down Expand Up @@ -9431,6 +9472,45 @@ exports[`runs examples > example "xfade" example index 0 1`] = `
]
`;

exports[`runs examples > example "zip" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:e s:folkharp ]",
"[ 1/8 → 1/4 | note:e s:folkharp ]",
"[ 1/4 → 3/8 | note:g s:folkharp ]",
"[ 3/8 → 1/2 | note:f s:folkharp ]",
"[ 1/2 → 5/8 | note:f s:folkharp ]",
"[ 5/8 → 11/16 | note:f s:folkharp ]",
"[ 11/16 → 3/4 | note:e s:folkharp ]",
"[ 3/4 → 7/8 | note:e s:folkharp ]",
"[ 7/8 → 1/1 | note:g s:folkharp ]",
"[ 1/1 → 9/8 | note:a s:folkharp ]",
"[ 9/8 → 5/4 | note:f s:folkharp ]",
"[ 5/4 → 11/8 | note:e s:folkharp ]",
"[ 11/8 → 3/2 | note:f4 s:folkharp ]",
"[ 3/2 → 13/8 | note:e s:folkharp ]",
"[ 13/8 → 7/4 | note:f s:folkharp ]",
"[ 7/4 → 15/8 | note:c s:folkharp ]",
"[ 15/8 → 2/1 | note:f s:folkharp ]",
"[ 2/1 → 17/8 | note:g s:folkharp ]",
"[ 17/8 → 9/4 | note:g s:folkharp ]",
"[ 9/4 → 19/8 | note:e s:folkharp ]",
"[ 19/8 → 5/2 | note:e s:folkharp ]",
"[ 5/2 → 41/16 | note:f s:folkharp ]",
"[ 41/16 → 21/8 | note:e s:folkharp ]",
"[ 21/8 → 11/4 | note:f s:folkharp ]",
"[ 11/4 → 23/8 | note:f s:folkharp ]",
"[ 23/8 → 3/1 | note:a s:folkharp ]",
"[ 3/1 → 25/8 | note:e s:folkharp ]",
"[ 25/8 → 13/4 | note:g s:folkharp ]",
"[ 13/4 → 27/8 | note:f4 s:folkharp ]",
"[ 27/8 → 7/2 | note:f s:folkharp ]",
"[ 7/2 → 29/8 | note:e s:folkharp ]",
"[ 29/8 → 15/4 | note:c s:folkharp ]",
"[ 15/4 → 31/8 | note:e s:folkharp ]",
"[ 31/8 → 4/1 | note:f s:folkharp ]",
]
`;

exports[`runs examples > example "zoom" example index 0 1`] = `
[
"[ 0/1 → 1/6 | s:hh ]",
Expand Down
56 changes: 41 additions & 15 deletions website/src/pages/learn/stepwise.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import { JsDoc } from '../../docs/JsDoc';

# Stepwise patterning (experimental)

This is a developing area of strudel, and behaviour might change or be renamed in future versions.
This is a developing area of strudel, and behaviour might change or be renamed in future versions. Feedback and ideas are welcome!

## Introduction

Usually in strudel, the only reference point for most pattern transformations is the _cycle_. Now it is possible to also work with _steps_, via a growing range of functions.

Expand All @@ -20,35 +22,59 @@ With the new stepwise `stepcat` function, the steps of the two patterns will be

<MiniRepl client:idle tune={`stepcat("bd hh hh", "bd hh hh cp hh").sound()`} />

Some stepwise functions don't appear to do very much on their own, for example these two examples of the `expand` function sound the same despite being expanded by different amounts:
By default, steps are counted according to the 'top level' in mini-notation. For example `"a [b c] d e"` has five events in it per cycle, but is counted as four steps, where `[b c]` is counted as a single step.

However, you can mark a different metrical level to count steps relative to, using a `^` at the start of a sub-pattern. If we do this to the subpattern in our example: `"a [^b c] d e"`, then the pattern is now counted as having _eight_ steps. This is because 'b' and 'c' are each counted as single steps, and the events in the pattenr are twice as long, and so counted as two steps each.

## Pacing the steps

Some stepwise functions don't appear to do very much on their own, for example these two examples of the `expand` function sound exactly the same despite being expanded by different amounts:

<MiniRepl client:idle tune={`sound("bd hh mt lt").expand(2)`} />
<MiniRepl client:idle tune={`"c a f e".expand(2).note().sound("folkharp")`} />

<MiniRepl client:idle tune={`sound("bd hh mt lt").expand(4)`} />
<MiniRepl client:idle tune={`"c a f e".expand(4).note().sound("folkharp")`} />

The number of steps per cycle is changing, but on its own, that doesn't do anything. You will hear a difference once you use another stepwise function with it though, for example `stepcat`:
The number of steps per cycle is being changed behind the scenes, but on its own, that doesn't do anything. You will hear a difference however, once you use another stepwise function with it, for example `stepcat`:

<MiniRepl client:idle tune={`stepcat(sound("bd hh mt lt").expand(2), sound("cp cp"))`} />
<MiniRepl
client:idle
tune={`stepcat("c a f e".expand(2), "g d").note()
.sound("folkharp")`}
/>

<MiniRepl client:idle tune={`stepcat(sound("bd hh mt lt").expand(4), sound("cp cp"))`} />
<MiniRepl
client:idle
tune={`stepcat("c a f e".expand(4), "g d").note()
.sound("folkharp")`}
/>

You should be able to hear that `expand` increases the duration of the steps of the first subpattern, proportionally.
You should be able to hear that `expand` increases the duration of the steps of the first subpattern, proportionally to the second one.

You can also change the speed of a pattern to match a given number of steps per cycle, with the `pace` function:

<MiniRepl client:idle tune={`stepcat(sound("bd hh mt lt").expand(2), sound("cp cp")).pace(8)`} />
<MiniRepl
client:idle
tune={`stepcat("c a f e".expand(2), "g d").note()
.sound("folkharp")
.pace(8)`}
/>

<MiniRepl client:idle tune={`stepcat(sound("bd hh mt lt").expand(4), sound("cp cp")).pace(8)`} />
<MiniRepl
client:idle
tune={`stepcat("c a f e".expand(4), "g d").note()
.sound("folkharp")
.pace(8)`}
/>

The first example has ten steps, and the second example has 16 steps, with both played at a rate of 8 steps per cycle.
The first example has ten steps, and the second example has 18 steps, but are then both played a rate of 8 steps per cycle.

The argument to `expand` can also be patterned, and will be treated in a stepwise fashion where the resulting patterns from the value in the argument will be `stepcat`ted together:
The argument to `expand` can also be patterned, and will be treated in a stepwise fashion. This means that the patterns from the changing values in the argument will be `stepcat`ted together:

<MiniRepl client:idle tune={`sound("bd hh mt lt").expand("3 2 1 1 2 3")`} />
<MiniRepl client:idle tune={`note("c a f e").sound("folkharp").expand("3 2 1 1 2 3")`} />

This results in a pretty dense pattern, because the different expanded versions are squashed (stepwise) into a single cycle. `pace` is again handy here for slowing down the pattern to a particular number of steps per cycle:
This results in a dense pattern, because the different expanded versions are squashed into a single cycle. `pace` is again handy here for slowing down the pattern to a particular number of steps per cycle:

<MiniRepl client:idle tune={`sound("bd hh mt lt").expand("3 2 1 1 2 3").pace(8)`} />
<MiniRepl client:idle tune={`note("c a f e").sound("folkharp").expand("3 2 1 1 2 3").pace(8)`} />

## Stepwise functions

Expand Down

0 comments on commit d5235ef

Please sign in to comment.