Files
README.md

osmose-preset-loader #

Bulk-install EaganMatrix presets (e.g. from Patchstorage) into an Expressive E Osmose, instead of dragging them into the Haken Editor one at a time.

Firmware 2.x: just convert them #

One-time setup — read the preset key out of your own Osmose Updater install:

python tools/extract_key.py --save

The key is Expressive E's and is not shipped with this project. The Updater has to decrypt presets to do its job, so it carries the key; the script finds it, verifies it by decrypting a real preset, and caches it to key.txt (gitignored — don't commit or publish it). Point it at the installed binary, not the downloaded installer, which is packed. Method documented in OSMOSE_2X_FORMAT_REFERENCE.md §12.

Then convert:

python mid2eepst.py --dir <patchstorage folder> --recursive --dedupe \
    --out <snapshot>/preset/ --template <snapshot>/preset/<any factory>.eepst

Then restore that snapshot with the Osmose Updater (Advanced Mode). The presets appear in the browser under real names and play. No dragging, no Haken Editor, no touching the device.

Where to get presets worth converting #

The obvious source is Patchstorage, but temper expectations: it holds 134 entries, one author wrote 101 of them, and a large share are deliberately minimal teaching examples ("Simple Preset Series", "SimpleFilter01"). It is a tutorial archive more than a sound library.

The far better source is already on your disk if you have the Haken Editor:

<editor>/Firmware/SysPresets/{Continuum,ContinuuMini,EaganMatrixModule,EaganMatrixMicro}
<editor>/Explorations/{Eagan,Duquesne,Kram}

These are Haken's own system presets for the other EaganMatrix instruments. They are plain .mid archives, so mid2eepst.py converts them directly. Deduplicated against the Osmose system set, they yield ~711 presets the Osmose does not ship.

Editor-version presets load fine on an older engine. A Haken Editor 10.73 preset using s_MacroHi/s_MacroLo/s_Form_Poke/s_Mat_Poke renders correctly on an Osmose running EaganMatrix 10.52 -- verified on hardware. Those stream ids are absent from the Osmose factory set only because Expressive E's presets do not use them, not because the engine lacks them; presets shipping with the 10.52 editor use them freely. Do not filter by stream vocabulary.

Two things that are genuinely not portable, and are easy to misread as version skew:

  • Processing patches make no sound. Names like Disto Cabinet or BitCrushUn colour an audio input the Continuum has and the Osmose does not. Silence here is the patch, not the engine.
  • Same name, different patch. Haken revises presets between releases, so a 10.73 Music Box Bells legitimately differs from the 10.52 one. A changed sound is not evidence of a failed load.

The .eepst preset format is documented in OSMOSE_2X_FORMAT_REFERENCE.md: AES-128-CTR with the IV in the last 16 bytes, wrapping a Protocol Buffers message whose field 13 carries the archive's MIDI stream verbatim. eepst.py reads and writes it; mid2eepst.py converts .mid archives into it.

Because the name is plaintext inside, renaming is a string edit — eepst.rename() on a snapshot renames as many presets as you like, offline, instead of thumbing each one in on the encoder.

Take a snapshot before restoring. Any factory preset can be restored byte-exact from factory_pack.zip inside the firmware package, so mistakes are cheap.

Older routes #

These predate the .eepst format being documented and are kept because they still work and need no key.

Firmware 1.x: the Advanced Mode export is a folder of plain .mid files plus group*.txt indexes. install fills every free slot directly in the export, which is then re-imported. Format learned from segfault16/osmose-usersettings.

Firmware 2.x without converting: make-groups turns a folder of .mid presets into Haken Editor "Preset Group" folders, and each group loads 16 presets into DSP user slots with a single drag-and-drop. Note that on 2.x the Osmose browser reads the mainboard, not DSP flash, so this gets sounds onto the EaganMatrix but not into the preset list — see the format reference §11.

make-groups (via Haken Editor) #

python osmose_bulk.py make-groups --presets <mid-folder> --out <folder>
    [--per-group 16] [--name "PS Group"] [--order-file list.txt]

