← JamDojo Combining Patterns

Combining Patterns

Two Patterns, One Question

Here’s a melody with 4 notes:

n("0 2 4 6").scale("C4:major").sound("piano")

And here’s a pattern of intervals with 2 values—one for each half of the cycle:

// "<0 7>" means: 0 for first half, 7 for second half
n("0 2 4 6".add("<0 7>")).scale("C4:major").sound("piano")

Look at the punchcard. We still have 4 notes. The first two stay the same (added 0), the last two go up a fifth (added 7).

But why 4 notes? The interval pattern only had 2 values. What decided to keep the melody’s rhythm?


Who Leads? (The Applicative Question)

When two patterns combine, we must decide whose structure survives. This is the Applicative question—and Strudel gives you three answers:

.add() — The melody leads (4 notes):

n("0 2 4 6".add("<0 7>")).scale("C4:major").sound("piano")

Each melody note gets the interval that’s “active” at that moment.

.add.out() — The interval leads (2 notes):

n("0 2 4 6".add.out("<0 7>")).scale("C4:major").sound("piano")

Now we only have 2 notes! Each interval picks up the melody note that’s active at that moment.

.add.mix() — Both must agree (intersection):

n("0 2 4 6".add.mix("<0 7 12>")).scale("C4:major").sound("piano")

Events only where both patterns have something happening at the same time.

ModeSuffixStructure From
Left(none)Left pattern
Right.outRight pattern
Both.mixIntersection

Hearing the Difference

Let’s make this audible with longer patterns:

// Left leads: fast melody, slow harmony
n("0 1 2 3 4 3 2 1".add("0 3 5 3"))
.scale("C4:major").sound("piano")

The melody plays 8 notes per cycle. The harmony changes 4 times per cycle. We hear: fast melody with slow harmonic movement underneath.

// Right leads: slow output, fast sampling
n("0 1 2 3 4 3 2 1".add.out("0 3 5 3"))
.scale("C4:major").sound("piano")

Now we only hear 4 notes per cycle—one per harmony change. Each harmony picks a note from the melody.

Completely different musical results. Same source patterns.


Not Just Addition

Every combining operation has these three modes:

// Multiplication
note("c2 e2 g2 b2")
.sound("sawtooth")
.gain("0.3 0.5 0.8 1.0")
.lpf("400 800 1200".mul("<1 2>"))
// Even non-numeric combinations
sound("bd sd hh cp")
.bank("<RolandTR808 RolandTR909>")

The first half uses 808 samples, the second half uses 909 samples.


Evolving Patterns

The power shows when patterns have different lengths:

// 3 notes + 4 intervals = evolving combination
n("0 2 4".add("0 3 5 7"))
.scale("C4:major").sound("piano")

Three melody notes. Four interval values. Since 3 and 4 don’t divide evenly, the combination takes 12 cycles to repeat (3 × 4 = 12). Each cycle sounds different as the intervals “rotate” against the melody.


A Fourth Mode: Squeeze

There’s one more way to combine—fit the entire right pattern into each event of the left:

n("0 2 4".add.squeeze("0 4 7"))
.scale("C4:major").sound("piano")

Each of the 3 melody notes becomes a rapid 3-note arpeggio. The interval pattern is squeezed into each event’s time slot. This is how you create arpeggios and fills.


Building Musical Ideas

With these modes, you can express precise musical intentions:

// A riff with changing root notes
n("0 2 4 7 4 2".add("<0 5 7 5>"))   // Left leads: riff rhythm preserved
.scale("C:minor")
.sound("sawtooth")
.lpf(800)

The riff plays its 6 notes. The root changes 4 times per cycle. We hear the riff transposed to different scale degrees.

// Chord stabs with melody sampled
n("0 1 2 3 4 5 6 7".add.out("<0 3 5>"))   // Right leads: chord rhythm wins
.scale("C4:major").sound("piano")

Three chord hits per cycle, each picking up a different melody note.


Quick Reference

// Left structure (default)
n("0 2 4".add("<0 7>"))          // 3 notes out
"1 2 3".mul("<1 2>")             // works on any numbers

// Right structure
n("0 2 4".add.out("<0 7>"))      // 2 notes out

// Intersection
n("0 2 4".add.mix("0 7"))        // varies

// Squeeze (fit right into each left event)
n("0 2 4".add.squeeze("0 4 7"))  // 9 notes out (3 × 3)

Why Timing Creates a Choice

Most containers have one obvious way to combine:

// Arrays: zip by position
[1, 2, 3].map((x, i) => x + [10, 20, 30][i])  // → [11, 22, 33]

// Position determines pairing—no ambiguity

But patterns have timing, and timing creates a choice that other containers don’t face:

ModeWho Decides When
.add()Left pattern
.add.out()Right pattern
.add.mix()Both must agree
.add.squeeze()Left events, right squeezed in

This is the Applicative question: when combining two containers, whose structure do we keep?

In databases, this is inner join vs. left join vs. right join. In streams, it’s merge vs. zip vs. sample. In patterns, it’s the choice between these four modes.

Different domains, same fundamental question. The Pattern type makes it explicit.


What’s Next?

We’ve combined patterns where each contains simple values. But what if a pattern contains… other patterns?

// A pattern of patterns
cat(
note("c4 e4 g4"),
note("d4 f4 a4 c5")
)

Two patterns, played in sequence. But now the outer structure holds inner structures. How do we “flatten” this?

Continue to Patterns of Patterns

You’ve seen how to combine patterns and choose whose rhythm wins. Next, you’ll discover what happens when patterns contain other patterns—and the many ways to flatten them.