Time as exact ratios
A pattern answers a question about an interval instead of stepping through a list that is consumed once. Mora
calls that operation events-in. A span is half-open: it includes its beginning and excludes its end.
Time is expressed in cycles, and fractions such as 1/3 and 7/8 can remain exact Common
Lisp ratios.
A cycle is a musical coordinate, not intrinsically a second or a bar. The transport maps it to physical time
using tempo and beats per cycle. With four beats per cycle, 140 BPM corresponds to 7/12 cycles per
second, so these two forms express the same rate:
(bpm 140)
(set-cps 7/12)
(events-in *first-pattern*
(span 5/8 9/8)
)
Drag the window or its endpoints, or tap the track to move the nearer endpoint. With an endpoint focused, use the arrow keys.
| Event | Whole | Visible part | Attack in this span |
|---|---|---|---|
sn
|
[1/2, 3/4) | [5/8, 3/4) | No, it began earlier |
hh
|
[3/4, 1) | [3/4, 1) | Yes |
bd
|
[1, 5/4) | [1, 9/8) | Yes |
*first-pattern*, bound to (s "bd [hh hh] sn hh") as in the
getting-started guide. The window is half-open: it includes its start
(filled endpoint) and excludes its end (open endpoint). An event cut by the window keeps its whole span, and only
an event whose onset falls inside the window starts a new attack. This figure is drawn by the website to explain
the idea; it does not run Mora.
Visible spans and whole events. A scheduler may query only a fragment of a note or loop. That query should not turn the fragment into a new full event, change a recording's pitch, or create another attack by accident. An onset belongs to the event's temporal identity, which is why score-time looping, audio-file looping, and changing a sample's playback speed are separate operations.
Notation inside Lisp
Mini notation is compact temporal notation inside strings. It is embedded in Common Lisp rather than replacing it.
The literal forms s, n, notes, p, and e are
macros with context-sensitive coercion: inside s, bare symbols can mean sample names.
| Notation | Meaning | Example |
|---|---|---|
Spaces |
Sequence within the current cycle or group. | "bd hh sn hh" |
~ |
Rest. | "bd ~ sn ~" |
[ ... ] |
Subdivide the containing slot. | "bd [hh hh] sn hh" |
Comma inside a group |
Parallel voices. | "[bd ~ sn ~, hh*8]" |
< ... > |
Alternate across cycles. | "<bd sn sn>" |
* |
Repetition, a faster local occurrence. | "hh*8" |
@ |
Relative temporal weighting. | "bd@3 hh sn@2 hh" |
_ |
Extend the preceding event's whole rather than make a new attack. | "f3 _ ab3 c4" |
Euclidean suffix |
A Euclidean attack structure. | "bd(3,8)" |
Use sub to subdivide a slot directly in s-expressions:
(d1! (s bd sn hh cp))
(d1! (s bd (sub hh hh) sn (sub hh cp)))
Use ($ expression) when a literal should evaluate ordinary Lisp. Functions, variables, and control
structures remain available; a list is ordinary Lisp data unless it is explicitly turned into a pattern.
(defparameter *kit* '("bd" "hh" "sn" "hh"))
(d1! (s ($ (apply #'cat *kit*))))
Composition, left to right
>> threads a pattern through transformations and clauses from left to right. Use
stack for simultaneous voices. Parallel voices are not parameter merging: annotations such as
gain use merge-left, so each existing event keeps its multiplicity while receiving its
parameter. The open pattern-merge-style protocol selects that behavior.
(d1! (stack
(>> (s "bd ~ bd ~")
(cut 1))
(>> (s "~ sn ~ sn")
(cut 2))
(>> (s "hh*8")
(cut 3))))
| Operation | Purpose |
|---|---|
stack, overlay |
Parallel patterns. |
cat, fast-cat |
Subdivided succession. |
slow-cat |
Succession across cycles. |
time-cat |
Fit phrases into proportional weighted spans. |
arrange |
Allocate actual cycle durations to sections. |
align |
Combine temporal value layers according to their spans. |
merge-left |
Annotate existing events without turning sibling voices into one event. |
time-cat weights are proportions; arrange allocates actual cycle durations. A Mora
channel owns a source pattern, while a SuperDirt orbit is an output bus, so one stack can route its lanes to
separate orbits.
Transformation and groove
Temporal transformations act on score time. Audio-file parameters act on the resulting sound: rev
reverses a score, while sample-rev reverses sample playback. The supplied chance operators are
temporally reproducible, rather than consulting global random state on every scheduler fragment.
- Rate and phrase shape
-
fast, slow, rev, palindrome, hurry, rot, iter, iter-prime - Placement and extraction
-
early, late, compress, zoom, trunc, linger, within, inside, outside - Repetition and copies
-
ply, jux, jux-by, off, superimpose, spin - Groove
-
swing-by, press-by - Conditional application
-
every, sometimes, and related conditional and chunk operations - Echoes
-
echo-with, echo-spaced, echo-indexed
(d1! (>> (s "bd hh sn cp")
(every 3 #'rev)))
(d1! (>> (s "hh*8")
(swing-by 1/3 4)))
(d1! (within 1/2 1 #'rev (s "bd hh sn cp")))
(d1! (>> (s "bd hh sn cp")
(jux #'rev)))
(d1! (echo-spaced 4 1/8 0.7 (s "bd ~ sn ~")))
Musical structure, explicitly authored
Named pitches, scale degrees, chords and voicings, arpeggiation, tuning beyond twelve equal divisions, and authored
meter are part of the language. Chord identity and its realization as voices are separate, and
arpeggiate selects actual voices from those chords.
(defparameter *changes* (chords "<Fm7 Dbmaj7 Eb7 Cm7>"))
(d1! (>> (voicing *changes*
:anchor "c4")
(arpeggiate "0 ~ 2 [1 3]")
(s "psin")
(legato 0.6)))
Authored positions are not the same as emitted attacks: rests count as positions. pace retimes a
phrase according to authored units, separately from global BPM. Here the three-unit and five-unit phrases occupy
3/4 and 5/4 cycles at four units per cycle, with consistent unit spacing instead of
squeezing each phrase into an equal duration.
(defparameter *phrases*
(slow-cat (s "bd ~ sn")
(s "bd hh ~ sn hh")))
(d1! (pace 4 *phrases*
:period 2))
The pitched examples need an available instrument named psin; an arbitrary SuperDirt installation is
not assumed to contain it.
What remains after evaluation
Consider a composition made from a sample sequence and a speed change, like the one below. The source is a convenient way to construct it, but the result is not limited to source text. Named pattern instances, their children, metadata, and applicable methods remain available for inspection and interpretation.
-
1
Source expression
(defparameter *example-pattern* (>> (s "bd sn") (fast 2))) -
2
Retained pattern objects
speed-pattern instance
(s "bd sn")pattern Inspectable now:pattern-summary,pattern-children -
3
A query over a span (here, cycle 0)
Arbitrary spans use the lower-level(query-cycle *example-pattern* 0)events-inprotocol, whose methods can be extended -
4
Events with values, spans, and provenance
bd[0, 1/4)sn[1/4, 1/2)bd[1/2, 3/4)sn[3/4, 1) - 5 Interpretation by an output backend For sample and synthesis output: timestamped OSC to SuperDirt in SuperCollider.
Introspection
Inspect the retained objects, their structure, parameters, and event answers. These forms are not audible.
Intercession
Add or redefine applicable protocol methods, changing the behavior obtained through those objects on subsequent queries. Output already dispatched is not rewritten.
(defparameter *pattern* (>> (s "bd ~ sn hh") (fast 2)))
(class-of *pattern*)
(pattern-summary *pattern*)
(pattern-children *pattern*)
(pattern-metadata *pattern*)
(events-in *pattern* (span 13/4 7/2))
Common Lisp and CLOS
Common Lisp is not only a notation host for Mora; it supplies the working environment in which the language is developed and performed. In CLOS, a generic function can have methods specialized for different classes. Mora uses this to separate temporal interpretation, structural observation, construction, composition policy, and shader emission, so an extension can implement the operation it actually needs.
Mora chooses named classes and generic operations so representation, strategy, and state can be inspected and specialized in the running image.
-
Authored pattern program
(>> (s "bd sn") (fast 2))Constructs a temporal program. - Mora's retained implementation objects A speed-pattern instance, events-in, applicable methods Interpret and expose that temporal program.
- CLOS implementation facilities Classes, generic functions, methods Supply the metalanguage in which Mora is built.
| Surface | Representative operations | Scope |
|---|---|---|
| Retained temporal interpretation | events-in |
Answers event queries. |
| Structural observation | pattern-children, pattern-summary, pattern-metadata |
Supports inspection and tools. |
| Tooling estimate | pattern-period |
Estimates visible repetition; it is not an exact metrical clock. |
| Open construction and composition | apply-transform, normalize-clause, apply-clause, pattern-merge-style |
Builds and combines behavior; some construction objects are transient. |
| Adjacent visual interpretation | emit-glsl |
Applies to the visual-operator subset, not every arbitrary pattern class. |
The openness is bounded. Not every intermediate construction object is retained, not every custom extension automatically gains every editor affordance, and a new interpretation may apply only to a particular class family.
Extending the language from inside
defpattern defines a named pattern class, constructor, and observation and query methods. The class
generated here is pulse-pattern, with a pulse-factor reader.
(defpattern pulse (&key (factor 1))
"An inspectable backbeat. Example: (pulse :factor 2)."
(fast factor (s "bd ~ sn ~")))
(defparameter *live-pulse* (pulse :factor 1))
(d1! *live-pulse*)
An applicable method can change the behavior of the same object on subsequent queries:
(defmethod events-in ((self pulse-pattern) query-span)
(declare (ignore self))
(events-in (s "bd hh sn [hh cp]") query-span))
The defpattern body is evaluated by its query method, not once as a universal compile step, so keep it
suitable for repeated arbitrary-span interpretation. deftransform defines a reusable, inspectable
transformation, and musical vocabularies such as scales can be extended as well:
(deftransform doubles (factor)
"Double or otherwise scale source rate."
(:apply (pattern)
(fast factor pattern)))
(d1! (>> (s "bd sn")
(doubles 2)))
(define-scale :my-pentatonic '(0 3 5 7 10))
(d1! (>> (degrees "0 1 2 3 4 5 6 7")
(in-scale :my-pentatonic
:root "f3")
(s "psin")))
The shape of the system
Mora is SBCL-first Common Lisp. Its scheduler and backends are separate from the notation layer, and Studio's editor and evaluation model is separate from its native GL renderer. Visual operators are pattern objects with an adjacent GLSL interpretation for that subset.
The language
-
mora/coreTemporal values, events, retained pattern protocol -
mora/runtimeTransport, channels, scheduling, backend protocol -
mora/miniTerse livecoding language -
moraUser facade
Optional clients and adapters
-
mora/superdirtTimestamped sound output -
mora/audioPrepared audio and control descriptions -
mora/samples*Discovery, cache, control, import, audition, Studio glue -
mora/visualVisual operators and native GL renderers -
mora/analysiscl-mltk integration -
mora/linkLink integration -
mora/studio-coreEditor, evaluation, assistance, inspection; no GL -
mora/studioNative window, GL rendering, application lifecycle
mora/runtime builds on mora/core; the mini language
and the mora facade sit above the runtime, and every client is optional. Studio's editor model is
separate from its GL renderer, which allows substantial work from a plain Lisp REPL. Entries with a solid blue outline
include native OpenGL rendering.