Concat
A corpus synthesizer. Point it at samples, it cuts them into slices, measures each one, and lays them out on a map of timbre. Then you play the map.
I made Concat because the granular modules on the Move each read one buffer. Concat reads a whole sample library at once: you travel through timbre across many files, or sing into it and hear your voice rebuilt from the corpus.
A map of sounds, not a timeline
Every slice gets measured: how bright, how noisy, how loud, what pitch, what timbre (13 MFCCs), each as an average and a variation over the slice. That puts every slice at a point in a 36-dimensional space. Concat flattens it to a 2-D map and plays a cloud of grains from the slices nearest your position.
A corpus
One sample, one folder, or a .concat file that gathers many folders and samples, trimmed and filtered.
A position
X and Y on the map, moved by knobs, the LFO, a note (Zone), a held pad (clusters), or your voice (Mosaic).
A cloud
Grains at a density you set, each a slice near the position, never the same one twice in a row, flowing from the last.
First sound in a minute
- In Schwung, add Concat to a track. A new Concat loads the first sample of your first Samples folder by itself.
- To choose your own: open Corpus from the root menu, select Source (knob 1), press the jog wheel, and pick a sample, or a
.concatfile fromCorpora. - Scope (on Mod & Set) decides what a picked sample means: File (default) makes that sample the whole corpus, cut into many slices; Folder takes its whole folder.
- Wait for Analyze to reach 100 %. A big folder takes a minute the first time; after that it loads from the cache.
- Play the pads. Each pad plays a different part of the corpus.
- Open View: see the map, and turn the jog wheel through the Recipes. Try Cloud, then Drone.
The corpus on the Move's screen
Knobs on View
X Position, Y Position, Radius, Density, Grain Size, K Nearest, Smooth, Filter: everything you need to move around without leaving the map.
Status line
analysing 3/40 while it works, then nothing. If something went wrong: out of memory, kept old, bad JSON: ..., kept old, 2 sources missing. A failed load never stops the corpus that is playing.
Three ways in
Notes (default)
Polyphonic: each pad is a voice with its own envelope. Note Match: Zone puts each pad somewhere else on the map, at natural pitch. Pitch picks the nearest-pitched slice and retunes it. Off plays the current position, retuned.
Map
Move the position with X/Y, or let Follow do it (LFO, or Sequence on the MIDI clock). Hold a pad to jump to one of 16 timbre clusters; let go to come back.
Mosaic
Concat listens to the line/mic input about every 23 ms and answers with the nearest slices. Beatbox into drums, hum into field recordings. Dry Mix blends your input in.
The pages
The root page holds 8 performance knobs. The menu below it opens View · Corpus · Map · Mod & Set · Grain · Shape · Play · Out.
Root
| Knob | Param | What it does |
|---|---|---|
| K1 · K2 | X / Y Position | Where you are on the map. |
| K3 | Density | Grains per second. |
| K4 | Grain Size | Grain length. |
| K5 · K6 | Amp Attack / Release | Per-note envelope (Notes). |
| K7 | Polyphony | Grain voice ceiling. |
| K8 | Filter | Bipolar DJ filter: left darker, right thinner. |
Corpus
| Param | Range | Default | Notes |
|---|---|---|---|
| Source | file browser | auto | A sample (.wav .aif .aiff .flac .mp3) or a .concat corpus file. |
| Pool Cap | 16 to 512 MB | 128 | RAM for corpus audio; 128 MB is about 25 minutes of mono. Free RAM can lower it, then Concat keeps an even subset of slices. |
| Rescan | trigger | Turn right: analyse again from scratch. | |
| Slice Mode | Onset / Fixed | Onset | Cut at attacks, or in even grains of Max Slice. |
| Min / Max Slice | 20 to 500 / 100 to 2000 ms | 20 / 2000 | Slice length limits. |
| Analyze | 0 to 100 % | Progress, read only. | |
| Silence Gate | 0 to 1 | 0.02 | Drops quieter slices. |
A .concat file's own slicing settings replace Slice Mode, Min/Max Slice and Silence Gate.
Map
| Param | Range | Default | Notes |
|---|---|---|---|
| X / Y Descriptor | Bright, Pitch, Loud, Noisy, Rolloff, MFCC1-3 | Bright / Pitch | The axes in Raw mode. |
| X / Y Position | 0 to 1 | 0.5 | Good targets for the Move's LFOs. |
| Map Mode | Raw / Learned | Raw | Learned uses the two directions along which this corpus varies most (PCA), so it spreads evenly. |
| Radius | 0 to 1 | 0.15 | How far grains may wander from the position. |
| K Nearest | 1 to 16 | 4 | How many nearest slices a grain picks from. |
| Repeat Avoid | 0 to 16 | 4 | Skips the last N slices played. |
Mod & Set
| Param | Range | Default | Notes |
|---|---|---|---|
| Follow | Manual / LFO / Sequence | Manual | LFO moves the position by itself; Sequence locks it to the MIDI clock. |
| LFO Rate | 0.01 to 10 | 0.2 | Hz with LFO; cycles per beat with Sequence (0.25 = a bar, 2 = an eighth). |
| LFO Shape | Circle, SineX, SineY, S&H | Circle | S&H with Sequence jumps on the beat. |
| Smooth | 0 to 1 | 0.35 | How much each grain follows on from the last one. |
| Pos Jitter | 0 to 1 | 0.15 | Random start inside the slice. |
| Scope | Folder / File | File | What a picked sample means: just it, or its whole folder. |
Grain
| Param | Range | Default | Notes |
|---|---|---|---|
| Density | 1 to 100 g/s | 20 | Grains per second. |
| Grain Size | 10 to 2000 ms | 120 | Longer than its slice, a grain loops it with a crossfade. |
| Grain Env | Hann, Tukey, Tri, Gauss, Expo, RevExpo, Trapez, Black | Hann | Window shape. |
| Pitch | -24 to +24 st | 0 | Transpose. |
| Pan Spread | 0 to 1 | 0.3 | Random stereo position per grain. |
| Grain Random | 0 to 1 | 0 | Random grain length. |
| Reverse | 0 to 1 | 0 | Chance a grain plays backwards. |
| Pitch Jitter | 0 to 1 | 0 | Random detune per grain. |
Shape
| Param | Default | Notes |
|---|---|---|
| Amp Attack / Decay / Sustain / Release | 0.01 / 0.2 / 1 / 0.2 | Per-note envelope (Notes mode). Map and Mosaic play at full level. |
| Filter Env | 0.45 | Envelope amount on the Filter (0.5 = none). |
| Filter Attack / Decay / Sustain | 0.01 / 0.3 / 0 | Filter envelope. |
Play
| Param | Range | Default | Notes |
|---|---|---|---|
| Play Mode | Map / Notes / Mosaic | Notes | See Play modes. |
| Note Match | Off / Pitch / Zone | Zone | Notes mode. |
| Pressure | Off, Proximity, Density, Bright, Sat, Pitch, Grain, Jitter | Density | What pad pressure changes. |
| Pressure Amt | 0 to 1 | 1 | |
| Velocity Amt | 0 to 1 | 0.8 | 0 ignores velocity. |
| Polyphony | 1 to 128 | 48 | Grain voice ceiling. |
Out
| Param | Range | Default | Notes |
|---|---|---|---|
| Filter | 0 to 1 | 0.5 | Centre off, left low-pass down, right high-pass up. |
| Resonance | 0 to 1 | 0 | |
| Saturation | 0 to 1 | 0 | tanh drive with make-up gain. |
| Dry Mix | 0 to 1 | 0 | Live input in the mix (Mosaic). |
| Volume | 0 to 1 | 0.8 | |
| Width | 0 to 1 | 1 | Stereo width. |
| Level Norm | 0 to 1 | 0.5 | Evens out slices from different recordings. |
Hands on
Pads
Notes: up to 16 held notes, each with its own envelope, sharing up to 128 grain voices.
Map: hold a pad to jump to a timbre cluster (cluster = note number mod 16). The last pad pressed wins; let go to return.
Pressure goes where the Pressure param says; velocity scales the level.
MIDI
Pitch bend transposes ±2 semitones. MIDI clock drives Follow = Sequence. CC 123 releases all notes. Poly aftertouch and channel pressure both work.
Eight starting points
On the View page, the jog wheel browses them. A recipe sets play and sound parameters and never changes your corpus.
| Recipe | What it sets up |
|---|---|
| Init | Defaults. |
| Cloud | Map + slow circular LFO, dense 400 ms grains, wide, smooth. |
| Stutter | Notes/Zone, 40 ms grains, tight neighbourhood, repeats allowed. |
| Drone | Map + very slow LFO, 1.5 s Gauss grains, long attack and release. |
| Scatter | Map + S&H LFO, random sizes, detune, reverse, the whole map in play. |
| Pitched | Notes/Pitch: the corpus played chromatically. |
| Mosaic | Mosaic mode, 80 ms grains, some dry input. |
| Sequence | Map + clock-locked LFO, one cycle per bar. Needs the MIDI clock. |
Build a corpus in the browser, hear it before the Move does
A web page that runs on your Move. Gather folders and samples, trim them, choose the descriptors, see and play the map, then load it onto Concat's track. It runs Concat's own analysis code (compiled to WebAssembly), so what you see is what the Move will compute: the same slice boundaries, descriptors within 0.06 %.
Open it
With the Move on your network (or connected over USB), open this in a desktop browser:
http://move.local:7700/api/remote-ui/module-assets/concat/builder/app.html
It is also one click away in schwung-manager's Remote UI: Concat's track has an Open the Corpus Builder button. If your Move answers on another name or address, replace move.local with it.
Build a corpus
- Gather. Drag folders and samples from the Library onto the corpus column, or use +. Ctrl+A selects every sample in a folder; drag them all, or press Add. ↑ ↓ step through samples and play each one.
- Listen and prune. A folder arrives collapsed: open it (▸) to hear its samples one by one and leave some out (✕, + brings them back). Only / Skip take name patterns like
*Kick*. - Trim. Click a sample: drag on its waveform to keep only that part. Click a slice to hear just that slice. For a sample inside a folder, Trim on its own pulls it out first.
- Slice. Onsets or fixed grains, min and max length, gate: saved into the corpus file.
- Choose descriptors. The Descriptors tab shows each one's range, median and histogram, and how much it shapes the map. Untick what you don't want Concat to listen for.
Hear the map
Two views, like Concat
Learned (3-D; Move view flattens it to the plane Concat uses) or Raw with Concat's X/Y menu. Colour by source, by a descriptor, or by Pad clusters. Click a source and its slices light up; click a dot to hear it.
Play it
Play the map (or P, or Shift+drag): press and move, and grains fire like Concat's Map mode, with Density, Grain and Spread. With Pad clusters, hold a pad in the grid to hear what that pad reaches on the Move.
Record
● Rec (or R) records what you play. Download the WAV, or save it to Recordings/Corpus Builder on the Move and add it straight back into the corpus.
Size it
The summary shows slices, files and length, and a meter against Concat's Pool Cap: it tells you the Pool Cap to set when the corpus is bigger than 128 MB.
Save and load
Save (Ctrl+S) writes UserLibrary/Corpora/<name>.concat. The header shows which track Concat is on and what it plays. Load on track N saves and makes that track play the corpus, with live progress. After that, every save reloads it on the Move while the old version keeps playing. Unsaved work survives a page reload.
The .concat format
Plain JSON, written by the Builder and readable by hand. Pick one in Source like a sample.
{
"concat": 1,
"name": "Night Market",
"sources": [
{ "path": "Samples/Ahuntsic_Ambiances" },
{ "path": "Samples/MELLOTRON/M300SaxB-20.wav", "start": 0.4, "end": 6.2 },
{ "path": "Samples/DRUMS", "include": ["*Kick*"], "recurse": false }
],
"exclude": ["*click*"],
"descriptors": { "use": ["loud", "centroid", "flatness", "pitch", "mfcc1-13"] },
"slicing": { "mode": "onset", "min_ms": 40, "max_ms": 600, "gate": 0.02 }
}
| Key | Meaning |
|---|---|
sources | Folders and samples, relative to /data/UserData/UserLibrary. A missing one is skipped and counted. |
start · end | Seconds: only that part of a sample. |
include · exclude | File-name patterns for a folder (*, ?, case ignored). A top-level exclude applies to every folder. |
recurse | false keeps a folder to its own files. |
descriptors.use | loud, centroid, flatness, zcr, pitch, mfcc, mfccN, mfccA-B. Left-out descriptors leave the search, the clusters and the Learned map. |
slicing | mode, min_ms, max_ms, gate: replaces the Corpus page settings. |
Live reload: when the loaded file changes, Concat rebuilds it in the background while the old corpus keeps playing, and only new or changed files are analysed again (a 9170-slice corpus went live in 8.6 s instead of 76 s).
Concat's panel in schwung-manager
In the manager's Remote UI (http://move.local:7700/remote-ui), Concat's track shows a small panel: the corpus playing and its status, a menu of your corpus files to load, and Open the Corpus Builder. Switch Interface to Default at the top of the track for every knob; the manager forgets that choice when the page reloads.
What it costs
RAM
Pool Cap (128 MB by default, 512 max), checked against free RAM at every load with 350 MB kept for the Move. Too big a corpus keeps an even subset of slices across all of it.
CPU
On a 25,000-slice corpus, under 2 ms per audio block at worst (the budget is 2.9 ms). Analysis runs on its own thread on cores 0 to 2.
Disk
Caches in /data/UserData/schwung/concat-cache: whole corpus (instant reload) and per file (edits). Nothing cleans them yet; delete the folder to reclaim space.
Get it on the Move
- Your Move runs Schwung 1.3.0 or later.
- Open schwung-manager (
http://move.local:7700) and install a custom module from this repo (filliformes/concat-move), or uploadconcat-module.tar.gzfrom the latest release. - Power-cycle the Move (Schwung reads module manifests at startup).
When it doesn't
| You see | Do this |
|---|---|
no audio files | The pick has nothing Concat can read. Pick a sample, not a folder. |
out of memory, kept old | Lower Pool Cap or trim the corpus. |
bad JSON: ... | The corpus file has a mistake; the old corpus keeps playing. Save it again from the Builder. |
| No sound | Check Play Mode and Volume, that Analyze reached 100 %, and that a pad is held (Notes) or the position sits among the dots (Map). |
| Source shows a number | The module metadata did not load: power-cycle the Move. |
Builder: Concat is not on a track | Add Concat to a track; the badge updates by itself. |
Corpus Builder keys
| Ctrl+A | Select every sample in the library folder |
| Enter | Add the selected samples |
| ↑ / ↓ | Step through samples and play each one |
| → / ← | Open / close a folder source |
| Space | Play / pause the selected sample |
| Esc | Stop, clear selections, show the whole map |
| P | Map: rotate ↔ play |
| Shift + drag | Map: play while rotating |
| R | Record what you play |
| Ctrl+S | Save to the Move |
| ? | Keys and tips |
Made with
Built on Schwung by Charles Vestal and contributors. Decoding with dr_libs (public domain). Algorithms studied, not copied, from FluCoMa (BSD-3) and Diemo Schwarz's CataRT papers on corpus-based concatenative synthesis; the DSP is written from scratch.
Inspired by Mosaïque (site), a corpus-based concatenative synthesizer for Max for Live that lays a corpus out in a 3-D timbral space, made by UdeM's Laboratoire formes • ondes (LFO-lab).
Instrument, DSP and Builder by Filliformes. MIT license.