A web-based simulator for the Critter & Guitari EYESY video synthesizer that runs actual Python scripts locally.
Version 1.0 | Created by Stuart Frederich-Smith | signalfunctionset.com
- Flask server with WebSocket (flask-socketio) for real-time communication
- pygame compatibility shim that renders to PIL images
- Eyesy script executor that runs actual main.py files
- Real-time WebSocket streaming of canvas frames at 30 FPS
- Web UI with 5 knob controls and mode selector
- File browser for loading external Python files
- Full Eyesy hardware API compatibility
- Multiple audio types: Silence, Sine Wave, White Noise, Beat/Kick, Audio File
- Audio level and frequency controls
- "Play in browser" toggle to hear audio
- Audio file upload and playback with Web Audio API
- Real-time audio data streaming to backend via WebSocket
- Internet radio streaming with preset stations (SomaFM)
- Audio analyser for waveform extraction
The simulator includes a pygame compatibility layer supporting:
pygame.draw.circle(),rect(),line(),polygon(),ellipse(),arc()pygame.draw.lines()for connected line segmentsSurface.fill(),get_size(),blit(),get_width(),get_height(),get_rect()Surface.convert_alpha()for alpha channel supportpygame.font.Fontandpygame.font.SysFontfor text renderingRectclass with position and size attributes
- Clean, modern interface with League Spartan font
- Collapsible Controls panel
- Preview panel with 16:9 aspect ratio maintained
- Preview size indicator showing actual canvas dimensions
- Auto-apply audio settings when sliders change during playback
- Play/Pause button for audio simulation control
- About and Help slide-in panels with documentation
-
Install dependencies:
cd eyesy_sim python3 -m venv venv source venv/bin/activate # or `venv\Scripts\activate` on Windows pip install -r requirements.txt
-
Run the simulator:
source venv/bin/activate python backend/app.py -
Open in browser:
http://localhost:5001
- Select a mode from the dropdown
- Click "Load Mode" to load the Python script
- Click "Start" to begin rendering
- Adjust the 5 knobs to control the visuals in real-time
- Configure audio using the Audio controls section
- Click the play button next to "Audio" to start audio simulation
- Click "Stop" to stop rendering
- S-Simple-Circle: Basic circle controlled by knobs (position, size, color)
- S-Spiral: Animated spiral with color cycling
- S-Scope: Audio waveform visualization (oscilloscope style)
- S-String-Vibration: String vibration visualization responding to audio
- S-Living-Grid: Dynamic grid pattern
- T-Flash: Trigger mode that flashes on audio triggers or when knob 5 > 0.8
The simulator supports the full Eyesy hardware API:
# Always use etc.knob1-5 for hardware compatibility
x = int(etc.knob1 * 1280)
color_intensity = etc.knob4 * 255etc.audio_in # Mono audio buffer (100 samples)
etc.audio_left # Left channel
etc.audio_right # Right channel
etc.audio_peak # Peak audio level (0.0 to 1.0)
etc.audio_trig # Audio trigger boolean
etc.trig # Alias for audio_trigscreen # pygame Surface object (1280x720)
etc.xres # Screen width (1280)
etc.yres # Screen height (720)etc.color_picker(value) # Returns RGB tuple for value 0.0-1.0
etc.color_picker_bg(value) # Background color picker
etc.color_picker_fg(value) # Foreground color pickeretc.mode # Current mode name
etc.bg_color # Current background color
etc.fg_color # Current foreground color
etc.auto_clear # Whether screen auto-clears between frames
etc.midi_note_new # True when new MIDI note received
etc.midi_note # Last MIDI note number (0-127)
etc.midi_velocity # Last MIDI velocity (0-127)Create a new directory in modes/ with a main.py file:
import pygame # Required for hardware compatibility
def setup(screen, etc):
"""Called once when mode loads"""
pass
def draw(screen, etc):
"""Called every frame"""
screen.fill((0, 0, 0)) # Clear screen
# Use etc.knob values (hardware compatible)
x = int(etc.knob1 * 1280)
y = int(etc.knob2 * 720)
radius = int(etc.knob3 * 100) + 10
color = etc.color_picker(etc.knob4)
# Respond to audio
if etc.audio_trig:
radius *= 2
# Draw with pygame
pygame.draw.circle(screen, color, (x, y), radius)Always use etc.knob1 through etc.knob5 instead of bare globals:
# CORRECT - works on real Eyesy hardware AND simulator
x = int(etc.knob1 * 1280)
# WRONG - only works in simulator, fails on real hardware
x = int(knob1 * 1280)Always import pygame at the top of your mode:
import pygame # Required for real hardwareeyesy_sim/
├── backend/
│ ├── app.py # Flask app with WebSocket (port 5001)
│ ├── eyesy_engine.py # Core Eyesy execution engine
│ ├── pygame_shim.py # pygame compatibility layer (PIL-based)
│ └── audio_processor.py # Audio input simulation
├── frontend/
│ ├── static/
│ │ ├── css/style.css # UI styling
│ │ ├── js/app.js # WebSocket client, audio playback
│ │ └── audio/ # Built-in audio samples
│ └── templates/
│ └── index.html # Main UI
├── modes/
│ ├── S-Simple-Circle/main.py
│ ├── S-Spiral/main.py
│ ├── S-Scope/main.py
│ ├── S-String-Vibration/main.py
│ ├── S-Living-Grid/main.py
│ └── T-Flash/main.py
├── config.py # Server configuration
├── requirements.txt
├── README.md
└── CLAUDE.md # Development documentation
Copyright 2025 Stuart Frederich-Smith. All rights reserved.