Each output folder holds a group .txt (index,"file.mid" lines, CRLF — the editor's native Preset Group format per the Continuum User Guide sec. 8.7) plus the member .mid files. In the Haken Editor, drag the .txt onto the user preset slot the group should start at. Loading a group overwrites the slots it lands on — aim at empty ranges and snapshot your user data first.

install / status (firmware 1.x export) #

python osmose_bulk.py status  --state <exported-state-dir>
python osmose_bulk.py install --state <exported-state-dir> --presets <mid-dir>
                              [--order-file list.txt] [--dry-run]

Fills slots referencing the Empty.mid placeholder, rewrites the groupN.txt indexes byte-identically, and zips a backup of the untouched export first.

Talking to the device directly #

python osmose_probe.py names                    # read-only: DSP user slot names
python osmose_probe.py ask --task userToMidi    # read-only ccTask requests
python osmose_flash.py load  --port "MIDIOUT2 (Osmose)" --mid preset.mid
python osmose_flash.py name  --port "MIDIOUT2 (Osmose)" --text "New Name"
python osmose_flash.py store --port "MIDIOUT2 (Osmose)" --slot 10
python osmose_flash.py diff <before>/preset <after>/preset --expect "New Name"

osmose_probe.py only ever asks the device for information and whitelists the ccTask values documented as read-only, so a typo can't trigger a calibration or reboot. osmose_flash.py is the write side.

These reach the EaganMatrix DSP (Port 2), not the Osmose preset browser. Storing works and persists, but 2.x reads its presets from the mainboard, which never looks at DSP flash. See OSMOSE_2X_FORMAT_REFERENCE.md §11.

Read the Updater log, not the dialog #

The Updater reports the same "failed to upload user presets" whether one preset was rejected or every one was. Three separate runs here looked identical in the UI: one where 848 of 849 succeeded, one where nothing was wrong at all, and one where the operation never started. The log is the only signal worth acting on.

~/AppData/Roaming/OsmoseUpdater/logs/osmoseFirmwareUpdater.log   (Windows)

grep -E "process error|insert fail" osmoseFirmwareUpdater.log
log line meaning
Success to transfer file X installed
fails due to content already exists identical content already on the device; harmless
fails due to insert fail name collision -- another preset already uses that name
fails due to process error the device refused the file itself (see the oversized-expansion note below)

Two things that read as catastrophic and are not:

  • A tail that stops mid-line means the log is still being written, not that the Updater crashed. Counting successes from a truncated log undercounts them. Wait for the operation to finish before drawing conclusions.
  • insert fail on every preset means the content is already installed -- see the non-idempotency note below. It is what a redundant retry looks like, not a broken snapshot.

Verify against the device rather than the dialog: if the presets are in the browser and play, the restore worked regardless of what the UI said.

Gotchas that cost real time #

  • An archive .mid is not self-contained. It stops at archiveToFile and carries no name, no commit and no terminator. The Haken Editor follows every archive with s_Name, then ccBankH=catEdBuf; ccBankL=0; ccStore=0, then ccTask=curGloToFlash(8). Miss the ccStore and nothing is committed — the patch evaporates within seconds. Miss the ccTask=8 and the DSP sits parked in Archive Retrieve, completely silent, until you change presets on the panel. stream_mid sends the whole sequence now via haken.commit_archive().
  • MIDI auditioning does work once the sequence is right: the loaded patch holds indefinitely and is displaced only by selecting a preset on the panel. It overrides the sound without becoming a named preset — the panel still shows whatever was selected before.
  • Clear the Osmose screen before running the Updater. While a vendor SysEx 20 01 status message is displayed, snapshots silently produce an empty preset/ directory. Send f0 00 21 26 20 02 f7 first.
  • Don't bake names into archives. osmose_name.py inserts messages inside the region archiveEof verifies and uses the wrong sData carrier. Send s_Name as a separate stream after the retrieve closes instead.
  • The device drops characters it doesn't like, silently. All 635 factory preset names use only space & + - 0-9 a-z — not one uppercase letter. The Osmose does not fold case, it deletes: Acoustic Bass arrives as coustic ass. mid2eepst.sanitize_name() handles it.
  • User preset names must be unique, or the whole upload fails. A colliding name is rejected with Transfer of file <name>_<handle> fails due to insert fail in the Updater log, and the operation reports "failed to upload user presets" even though most files transferred. The handle makes a preset distinct; the name must additionally not collide. This is scoped to the user insert path -- the factory collection itself ships 11 duplicate name pairs and the device accepts them. mid2eepst now seeds its uniquing set from the output directory (existing_names), so a converted batch cannot collide with presets already in the snapshot.
  • Restore is additive, not a sync. It pushes what is missing and skips what is present ("content already exists"); it never deletes a preset absent from the snapshot. Repeated restores layer up. To actually replace the user bank, clear user presets on the device first (there is a clear user presets item on the Osmose itself) -- or use the Updater's overwrite all user presets mode rather than Restore User Data, which only overwrites duplicates.
  • A user restore is not idempotent. Never retry a successful one. Once a user preset named N is installed, pushing anything named N again is rejected. Re-run a restore that already succeeded and every preset fails with insert fail -- correctly, and the UI reports the same generic error as a real failure. If a run reports failure, read the log before retrying: the retry cannot succeed and will bury the original cause under hundreds of spurious rejections.
  • Some expansion presets can only be installed by Push Factory Collection. The eleven va * (virtual analog) presets decrypt to ~7.7-8.1 KB against a median of 2.7 KB, and pushing one through Restore User Data fails with process error. They arrive via Push Osmose Factory Collection instead. If a snapshot restore fails on exactly one oversized expansion 01 preset, drop it from preset/ and re-push the factory collection afterwards.
  • restore-user-data keys on the handle inside the file, not the filename. A byte-copy under a new filename is deduplicated and never appears. Mint a fresh handle (eepst.new_handle()) to add a genuinely new preset.

Notes #

  • Filenames become on-device preset names: lowercased and filtered to the device's character set, hyphens/underscores to spaces, duplicates get numeric suffixes, Empty.mid is reserved.
  • Source files are validated as MIDI (MThd header).
  • Python 3 standard library, plus mido for .mid parsing and cryptography for AES. tools/pe_xref.py and tools/extract_key.py also need capstone.
  • Preset .mid files are recordings of the editor-to-device MIDI stream, and a .eepst payload turns out to carry that same stream verbatim in protobuf field 13 — which is why conversion is a transcode rather than a synthesis.

If you fork or republish this #

The .eepst key is Expressive E's. This project ships the method for reading it out of your own Updater, never the key itself. Keep it that way: key.txt is gitignored, and if you rewrite history or export this repo, check that no earlier commit or scratch file carries the literal value.

Disclaimer #

Reverse-engineered formats; back up your user data (Osmose Updater Advanced Mode) before bulk operations. Use at your own risk.