7.10.1. STRUDEL-01 — Hello Pattern

This tutorial is the entry point for the daStrudel series. It introduces the two central concepts of the library — the Pattern type and the Hap event — and shows three different ways to create your first pattern. By the end you will have queried a pattern by hand, played a single drum hit through the audio system, and played a single sine note. No mini-notation, no combinators — just the bare data model and the playback loop.

7.10.1.1. What is a Pattern?

A Pattern is a pure function from a query window to a list of events. Concretely:

typedef Pattern = lambda<(span : TimeSpan) : array<Hap>>

You give it a TimeSpan (a half-open interval [start, stop) measured in cycles — not seconds), and it returns every Hap whose lifetime intersects that span. The function has no side effects, allocates only the result array, and can be queried any number of times. This is the single design idea that makes the rest of the library work — every combinator (fast, slow, rev, euclid, jux, …) just wraps an inner pattern with a function that rewrites query spans on the way in and result haps on the way out.

7.10.1.2. What is a Hap?

A Hap is one event with two timestamps and a value:

struct Hap {
    whole : TimeSpan      // the event's full lifetime
    part  : TimeSpan      // the slice that fell inside the query
    has_whole : bool
    value : Event         // sound name, note number, gain, pan, ...
}

whole is when the event would naturally start and stop. part is the portion that actually lies inside the query span. They differ when your query cuts an event in half — the audio engine uses part to decide what to schedule and whole to compute envelope phase.

7.10.1.3. Part A: Query a Pattern by hand

The simplest constructor is pure(Event) — it lifts an event into a pattern that emits it once per cycle. We can call the pattern lambda directly with invoke to see what it produces:

let pat <- pure(Event(s = "bd"))
var haps <- invoke(pat, TimeSpan(start = 0.0lf, stop = 1.0lf))
for (h in haps) {
    print("whole=[{h.whole.start}, {h.whole.stop})  s='{h.value.s}'\n")
}
// output: whole=[0, 1)  s='bd'

That single hap is what the audio engine will receive once per cycle and turn into a kick-drum sample trigger. No audio yet — just data.

7.10.1.4. Part B: Play a single drum sound

To turn haps into audio you need three things: an audio system, a strudel channel, and a tick loop. The minimal harness used throughout this tutorial series:

require strudel/strudel public
require strudel/strudel_player
require audio/audio_boost
require daslib/fio

def play(var pat : Pattern; seconds : float = 4.0; cps : double = 0.5lf) {
    with_audio_system() {
        strudel_create_channel()
        strudel_set_cps(cps)
        strudel_add_track(pat)
        let t0 = ref_time_ticks()
        while (float(get_time_usec(t0)) / 1000000.0 < seconds) {
            strudel_tick()
            sleep(5u)
        }
        strudel_shutdown()
    }
}

The fully featured version of this harness — with WAV rendering, --duration and --track-memory flags — lives in examples/daStrudel/features/feature_common.das. The minimal form above is enough to get sound out and is what every tutorial in the 01..05 set uses.

s("bd") is the shortest mini-notation: a single token. It parses to the same Pattern as pure(Event(s = "bd")) but goes through the mini-notation parser:

let pat <- s("bd")
play(pat, 4.0)

cps = 0.5 means half a cycle per second — one cycle every two seconds. So you hear “bd” played twice over a four-second run.

7.10.1.5. Part C: Play a single synth note

For pitched sound, note() is the synth counterpart of s(). It parses note names (“c4”, “eb3”, “g#5”) into MIDI numbers, and the optional second argument picks an oscillator:

let pat <- note("c4", "sine") |> sustain(0.5)
play(pat, 4.0)

sustain(0.5) says each note holds for half its slot. Without it the default ADSR envelope would cut the note very short.

The pipe operator |> is just function call with the left side as the first argument — pat |> sustain(0.5) is identical to sustain(pat, 0.5). Every per-event setter (gain, pan, lpf, room, …) follows this Pattern-in / Pattern-out shape, so you build effect chains by piping.

7.10.1.6. Where next

You now know:

  • A Pattern is a pure function TimeSpan -> array<Hap>

  • A Hap is an event with two timestamps and a value

  • pure, s, and note are three constructors

  • The minimal playback harness is with_audio_system + create_channel + set_cps + add_track + tick loop + shutdown

Tutorial 02 introduces real mini-notation — sequences, rests, and subdivisions — so a single s(...) call can describe a full drum pattern.

See also

Full source: tutorials/daStrudel/daStrudel_01_hello_pattern.das

Next tutorial: STRUDEL-02 — Mini-Notation Fundamentals

Related: Lambdas and Closures — the Pattern type is a typed lambda

Related: Modules and Program Structurerequire strudel/strudel and other module forms