Guide
Stems and sample libraries
Import a user-owned library, select an eligible pool with loops, compose from it with part and stem by type, variant, family, slice, and tag, and layer stems as replaceable lanes with stem-stack.
On this page
- Importing a Splice library
- What the importer keeps
- Restoring the library at startup
- Select a pool, then compose from it
- What a selection means
- Genre expressions
- Each selection is complete
- Requested pool and active playback
- Variants and slices
- Layering parts
- The keyword form
- Slice timing
- Replaceable lanes with stem-stack
- Lane options and identity
- How a lane is handed off
- Families and recordings
- Tags at selection time
- Your library and its limits
Mora can compose from a library of recorded loops by role, such as drums, bass, or melody, rather than by filename. This guide covers importing a user-owned library, selecting an eligible pool, playing variants, families, slices, and tagged subsets from it, and layering replaceable stem lanes.
sync-splice imports Splice content you have already downloaded, together with its available metadata, into Mora’s library. It is designed to be repeatable and incremental.
(sync-splice)The importer keeps confirmed metadata distinct from guesses. It preserves tags beyond a few predefined genres, maps useful role labels, and carries source identity into the sample catalog.
One-shots can be imported and embedded, but requesting a stem category does not turn them into BPM-, key-, or bar-qualified loops.
Mora’s normal startup integration can restore your usual saved library, so the catalog does not need to be rebuilt by hand on every launch. Restoring it requires the SuperDirtMora companion’s sample-loading path and sufficient configured memory.
loops describes the eligible pool of source recordings. part and stem construct ordinary temporal patterns that read from it: part selects a typed variant, and stem is a keyword-oriented version for richer variant and slice schedules. stem-stack layers stems as named, replaceable lanes. parts and stem-tags inspect what is available; you do not need to call them before each phrase.
A broad selection:
(loops :bpm-range '(130 150)
:mode :minor)A narrower selection that also names genres:
(loops :bpm-range '(130 150)
:mode :minor
:genre '(:drill :trap)
:sync nil)A selection like this names a range, not a hidden exact-BPM or exact-key clamp. It describes a source-tempo palette of pitched material known to be in a minor mode, not one exact tonic. Unpitched percussion remains eligible, and the source-tempo range logic recognizes appropriate half-time and double-time readings.
:modekeeps minor or major selection explicit.:genreaccepts multiple styles. A plain list is an OR: a source matching any requested genre is eligible.- An exact
:keyor:bpmfilter intentionally narrows the pool. Leave a restriction out when you do not want it.
Known metadata and active filters determine eligibility. The importer does not fill in a missing metadata field by guessing.
Selection is not pitch or time processing. Global tempo and key matching is requested on the player, not folded into discovery by filtering everything to one original tempo and key. See tempo and key matching.
Quoted :and, :or, and :not operators combine genres and may nest. This selects drill or trap, excluding witch house:
(loops :mode :minor
:genre '(:and (:or :drill :trap)
(:not :witch-house))):not takes exactly one expression, and a plain list inside it is still an OR. Names match explicit genre metadata and the corresponding Splice tags, ignoring case and treating spaces as hyphens. Genres are never guessed from tempo or filename, a negative-only query can include untagged sources, and :genre nil clears the filter.
loops publishes a complete selection context. Omitted filters are unrestricted; they are not inherited from the preceding query. That includes grouping: repeat :group :family on each call to stay grouped. A bare (loops) reports the current selection and does not reset it.
Existing part and stem readers follow the new pool at subsequent scheduled onsets. Audio already dispatched is not canceled solely because the library selection changed.
Requested source metadata publishes immediately, so parts describes the requested pool, while playing voices keep their frozen view until the replacement is prepared. If preparation fails, the old score keeps playing with one nonmodal diagnostic; correct the query and run it again. A query that would leave a playing type without eligible sources is refused.
Variant selection and slicing are independent patterns. The first argument after the type selects variants and may be an integer or mini notation. The optional following string selects slice steps. With no slice pattern, the complete source loop plays over its native length.
(d1! (part :bass 0))
(d1! (part :bass "<0 1 1>"))
(d1! (part :bass 0 "0 ~ 2 3 4 ~ 6 7"))In a variant string, <0 1 1> chooses variant 0, then 1, then 1 in successive cycles. A slice pattern rearranges slices; slice numbers are not treated as pitch values.
Slice strings use mini notation, timed by default in ordinary Mora cycles for part and by the source recording for stem (see slice timing). The default source division has eight slices, but slice strings are not required to contain eight tokens. Nested alternation and subdivision are supported, so a rhythm need not be flattened into eight manually counted values, and nested alternations and rests do not retune the sample.
(d1! (stack
(>> (part :drums 0 "0 1 2 3 4 4 6 7")
(cut 1))
(>> (part :bass "<0 1 1>" "0 ~ 2 3 4 ~ 6 7")
(cut 2))
(>> (part :melody 0)
(cut 3))))These examples need matching drums, bass, and melody members in your own collection.
stem makes the two axes explicit: :n selects which loop, and :p selects its slices. In family mode, :v also selects a recording within the family. A single unindexed :p string supplies a common slice pattern:
(d1! (stack
(>> (part :drums 0)
(cut 1))
(>> (part :bass 0)
(cut 2))
(>> (stem :type :melody
:n "<0 1 1>"
:p "0 ~ 2 3 4 ~ 6 7")
(cut 3))))No sample filenames need to appear in the performance form. This needs enough eligible variants for each requested type.
Repeated :p entries with numeric positions address occurrences in the variant progression, not globally named sample IDs or :v positions:
(d1! (stem :type :melody
:n "<0 1 1>"
:p 0 "0 ~ 2 3 4 ~ 6 7"
:p 1 "0 ~ 4 2 3 ~ 6 7"
:p 2 "2 ~ 0 3 4 ~ 6 7"))<0 1 1> uses variant 0 for one cycle and variant 1 for the next two occurrences before repeating. Here :p 2 addresses position 2 of the progression, not variant 2: it supplies a second slice strategy for the repeated variant 1, so the two variant-1 occurrences can have different slice patterns.
| Form | Default :timing | One cycle of slice notation lasts |
|---|---|---|
stem | :source | One source slice: the recording’s effective length divided by :slices. |
part | :cycle | One ordinary Mora cycle. |
Both accept either value explicitly, and :slices defaults to 8 regardless of how many tokens the slice string has. The effective length includes the normal, half-time, or double-time reading chosen under matching. With eight slices, the first form below traverses each recording over its own length, even when lanes have different lengths; the second plays one slice per Mora cycle:
(d1! (stem :type :melody
:n 0
:p "<0 1 2 3 4 5 6 7>"))
(d1! (stem :type :melody
:n 0
:p "<0 1 2 3 4 5 6 7>"
:timing :cycle))With cycle timing, a short recording can finish a slice before the next trigger: the spacing changes, not the speed or pitch. :n and :v progressions always use ordinary Mora cycles.
stem-stack layers stems as named lanes. When the form is evaluated again, unchanged lanes keep playing and each changed lane fades to its replacement on the running audio.
(d1! (stem-stack :fade-beats 4
:curve :equal-power
(>> (stem :type :drums
:n 0
:match :tempo)
(cut 1)
(orbit 0))
(>> (stem :type :bass
:n 0
:match t)
(cut 2)
(orbit 1))
(>> (stem :type :melody
:n 0
:match t)
(cut 3)
(orbit 2))))Change the melody lane’s :n 0 to :n 1 and evaluate the form again: only that lane hands off, and the drum and bass lanes keep playing. This needs a second eligible melody for the change to be audible.
Options come before all lanes: :fade-beats is a positive, finite number of musical beats, not seconds (default 4, one bar in 4/4), and :curve is :equal-power (default) or :linear.
Each lane contains exactly one stem, optionally wrapped in transforms, effects, gain, cut, and orbit. A part or s lane is refused; layer those with ordinary stack, as in layering parts.
A lane is identified by its stem type when that type is unique; when a type repeats, each lane of that type needs a distinct :id on its stem, such as :id :lead. Identity is never :n, :v, an orbit, or a cut group, so reordering lanes keeps it.
- A lane keeps playing when its literal recipe and selected recording are unchanged; a
loopschange that alters its recording hands it off even if its text is unchanged. - The fade acts on running audio, not only on newly triggered events. It starts shortly after evaluation, on a quarter-cycle boundary rather than the next loop boundary, and incoming lanes join at their current source phase; if preparation misses that point, the edit is refused and the previous lanes keep playing.
- Removing a lane fades it out. During a fade, only the newest pending edit is kept.
- A failing lane is silenced on its own with a nonmodal notice while the others continue. Hush and stopping the channel behave as usual.
Mora does not detect equivalent rebuilt expressions, so wrapping a stored stem in a new >> on each evaluation makes a new lane; keep complete routed lanes in variables instead. One stem-stack forms the whole audio assignment: outer transforms may wrap it, but other parallel audio cannot sit outside its lanes.
The default :group :flat keeps every recording’s original number. :group :family is opt-in and groups related recordings of one musical idea into a family.
(loops :group :family
:mode :minor
:genre '(:drill :trap))
(parts :melody)In family mode, :n selects the family and :v selects its member recording (default 0). parts lists family and member coordinates, original filenames, and each member’s bars, BPM, and key; full and chopped recordings in one family can differ in length. The two selectors are independent progressions:
(d1! (stem :type :melody
:n "<0 1 1>"
:v "<0 1>"
:match t))stemaccepts a nonnegative integer or numeric mini notation for:v;partaccepts one nonnegative integer. Flat mode accepts only:v 0.- A changing
:vchanges the recording only in a family with several members. - Numbers wrap within established slot counts. A removed recording leaves a silent, unavailable slot rather than renumbering the others.
Grouping is conservative inference from names, such as trailing treatment words (alternate, dry or wet, fill, chops) within the same pack, type, BPM, and key; meaningful numbers and instrument words stay significant, and ambiguous names stay separate. It is not audio clustering, no audio is deleted, and member 0 is the first member found, not necessarily the dry original.
Family numbering is a separate, persisted view. Returning to flat grouping restores the original numbers:
(loops :group :flat
:mode :minor
:genre '(:drill :trap))Tags remain useful after the initial genre filter. The following restricts the melody candidates to those matching the requested tag:
(part :melody 0
:filter '(:synth))Use the library’s tag inventory rather than assuming that every provider uses the same spelling for an instrument or adjacent genre:
(stem-tags :melody)
(d1! (part :melody "<0 1 1>"
:filter '(:synth :wet)))By default, stem-tags counts tags across the complete registered library; :scope :selection limits the counts to the selected pool:
(stem-tags :melody
:scope :selection)Tag filters follow these rules:
- A flat
:filterlist is an AND: it requires every requested tag. The:genrelist inloops, by contrast, accepts any requested genre. - Keyword and string forms normalize case, spaces, underscores, and hyphens.
nilremoves the local tag restriction.- Filtered variant numbering belongs to that subset and retains established logical identities. It does not reorder the global pool by search relevance. In family mode, families and members are numbered inside the filtered pool.
A tag is a source attribute. Learned compatibility between stems is a separate tool; see PHALAR stem pairing.
Available recordings depend on your library. Sample import is explicit, repeatable, and subject to configured limits. Missing metadata, an empty filtered pool, and a sound bank absent from SuperDirt are different conditions.
Mora does not include licensed Splice audio. See licensing and assets.
Source references
Repository-relative paths in the Mora source where these mechanisms are implemented.
-
src/stems/import.lisp -
src/samples-import/ -
src/mini/loop-library.lisp -
src/mini/loop-tag-filter.lisp -
src/mini/loop-families.lisp -
src/mini/loop-session.lisp -
src/mini/stem.lisp -
src/mini/stem-stack.lisp -
src/mini/stem-stack-runtime.lisp