Port DX7 import/export tools to OPSL 0.3 device JSON #1
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/opsl-0.3-device"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Scope
Tools/dx7-import/(importer, exporter, reclassify,__main__) importeda Python package,
opsl(from opsl import parse, serialize,from opsl.model import OPSLPreset), that has never existed anywhere this repocan reach — and its own files cross-imported each other as
from dx7.reader import ...even though nodx7package exists either (thisdirectory is literally named
dx7-import, not a valid Pythonidentifier). Both meant the tool could not run at all.
This PR:
opsl_device.py, a small, stdlib-only module that builds/reads/writes OPSL 0.3 "device" documents directly, replacing the dependency
on the nonexistent
opslpackage. It mirrors opsl-data'sscripts/opsl01_to_device.py(the converter that built the publisheddx7-corpus-v1corpus from this tool's old OPSL 0.1 output) sochavo's own output matches that corpus's shape:
device_id: "yamaha/dx7",role: "patch",kind: "hardware",protocol: "sysex_bulk", and the same 145-entry DX7 params table.importer.py/exporter.py/reclassify.py/__main__.pyto it, and to plain same-directory imports instead ofthe never-working
dx7.*path. Globs*.device.jsoninstead of*.opsl.schema/device.schema.json,pinned to
opsl-publictagv0.3.1, byte-identical — seeschema/VERSION.md) so tests can validate real output against it.file round-trips byte-identically (both documented in detail in
opsl_device.py's module docstring):being merged into the rate-scale byte (bits 3-6), which also pushed
every later global-param offset 6 bytes too far.
instead of the real 7-bit (0-99) range — harmless for opsl-data's
corpus (no preset there happened to use >63) but wrong for at least
one real factory bank used as a test fixture here (two voices use
96 and 99).
reader.py's single-voice (163-byte) SysExparsing path, and a latent downstream consequence in
__main__.py(the classifier's algorithm-extraction byte offset was hardcoded to
the old, wrong global-params offset).
LICENSE(chavo had none; Dexed's own source files arealready GPL-3.0-or-later per their headers).
Tools/dx7-import/__pycache__(accidentally committed in theinitial commit) and adds a
.gitignore.Did not touch: the C++/JUCE plugin source (
Source/) — nothing inthis port required it.
Testing
30
unittestcases, runnable with:All 30 pass locally. Summary by file:
tests/test_opsl_device.py— packed-voice codec round-tripsbyte-identically for every voice in a real DX7 factory SysEx bank
(
tests/fixtures/brass.syx, a standard brass/horn bulk dump); the145-entry params table's shape; and (skipped if
jsonschemaisn'tinstalled, otherwise exercised for real) JSON Schema validation of
built documents against the vendored schema.
tests/test_round_trip.py— the core acceptance claim:.syx->device documents ->
.syxreproduces the original 4104-byte filebyte-for-byte (
test_full_bank_without_dedup_is_byte_identical_to_source_file).tests/test_cli.py— drives the actual__main__.pyCLI dispatcher(
importthenexport) against a temp directory.tests/test_corpus_sample.py— compares a chavo-built documentagainst a real document from the published
dx7-corpus-v1corpus,asserting where they match (document shape, params table) and where
they're expected to differ (param values — different source patches
sharing a factory name; and the one param-domain divergence from the
depth-mask fix above).
Per the "prove the test can fail" requirement, several tests include an
explicit negative control before trusting the positive case: corrupting
one byte of a real voice and confirming the round-trip comparison now
(correctly) fails; corrupting a built document and confirming schema
validation now (correctly) rejects it.
Also manually smoke-tested
reclassify.py(not covered by a dedicatedtest file) against a real imported+categorized preset tree: KNN
dry-run and
--moveboth work, and moved files now get theircategory:<slug>tag rewritten to match their new folder (previouslythis tool left the old, now-wrong tag in place after a move — a small
latent inconsistency bug fixed as part of this port).
Impact Statement
"cannot run" (missing
opslpackage) to working end-to-end. Noexisting working behavior changes, since none of it previously ran.
importer.voice_to_preset(packed, name, source) -> OPSLPresetbecomesimporter.voice_to_device(packed, name, source) -> dict(an OPSL 0.3 document).exporter/reclassifyfunction signatures now take/return plain dicts instead of
OPSLPresetobjects. None of this was callable before (the importthat defined
OPSLPresetdidn't exist), so there is no live caller tobreak.
(
preset { schema: "0.1" ... }) to OPSL 0.3 JSON device documentsvalidated against the vendored schema.
opsl_device.py's params tablehas a wider domain (0-127, not 0-63) for
kbd-lev-scl-{l,r}-depththan opsl-data's published corpus uses forthe same two keys — a deliberate, documented fix (see above); anything
reading those two domains across both sources should not assume they
match.
*.opslto*.device.json. Nothing in the repo currently consumes the old*.opsloutput (no such files exist in chavo; the only consumer wasopsl-data's separate converter, which is a different repo and not
changed here).
this repo (Source/ C++ plugin code, untouched, does not call it).
op{N}.*/ global DX7 param naming convention isunchanged, so any external document keyed by those names still lines
up with
opsl_device.dx7_params_table()'s keys.before this change (the referenced
opslpackage does not exist), sothere is no working prior behavior to remain compatible with.
Docs
Tools/dx7-import/README.md(new): how to run the tool and its tests,and the format change.
Tools/dx7-import/opsl_device.py: extensive module docstring coveringthe document shape, the two codec bug fixes, and why fresh imports
carry
lineage/x-dx7importinstead of opsl-data'sx-opsl01carry-over block.
Tools/dx7-import/schema/VERSION.md(new): schema provenance/pinning.Open decisions for review
opsl_device.pyuses a 0-127 domain for
kbd-lev-scl-{l,r}-depth; the publisheddx7-corpus-v1corpus uses 0-63 for the same two keys (opsl-data'sscripts/opsl01_to_device.pyis not changed in this PR — that's aseparate repo). Flagging in case opsl-data's maintainers want the
same fix, or want to re-derive/re-publish the corpus with it.
.syximports now carry alineage+x-dx7importextensionblock (voice sha256, 1-indexed algorithm, and the exact raw 10 name
bytes losslessly latin-1 encoded) instead of an
x-opsl01block,since there's no OPSL 0.1 document in this pipeline to carry over.
Documented in
opsl_device.py's module docstring; flagging here incase a different shape is preferred for interop with other OPSL 0.3
producers.
30 unittest cases, runnable with: python3 -m unittest discover -s Tools/dx7-import -p "test_*.py" - test_opsl_device.py: packed-voice codec unit tests against every voice in a real DX7 factory SysEx bank (tests/fixtures/brass.syx), the 145-entry params table's shape, and (when jsonschema is installed) real JSON Schema validation of built documents. - test_round_trip.py: the core acceptance claim -- a real .syx file converted to device documents and back to .syx reproduces the original file byte-for-byte. - test_cli.py: drives the actual __main__.py import/export subcommands against a temp directory, proving the tool runs end to end (it could not run at all before this port). - test_corpus_sample.py: compares a chavo-built document against a real document from the published dx7-corpus-v1 corpus (opsl-data), copied into tests/fixtures/corpus_brass_1.device.json, documenting where they match (structure, params table) and where they're expected to differ (param values, and the one param-domain divergence from opsl_device.py's depth-mask fix). Several tests include a deliberate negative control (corrupt a byte or a field, confirm the test now fails) before trusting the positive case, per the task's "prove the test can fail" requirement. tests/fixtures/brass.syx is a standard Yamaha DX7 factory-style SysEx bulk dump (brass/horn patches); chosen because two of its voices use a keyboard-level-scaling depth value above 63, which is exactly what exposed the depth-mask bug opsl_device.py fixes. README.md documents how to run the tool and the tests, and the document format change (*.opsl -> *.device.json).