Contents

Pattern Literals

Patterns can be written as typed string prefixes that disambiguate parse semantics at the literal site:

PrefixAtom semanticsCoerces to Signal?
v"…"Numeric only: 0, 0.5, -0.5, 1e3Yes
n"…"Note names + bare MIDI ints (both → Hz)Yes
s"…"Sample names: bd, sd, kick:2Audio output
c"…"Chord symbols: Am, C7, F#m7b5No (use poly())
p"…"Auto-detect, backwards-compatible legacyInherits

The legacy pat(string) / p"…" form still works and infers the mode per atom.

v”…” value patterns

A numeric pattern produces stepped raw scalars at each event boundary. Atoms must be numeric literals; note names, sample names, and chord symbols are rejected with E163.

osc("sin", v"<220 440 880>")        // raw Hz, no mtof
lp(sig, v"<200 800 2000>", 0.7)     // pattern-driven cutoff
sig * v"<0.2 0.5 1.0 0.5>"          // amplitude envelope

Negative numbers, decimals, and scientific notation all parse:

v"-0.5 0.25 1e3 -1.25e-2"

n”…” note patterns

Note names and bare integers both map to Hz via mtof. Identical to pat() for note input, but rejects sample-name fallback.

osc("sin", n"c4 e4 g4")
osc("sin", n"60 64 67")            // bare ints = MIDI notes

s”…” sample patterns

Atoms are sample names. Use with the sampler / out() audio path:

s"bd ~ bd ~" |> out(%, %)
s"hh*8 [bd sd]" |> out(%, %)

c”…” chord patterns

Atoms are chord symbols (Am, C7, Fmaj7, …). Multi-voice; consume via poly():

c"Am C G Em" |> poly(4, fn (e) -> osc("saw", e.freq) * ar(e.trig) ) |> out(%, %)

A chord pattern in a scalar slot errors E160. Silently dropping voices is not implicit.

Pattern → Signal coercion

When a Pattern value reaches a slot that expects a Signal, the compiler implicitly extracts the pattern’s primary value buffer (the freq field). This makes patterns first-class as scalar values:

osc("sin", n"c4 e4 g4")            // Pattern freq → osc freq slot (Signal)
lp(sig, v"<200 800>", 0.7)         // Pattern → cutoff slot (Signal)
osc("saw", n"c4 e4" + v"<0 12>")   // Pattern + Pattern arithmetic

The coerce only fires for monophonic, non-sample patterns. Polyphonic patterns (chord, multi-voice) error E160 with a hint to poly(), since silently emitting voice 0 is rarely what you want. Sample patterns route through SAMPLE_PLAY and produce audio output rather than a scalar; use out() directly.

scalar()

The explicit cast version. scalar(p) unwraps a monophonic pattern’s freq buffer as a Signal, identical to what auto-coerce produces. Useful when you want to be explicit, or when feeding into a context that the auto-coerce wouldn’t fire on:

scalar(n"c4 e4 g4")                // Signal carrying mtof'd freqs
scalar(v"<220 440>") * 2           // Signal * 2 → Signal

scalar() is idempotent on Signal: scalar(scalar(p)) is safe. It errors E161 on sample or polyphonic patterns.

Pipe-binding stays a pattern

The as e pipe binding does not coerce. e remains a Pattern, and e.freq, e.vel, e.trig, e.gate plus any custom record-suffix keys (e.cutoff, e.bend, …) are accessible as fields:

n"c4{cutoff:0.3} e4{cutoff:0.7} g4{cutoff:0.5}" as e
  |> osc("saw", e.freq)
  |> lp(%, 200 + e.cutoff * 4000)
  |> out(%, %)