Skip to content

Add local emulators for v1/v2/v3 hardware#142

Open
sastraxi wants to merge 29 commits intoTreeFallSound:pistomp-v3from
sastraxi:feat/v1v2-emulator
Open

Add local emulators for v1/v2/v3 hardware#142
sastraxi wants to merge 29 commits intoTreeFallSound:pistomp-v3from
sastraxi:feat/v1v2-emulator

Conversation

@sastraxi
Copy link
Copy Markdown
Contributor

@sastraxi sastraxi commented May 10, 2026

pi-stomp Emulator

Run locally against a MOD Desktop instance.

./run_emulator.sh [v1|v2|v3]   # default: v3

Requires MOD Desktop running at http://127.0.0.1:18181/.

Please note that a lot of this code and the PR description were assisted by Claude; normally I take more time cleaning up its code, but here I had a lighter touch because 99% of this code is out of the production path.

Hardware versions

Version Host flag Handler class
v3 (Pistomptre) emulator_v3 EmulatorModhandler + EmulatorHardwareV3
v2 (Pistompcore) emulator_v2 EmulatorModhandler + EmulatorHardwareV2
v1 (Pistomp) emulator_v1 EmulatorMod + EmulatorHardwareV1

Key classes

Class File Role
EmulatorHardwareBase emulator/hardware_base.py Shared base — LCD, footswitches, analog controls
EmulatorHardwareV1/V2/V3 emulator/hardware_v*.py Version-specific encoder/footswitch layout
EmulatorModhandler emulator/modhandler.py Modhandler subclass — owns VirtualAudiocard, StubWifiManager, StubRelay; drives render loop
EmulatorMod emulator/mod.py v1-only Mod subclass
EmulatorWindow emulator/window.py pygame window — scaled LCD left, clickable controls panel right
LcdPygame emulator/lcd_pygame.py LcdBase implementation — receives PIL frames, blits to pygame Surface
VirtualAudiocard emulator/stubs.py In-memory EQ/volume/bypass state; no ALSA
StubWifiManager emulator/stubs.py No-op wifi

Render loop

poll_controls runs every ~10 ms:

  1. EmulatorWindow.process_events() — drains pygame events, fires hardware callbacks
  2. super().poll_controls() — polls encoders/footswitches
  3. lcd.poll_updates() — flushes dirty PIL→pygame blit
  4. EmulatorWindow.render() — scales LCD surface and blits to screen

Notes

  • pygame has an unfixed circular import that specifically triggers in Python 3.14; by using the pygame._freetype C extension directly we can get around it
    *lilv is installed outside the venv, so there's a run_emulator.sh shim that uses pkg-config / Homebrew to find the prefix and exports DYLD_LIBRARY_PATH/PYTHONPATH for it
  • to avoid loading slightly-different system versions of fonts, emulator/__init__.py patches ImageFont.truetype to resolve bare names from fonts/
  • SPI bandwidth bottlenecking is emulated by time.sleep based on the number of pixels blitted

Images

image
image

sastraxi and others added 27 commits April 22, 2026 13:22
Account for blit elapsed time when computing SPI sleep deficit, and skip
sleeps below macOS's ~1 ms timer granularity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts:
#	pyproject.toml
#	uv.lock
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant