Preset.to_array() packs all synth state into a single (145,) float32 vector.
The first 123 values are continuous floats in [0, 1]. The last 22 values are integers (stored as float32 in the array, recovered by rounding on from_array()). The integer maxes are documented in the table below, making it straightforward to specify independent sampling distributions for each section.
You can create a Preset from a Patch (patch.to_preset() or Preset.from_patch(patch)), from a flat array (Preset.from_array(arr)), or by constructing one directly with keyword arguments.
All 145 values are JAX PyTree data leaves — changing any value, including algorithm, never triggers JIT recompilation.
| Index | Field | Native range |
|---|---|---|
| 0 | feedback | 0–7 |
| 1 | transpose | 0–48 |
| 2 | pitch_mod_sensitivity | 0–7 |
| 3 | lfo_speed | 0–99 |
| 4 | lfo_delay | 0–99 |
| 5 | lfo_pitch_mod_depth | 0–99 |
| 6 | lfo_amp_mod_depth | 0–99 |
| 7–10 | pitch_env_rates (4) | 0–99 |
| 11–14 | pitch_env_levels (4) | 0–99 |
The 6-operator fields are packed in op-major order: all values for op 0 come first, then op 1, and so on through op 5. For example, op_env_rates occupies indices 15–38 as:
[op0_r0, op0_r1, op0_r2, op0_r3, op1_r0, ..., op1_r3, ..., op5_r0, ..., op5_r3]
─────── op 0 ────────── ─────── op 1 ─── ─────── op 5 ───
The same pattern applies to every per-operator field in the tables below.
| Index range | Field | Native range |
|---|---|---|
| 15–38 | op_env_rates (6 × 4) | 0–99 |
| 39–62 | op_env_levels (6 × 4) | 0–99 |
| 63–68 | op_output_level (6) | 0–99 |
| 69–74 | op_frequency_coarse (6) | 0–31 |
| 75–80 | op_frequency_fine (6) | 0–99 |
| 81–86 | op_detune (6) | 0–14 |
| 87–92 | op_velocity_sensitivity (6) | 0–7 |
| 93–98 | op_amp_mod_sensitivity (6) | 0–3 |
| 99–104 | op_rate_scaling (6) | 0–7 |
| 105–110 | op_breakpoint (6) | 0–99 |
| 111–116 | op_left_depth (6) | 0–99 |
| 117–122 | op_right_depth (6) | 0–99 |
| Index | Field | Max |
|---|---|---|
| 123 | osc_key_sync | 1 |
| 124 | lfo_sync | 1 |
| 125 | algorithm | 31 |
| 126 | lfo_wave | 5 |
| Index range | Field | Max |
|---|---|---|
| 127–132 | op_frequency_mode (6) | 1 |
| 133–138 | op_left_curve (6) | 3 |
| 139–144 | op_right_curve (6) | 3 |
from dexed import Patch, Preset
import numpy as np
# Patch → Preset
preset = Patch.load_bank("bank.syx")[0].to_preset()
# Round-trip
arr = preset.to_array() # (145,) float32
preset2 = Preset.from_array(arr)
# Sample: 123 floats from Beta, 22 ints from Categoricals
floats = beta_distribution.sample((123,))
integers = np.array([
np.random.randint(0, mx + 1)
for mx in Preset.GLOBAL_INT_MAXES # [1, 1, 31, 5]
] + [
np.random.randint(0, mx + 1)
for mx in Preset.OP_INT_MAXES * 6 # [1, 3, 3] × 6 ops
])
preset = Preset.from_array(np.concatenate([floats, integers]).astype(np.float32))
# Bulk storage: 100k presets ≈ 55 MB
bank = np.stack([p.to_array() for p in presets]) # (N, 145) float32
np.save("bank.npy", bank)
presets = [Preset.from_array(row) for row in np.load("bank.npy")]A network predicts global params and one bundle per operator. The spec is self-contained: no DX7 knowledge needed, just array shapes and integer maxes.
# Spec (class constants — no DX7 knowledge required)
Preset.GLOBAL_CONTINUOUS_SIZE # 15
Preset.GLOBAL_INT_MAXES # [1, 1, 31, 5] (osc_key_sync, lfo_sync, algorithm, lfo_wave)
Preset.OP_CONTINUOUS_SIZE # 18
Preset.OP_INT_MAXES # [1, 3, 3] (frequency_mode, left_curve, right_curve)
# Decompose a Preset into bundles
gc = preset.global_continuous() # (15,) float32 in [0, 1]
gi = preset.global_ints() # (4,) int32
oc = preset.op_continuous() # (6, 18) float32 in [0, 1] — row i = operator i
oi = preset.op_ints() # (6, 3) int32
# Reconstruct from bundles
preset = Preset.from_operator_bundles(gc, gi, oc, oi)A network in this style:
# Network predicts all four arrays; sample independently per array
global_cont = beta_dist.sample((Preset.GLOBAL_CONTINUOUS_SIZE,))
global_int = np.array([np.random.randint(0, mx + 1)
for mx in Preset.GLOBAL_INT_MAXES], dtype=np.int32)
op_cont = beta_dist.sample((6, Preset.OP_CONTINUOUS_SIZE))
op_int = np.array([[np.random.randint(0, mx + 1)
for mx in Preset.OP_INT_MAXES]
for _ in range(6)], dtype=np.int32)
preset = Preset.from_operator_bundles(global_cont, global_int, op_cont, op_int)
synth.load_preset(preset)The per-operator column ordering within op_continuous() is:
env_rates[0..3], env_levels[0..3], output_level, frequency_coarse, frequency_fine,
detune, velocity_sensitivity, amp_mod_sensitivity, rate_scaling, breakpoint, left_depth, right_depth.
All Presets share one treedef (no meta fields):
import jax
from dexed import Preset
_, treedef = jax.tree.flatten(Preset()) # universal
# JIT-traceable flat → Preset
leaves = Preset.array_to_leaves(flat_array) # 28 arrays/scalars
preset = jax.tree.unflatten(treedef, leaves)