# PT32-V MIDI map (fw v32) Switching era (CC 105) rewrites bit depth, compand, dither, interpolator, src-rate reference, cutoff, resonance, filter env, filter position, pre cutoff/res, drive, crush rate and the output HP to that era's rig-verified defaults. It does **not** touch the live loop brackets, START, the ADSR, LEVEL, PERIOD, TUNE or REGRIND. (Before v31 it did clear the brackets, which threw away a loop the web editor was still drawing.) Parts 1–4 listen on MIDI channels 1–4 — four parts, like Paula's four hardware channels. Boot eras: ch 1 = PAULA, ch 2 = SP, ch 3 = S950, ch 4 = MPC60. Each part defaults to 4 voices, filling the 16-voice pool. All CCs below are per part (send on that part's channel). The CC matrix is 40 assignable slots per part — this is the default template; any CC can be re-pointed at any target over SysEx. ## Sound | CC | Parameter | Range / zones | |-----|------------------|------------------------------------------------------| | 102 | Sample slot | value % 8 → slot 0–7 | | 103 | START offset | 0–100% of sample (live; grains follow too) | | 105 | Era mode | 0 PAULA · 1 SP · 2 S950 · 3 MPC60 · 4 CLEAN (val%5) | | 106 | Bit depth | 4 → 16 bits | | 108 | Filter cutoff | 40 Hz → 20 kHz, log | | 116 | Drive | 0 → 1 | | 113 | SRC RATE (crush) | 4 kHz → 48 kHz log; **127 = OFF** (native) | | 114 | LED filter | ≥ 64 = on (Sallen-Key 3090 Hz, Q 0.66 — schematic) | ## Character (era voicing) | CC | Parameter | Range | |-----|------------|---------------------------------------------------------| | 22 | PERIOD | 0 free-pitch → 1 integer Amiga periods (chord magic); quantised in the crush-rate domain, so drift stays inside the authentic ~8-cent grid | | 23 | TUNE | ±100 cents fine tune | | 24 | HP FREQ | 5.1 Hz (the A500's own output HP) → 2 kHz, log | | 25 | HP RES | 0 → 1 resonance on the output high-pass | | 26 | AMIGA PAN | ≥ 64 = voices hard-panned L/R by slot [DEV-04] | | 27 | REGRIND | 0 → +24 st, snapped to semitones. The tracker resample-up trick, virtual: source reads as if cleanly resampled 2^(st/12)× faster, played back down by the same factor — pitch/tempo unchanged, playback step rate divided. A playback mode: every era and the grain layer carry it (PAULA runs its pulse train at period × k; the rest ZOH-hold k source samples per step). Low-period crunch without the note dropping; also lands 48 kHz tags in the authentic 113–856 period window so STRICT/PERIOD bite | ## Filter (fw v22) | CC | Parameter | Range | |-----|------------|---------------------------------------------------------| | 108 | CUTOFF | 40 Hz → 20 kHz, log (the post-engine VCF) | | 85 | RESONANCE | 0 → 1 | | 86 | FILTER ENV | −1 → +1 envelope sweep (SP pair 1·2) | | 87 | FILTER POS | 0–42 **POST** · 43–84 **PRE** · 85–127 **DUAL** | | 88 | PRE CUTOFF | **0 = TRACK CUTOFF** (CC 108 drives the pre filter); else 40 Hz → 20 kHz log | | 89 | PRE RESO | 0 → 1, Q up to 20 | **POST** is the classic chain and is bit-identical to v21. **PRE** moves the filter in front of the era colouration; **DUAL** runs a resonant pre filter *and* the normal VCF. The point of PRE is that both rate-reduction effects (SRC RATE crush and REGRIND) decimate at the source read, so a filter after them only ever hears signal that has already aliased. The pre filter runs at 48 kHz on a clean read and the decimation happens *after* it — so its resonant peak **folds**: > **image = |hold rate − pre cutoff|**, where hold rate = SRC RATE at root > pitch, or 48 kHz ÷ 2^(REGRIND/12). Sweep PRE CUTOFF up past half the hold rate and the image sweeps *down*. With SRC RATE 8 kHz, a 6 kHz peak lands at 2 kHz; at 7 kHz it lands at 1 kHz. Turn PRE RESO up and that image is the loudest thing in the sound. Works on every era (PAULA folds through the pulse train, keeping its BLEP construction) and on the grain layer, where each grain folds on its own. ## Voices (fw v22) | CC | Parameter | Range | |-----|------------|---------------------------------------------------------| | 90 | MAX VOICES | 1 → 16 for this part (default **4**) | | 91 | STEAL | <64 **FADE** (~1.5 ms ramp) · ≥64 **CUT** (immediate) | The pool is 16 voices shared by the four parts, so 4 each is the balanced split. Give a part more when the others are idle — the allocator's reservation floor still protects the rest, and above ~70% mean CPU load new voices are refused rather than overrunning the audio callback. Watch the heartbeat's mean-load byte: PAULA+grain is ~25% per voice, so a part set to 16 will hit the CPU ceiling long before the voice ceiling. ## Envelope (ADSR) | CC | Parameter | Range | |-----|-----------|-----------------------------| | 109 | Attack | 0 → 2 s (squared taper) | | 110 | Decay | 0.01 → 8 s (squared) | | 111 | Sustain | 0 → 1 | | 112 | Release | 0.005 → 6 s (squared) | Sustain 0 + short decay = one-shot drum behaviour. ## Loop (all live on held notes) | CC | Parameter | Range / zones | |-----|----------------|----------------------------------------------------------| | 104 | Loop mode | <32 sample-default · 32–63 OFF · 64–95 FWD · 96–127 P-P | | 117 | Loop bracket L | +0–100% **offset from START** (Digitakt-style) | | 3 | Loop bracket R | +0–100% **offset from START** | | 107 | Loop crossfade | 0 → 60 ms (forward-loop seam) | Brackets are START-relative: sliding START (CC 103) drags the loop window across the sample with its length preserved, and an owned playhead relocates phase-preserved when the window moves. Sample-embedded loop points (SysEx 0x04) stay absolute. Brackets capture the playhead only once it enters them; a START past the loop plays the tail out. Exact points persist to SD via SysEx 0x04 (the webapp auto-saves ~1.5 s after edits settle). Three edge cases behave properly as of v31, and all three used to land on the same 32-sample window — a 1.5 kHz squeal rather than a loop: - **START + bracket L past the end of the sample** (START 0.5 with bracket L at 0.6 is an ordinary gesture) slides the window back inside the sample with its **length preserved**, which is what the START-slide gesture promises. - **Crossed brackets** (R dragged below L) are **ordered**, so sweeping one past the other shrinks the loop and grows it again. - **CC 107 crossfade with the loop starting at the beginning of the sample.** The seam's partner is normally the material just before loop start; with no pre-roll the fade now uses the material just *past* loop end instead — the same seam read from the other side. Before v31 the knob was silently dead for the commonest bracket position of all. Bracket resolution over MIDI is 1/127 of the sample length. The web editor snaps its markers to that grid so the screen shows the loop the box has; the *absolute* points written to SD by SysEx 0x04 are sample-exact. ## Grain layer | CC | Parameter | Range / zones | |-----|----------------|---------------------------------------------------| | 20 | Grain on/off | ≥ 64 = on | | 21 | Sync source | 0–42 TRIG · 43–84 CLOCK · 85–127 OFF (free) | | 115 | Division | 12 zones: 1/4 1/4T 1/4. 1/8 1/8T 1/8. 1/16 1/16T 1/16. 1/32 1/32T 1/64 | | 118 | Gate | 0.05 → 4.0 (1.0 = seamless repeat, >1 = cloud) | | 2 | Scan | −1 → +1 playhead travel per grain — **only does anything with POS MODE = SCAN** | | 14 | Shape | grain window: 0 short declick → 1 full Hann | | 15 | Position | 0 → 100% of the remaining material after START (or active loop) | | 16 | POS MODE | 0–42 HOLD · 43–84 SCAN · 85–127 JUMP | | 17 | Spray | 0 → 250 ms onset jitter | | 18 | Pitch jitter | 0 → 12 st per grain | | 19 | **HOLD** | 0 → 400 ms **minimum** grain length, applied *after* GATE / GATE RATIO / SIZE | | 28 | **SWING** | straight → hard 3:1 shuffle, applied per grain PAIR | | 29 | **GATE RATIO** | ×0.25 → ×4 on the **offbeat** grain's length (centre-detented at ×1) | | 30 | **OFFSET** | −50% → +50% of a grain period; 64 = dead centre | | 31 | Grain size | 1 → 400 ms (the OFF-mode grain length) | SWING needs a whole even number of grains per bar, so it applies to 1/4 … 1/64 and all four triplets, and is ignored on the three dotted divisions (where a pair would straddle the barline). RATIO is included in that test: 1/16 × 1.5 = 24 per bar swings, 1/4 × 1.75 = 7 does not. Under TRIG and OFF sync the pairs are anchored to the note-on instead of the bar, so swing works on every division there — including dotted. In OFF sync GATE RATIO scales SIZE, since that is what sets grain length in that mode. **HOLD** is a floor on grain LENGTH, not a length. With it up, sweeping GATE down (or DIVISION up) stops shrinking the grains toward the window's own 0.5 ms edges — the period keeps falling while the length does not, so the cloud starts *overlapping* instead of thinning into a click train. The same gesture goes from stutter to drone. It is capped at **four grain periods**, which is exactly what GATE already reaches (its own clamp is 0.05 → 4.0), so HOLD cannot push the grain pool anywhere GATE could not already: worst-case per-voice load is unchanged. At musical divisions the cap never bites (1/16 at 120 BPM caps at 500 ms, above the knob's range); at 1/64 it caps around 125 ms, which is still 4× overlap. Note what HOLD *is*, acoustically: with POS MODE = HOLD the overlapping grains read the same material at fixed one-period offsets, which is a comb filter. On tonal material that colours it and can null partials outright (four grains one period apart on a steady sine is 90° spacing — a perfect null). SPRAY, PITCH JIT or POS = SCAN break the coherence if you want thickness without the comb. OFFSET is absolute against the clock in CLOCK sync; under TRIG/OFF (where there is no external grid) turning it nudges the running cloud. With no loop, grain base = START + (1 − START) × POSITION, read live at spawn. With an active loop, POSITION spans its brackets and grains stay inside that region, including held grains when brackets move. ## Pod performance controls (v33) CC 93 is pitch: 0 = −12 semitones, 64 = unity, 127 = +12 semitones. CC 92 is the fine-short-gate curve: `0.05 + 3.95 × (value / 127)^2`. CC 118 keeps its existing linear gate mapping for older controllers. All three act on held notes. Pod bank/control assignments are in [README.md](README.md). The Pod division knob covers all 12 existing CC 115 zones; no timing divisions were added or removed. ## Performance sources (mod matrix, re-routable) | Source | Default routing | |-------------------|----------------------------| | CC 1 (mod wheel) | → grain SHAPE, +1.0 | | Channel aftertouch| → RESONANCE, +0.6 | | Velocity | (unrouted; matrix source) | | Keytrack | (unrouted; matrix source) | | Pitch bend | ± bend range (default 2 st)| Note that CC 1 also lands on **grain SHAPE** through that default slot. It is a modulation on top of CC 14, not a replacement for it — re-route slot 1 and the wheel stops touching SHAPE, while CC 14 keeps working. Matrix: 8 slots per part, sources {LFO1, LFO2, ENV1, ENV2, wheel, aftertouch, velocity, keytrack} → 31 destinations: {pitch, cutoff, resonance, level, pan, bits, crush, drive, gate, shape, grain pos, loop L/R} plus, as of v22, {swing, gate ratio, grain offset, pre cutoff, pre reso, start, grain size, density, spray, pitch jitter, scan, grain ratio, hp freq, hp res, filter env, xfade, regrind} and, as of v32, {grain hold}. Destinations are **append-only** — the raw index travels over SysEx 0x07 and the web editor mirrors the list by index. Configured over SysEx (0x07 slot / 0x08 LFO / 0x09 env) — the webapp's MOD MATRIX panel drives it. ## Transport / system | Message | Effect | |--------------------|-------------------------------------------------| | MIDI clock (0xF8) | DPLL tempo + phase (grain sync, synced LFOs) | | Start / Continue / Stop | Bar phase reset / resume / free | | Song Position Ptr | Re-anchors bar phase | | Program change | Sample slot select on the part's channel | | **CC 119 = 127** | **DEV: jump to DFU bootloader (reflash)** | ## SysEx (all under `F0 7D 50 54 … F7`) | Cmd | Function | Reply | |------|-----------------------------------|-------| | 0x01 | Begin sample transfer | — | | 0x02 | Sample data chunk (7-bit packed) | 0x70 ack every 8 | | 0x03 | End transfer (checksummed) | 0x71 | | 0x04 | Set loop + root on a slot (→ SD) | — | | 0x05 | Ping | heartbeat | | 0x06 | Slot info + waveform preview | 0x72 + 0x73×17 | | 0x07 | Mod matrix slot | — | | 0x08 | LFO config (shape/rate/sync/swing)| — | | 0x09 | Mod env config (attack/decay) | — | | 0x0A | Debug: dump part state | 0x74 | | 0x0B | Debug: dump every active voice | 0x75 | Unsolicited: **0x76** reports ITCM corruption (PT32_ITCM builds only) — the first mismatching address between ITCM and its QSPI load image. Heartbeat (1 Hz, unsolicited): fw version, uptime, clock lock, BPM, active voices, output peak, **audio-callback load (peak %, mean %) and the count of blocks that overran the budget**. Mean load is the number to watch: the Seed renders roughly 4 PAULA+grain voices before the callback overruns, and a sustained overrun wedges the audio DMA. The allocator refuses new voices above ~70% mean rather than letting that happen, so heavy patches drop a note instead of freezing the box.