pyvmancer
Python library and CLI to automate the LZX Industries Videomancer over USB.
Two control paths, usable together or independently:
| Path | Transport | Covers |
|---|---|---|
| USB MIDI | ALSA rawmidi or any mido backend |
12 parameters (7/14-bit CC), preset recall, note triggers, clock/transport |
| USB serial | /dev/ttyACM* or libusb |
programs, presets, modulation operators, video, settings, filesystem |
Install
pip install pyvmancer # MIDI only
pip install "pyvmancer[all]" # adds serial and libusb transports
On Linux the python-rtmidi dependency links libasound.so.2:
sudo apt-get install libasound2t64
Use
import pyvmancer as vm
d = vm.Videomancer.open()
d.load_program("Isotherm")
d.set_named("Posterize", 5) # native units: Posterize is 0..7
d.set_named("Mix", 80) # Mix is 0..100
d.set_source("Contrast", "free lfo")
d.set_param(1, 0.75) # or address slots directly, 0.0..1.0
d.set_param(7, True) # toggles take bools
d.trigger(4) # note-trigger a modulation operator
d.set_bpm(128)
d.play()
d.close()
Device knowledge the library encodes, rather than leaving to each caller:
d.park() # manual values to a reference, so a CC is absolute
p = d.parameter("Posterize") # p.role, p.steps, p.sample_values()
d.program_manifest().get("combing").description
d.file_hash("sd:/programs/lzx/combing.vmprog")
d.video_state().source_locked # per-input lock, not the genlock flag
d.resync() # bounce the timing to re-init the output raster
MIDI only, no serial link needed:
with vm.open_midi(channel=1) as m:
m.set_param(1, 0.5)
m.select_preset(3)
m.run_clock(120, beats=8)
CLI
vmancer devices # discover attached units and their device nodes
vmancer operators # list the 25 modulation operators
vmancer set 1=0.5 7=on # set parameters over MIDI
vmancer preset 3 # recall a preset
vmancer programs # list installed FPGA programs
vmancer load Isotherm # load a program
vmancer manifest # SD program library metadata
vmancer hash sd:/programs/x.vmprog
vmancer resync # recover an output that stopped passing frames
vmancer shell modulation status # run any raw shell command
Firmware upgrade
LZX publishes firmware as GitHub releases on
lzxindustries/videomancer-firmware,
tagged videomancer/<version>. pyvmancer resolves a release, downloads its
UF2, validates it, and flashes it through the RP2040 bootloader.
vmancer firmware list # published releases, newest first
vmancer firmware status # running version vs the newest release
vmancer firmware download 1.0.0-rc.46 # fetch and validate a UF2, no flashing
vmancer firmware upgrade # dry run: shows what would be flashed
vmancer firmware upgrade --yes # flash the latest release
vmancer firmware upgrade 1.0.0-rc.40 --yes --force # pin a version, or downgrade
upgrade is a dry run unless --yes is given. With --yes it issues
reboot bootloader, waits for the UF2 volume to appear (mounting it with
udisksctl if nothing automounted it), copies the image, then waits for the
device to re-enumerate and reports the version it comes back on. Pass
--manual when the unit was put into bootloader mode by hand (hold BOOT while
powering on), which is the recovery path when the serial link is unusable, and
--volume when the bootloader is already mounted somewhere.
Allow minutes, not seconds: the image only reaches the device when the copy is flushed, and a 24 MB UF2 takes a while over a FAT mount. The device reboots itself once the last block lands, and the version it reports afterwards is the proof the flash took.
import pyvmancer as vm
vm.resolve_release("latest").version # newest published version
image = vm.inspect_uf2("videomancer-1.0.0-rc.46.uf2")
image.blocks, image.families # validated before anything is written
with vm.open_shell() as shell:
vm.upgrade("latest", shell=shell, report=print)
Every Videomancer release to date is flagged as a prerelease on GitHub, so
latest includes prereleases; pass --stable / stable_only=True to refuse
them. Set GITHUB_TOKEN if you hit the unauthenticated API rate limit.
Program library
The FPGA programs ship separately from the firmware, tagged programs/<version>
on the same repository. pyvmancer resolves a release, verifies the archive
against its published checksums.sha256, and copies it onto the SD card.
vmancer library list # published library releases
vmancer library status # newest release vs what is on the card
vmancer library download 1.0.3 # fetch and verify an archive, no install
vmancer library install # dry run: shows what would be installed
vmancer library install --yes # install the latest release
vmancer library install --yes --force # re-upload files already present
import pyvmancer as vm
with vm.open_shell() as shell:
vm.install("latest", shell=shell, report=print)
Files already on the card at the archive's size are skipped, so an interrupted
run resumes by re-running it. The upload uses fs put, the shell's raw
streaming path: about 157 kB/s, so a full 14 MB library takes about 90 seconds.
The base64 fs write path manages roughly 3 kB/s and is only for small files.
Restart the device after installing. The program index is built at boot, so newly installed programs are not listed — and cannot be loaded — until the unit is power cycled.
Permissions
MIDI needs read/write on /dev/snd/midiC*D* (usually granted by a desktop ACL);
serial needs the dialout group or an ACL on /dev/ttyACM*. See
docs/permissions.md.
Docs
- docs/protocol.md — MIDI implementation and the serial command set
- docs/permissions.md — device node access, containers, udev
- docs/firmware-notes.md — observed firmware behaviour and quirks
- CHANGELOG.md — release history
Status
Verified against a Videomancer running firmware 1.0.0-rc.37 and 1.0.0-rc.40.
Not affiliated with LZX Industries.
License
Apache-2.0
Metadata
Release files for pyvmancer 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyvmancer-0.3.0.tar.gz | 112.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyvmancer-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 171.9 kB
Release files / pyvmancer-0.3.0.tar.gz
| Download URL | pyvmancer-0.3.0.tar.gz |
|---|---|
| Size | 112.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fb3b634640648e547a513b57e8979aa1f3f0d56cb64555d88b9d34d0079ae716
|
|
BLAKE2b-256 checksum How to use checksums |
8120d7015e8ff219e1243295a7f05412f8a59907a79e090d463dc05c77404b2c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 8, 2026.
Transparency logRelease files / pyvmancer-0.3.0-py3-none-any.whl
| Download URL | pyvmancer-0.3.0-py3-none-any.whl |
|---|---|
| Size | 59.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5c1fa38f575f76e8ab889617b3361dbf762f9eed0c87ff621a4cb7db99ed16e1
|
|
BLAKE2b-256 checksum How to use checksums |
3d3f8781c80dfd6900ac504df6286c8c6371673a59c390bee58c359835b88c51
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 8, 2026.
Transparency log