# Psychodeli+ WebGL

[![Chapter 1 Graphic](images/chapter1_graphic.png)](https://psychodeli.com)

## Chapter 1: The Long Arc

Psychodeli+ represents a 30-year journey of digital exploration, evolving from early 'After Dark' screensaver fascinations to a complex WebGL-based performance system. By engineering visual flow through bio-inspired attention mechanisms and cognitive load management, the project bridges the gap between human perception and computational art. This synthesized history, curated with the help of LangChain, reflects a continuous effort to harness pre-attentive filtering for immersive, real-time visualization.

### The Timeline

- **2001**: **Uzilla** (MS Human Factors, Clemson) — Award-winning research into personalized search and attention; early web navigation visualization.
- **2005**: **The Best Hack** (eBay/Hasbro) — Scaling UX and performance for millions of users.
- **2007**: **Scrutinizer** — Original Foveal Vision simulator for accessibility and attention research.
- **2015**: **SessionBoard** — Temporal data visualization and history tracking (Patent [WO2015073666A1](https://patents.google.com/patent/WO2015073666A1/en)).
- **2024**: **Algorithmic Exploration** — Transitioning Psychodeli to a modular, AI-driven architecture with pattern memory.
- **2025**: **Psychodeli+** — Full release of the immersive, audio-reactive performance instrument.

A WebGL-based visualization performance system that uses the Lorenz attractor to create mesmerizing, audio-reactive patterns. Inspired by classic psychedelic visualizers and DJ tools, it allows for expressive, real-time visual performances.

## Features

- 11 different visual presets with unique patterns and color schemes
- Real-time audio reactivity with microphone input for live music visualization
- Rich, multi-modal interaction system for expressive performance control
- Embodied control — touch gestures, camera & body tracking, and **device motion** (iOS): move your phone and the visuals respond *and learn how you move*
- **Motion Resonance** — a live, source-neutral synchronization score credits the strongest proof that you are moving or playing in time with the music (gyro, touch, keyboard, SpaceMouse, head, or body)
- Moderate default animation speed with Turbo Mode option for faster motion
- Advanced visual effects with real-time intensity adjustments
- DJ-like control system with continuous parameter manipulation
- Optimized for performance on modern browsers
- Performance-oriented keyboard command system for dramatic visual transformations
- **Instant Synchronized Recording**: Shift-click to instantly record performances perfectly synchronized to the start of your local audio tracks.
- **Play your own files (desktop app)**: browse a faceted Library, label music and play a label directly. The current `electron` development tree adds named queue blocks, Party shuffle, a Gentle–Blend–Bold Mix intensity control, continuous handoffs, annotated scrubbing and attenuation-only volume normalization. Saved-playlist authoring is temporarily hidden while labels and sets take priority; see [current delivery and remaining work](docs/LOCAL_MEDIA_ROADMAP.md#current-delivery-and-next-work--2026-09-12) and [architecture](docs/LOCAL_MEDIA_ARCHITECTURE.md).

## ⚠️ Hardware & VR Note

**VR Support Deprioritized:** While VR exploration was initially planned (and `VR_QUICKSTART.md` exists), native WebXR support is currently deprioritized. Current standalone VR hardware (e.g., Quest 3) lacks the GPU performance required to render Psychodeli's complex Lorenz-attractor fractals at the high framerates required for a comfortable VR experience. 

For the best experience, use a desktop machine with a dedicated GPU.

## Structure

The project has been organized into a modular structure:

```
├── index.html              # Main HTML file
├── css/
│   └── styles.css          # All CSS styles
├── shaders/
│   ├── vertex.glsl         # Vertex shader
│   └── fragment.glsl       # Fragment shader
├── js/
│   ├── lib/
│   │   └── audioAnalyzer.js # Audio processing
│   ├── config/
│   │   ├── constants.js    # Global constants
│   │   ├── presets.js      # Visual presets
│   │   └── audio-mapping.js # Audio mapping configuration
│   ├── webgl-utils.js      # WebGL initialization and rendering (HTTP version)
│   ├── webgl-utils-file.js # WebGL initialization (file:// version)
│   ├── loader.js           # Auto-detects protocol and loads appropriate WebGL utils
│   ├── lorenz-attractor.js # Lorenz attractor simulation
│   ├── palette-manager.js  # Color palette generation
│   ├── audio-reactivity.js # Audio analysis and reactivity
│   ├── ui-controls.js      # User interface and controls
│   ├── command-registry.js # Keyboard command system
│   └── app.js              # Main application logic
```

## How to Run

The application is designed to work both directly from the file system and when served via a web server. Our loader will automatically detect which method you're using and configure the application appropriately.

### Method 1: Direct from File System

You can run the visualization directly by opening the `index.html` file in a modern web browser. This version uses inline shaders in the HTML file and `webgl-utils-file.js` to avoid the need for a web server.

Simply double-click `index.html` or drag it into your browser window.

### Method 2: Via HTTP Server (Recommended for Development)

For best performance and to use the original external shader files:

1. Use one of the included server scripts:
   - On macOS/Linux: Run `./start-server.sh` from a terminal
   - On Windows: Double-click `start-server.bat`
   
   These scripts will automatically detect which server you have available (Python, Node.js, or PHP) and start it for you.

2. Alternatively, run a server manually:
   - Python: `python -m http.server 8000`
   - Node.js: `npx http-server -p 8000`
   - PHP: `php -S localhost:8000`

3. Open `http://localhost:8000` in your browser

The application will automatically detect that it's running via HTTP and use the appropriate configuration.

## Controls

### Basic Controls
- Click anywhere to open the settings panel
- Press keys 1-9 to switch between visual presets
- Use the mouse wheel to adjust animation speed
- Mouse drag to adjust frequencies (horizontal) and amplitudes (vertical)
- Press +/- keys to add/remove nodes
- Press Space to open and Escape to close the control panel

### Touch & Mobile

One finger in each of three thumb-reachable zones is a **probe** — drag to shape the figure. The rule is **vertical drives the zone's control, horizontal shifts color**, so a diagonal swipe does both:

| Zone | Vertical drag | Horizontal drag |
|------|---------------|-----------------|
| Left third | Spiral pitch | Hue shift |
| Center third | Speed (tempo) | Hue shift |
| Right third | Twist / flow | Hue shift |

Drags are velocity-adaptive (slow = fine, fast = covers ground but capped), bounded (no wrap), and tick haptically at meaningful values — the same detents fire on the Speed dial slider and the Arrow-key speed ladder. Haptics are cross-platform (Android, plus the iOS Taptic Engine where `navigator.vibrate` can't reach) via `window.PdeliHaptics`; see [docs/HAPTICS.md](docs/HAPTICS.md). Other touch: **tap** = new palette · **double-tap** = next preset · **long-press** = help · **pinch** (two fingers) = zoom in / out · **twist** (two fingers, or circle one finger) = spin the whole canvas — flick it and it coasts to a rest · **corners** = layout / nodes / kaleidoscope / menu. Full design: [docs/TOUCH_CONTROLS.md](docs/TOUCH_CONTROLS.md).

### Keyboard Commands

Press **H** to view a complete list of keyboard commands. The active commands are also displayed in the control panel.

| Key | Command | Description |
|-----|---------|-------------|
| `0` | Reset Visualization | Reset all effects to default configuration |
| `|` | Psychodeli Splash | Toggle the palette-reactive promotion with download QR |
| `.` | Clear All Modifiers | Remove all active effects while keeping current parameters |
| `c` | Center Node | Pin a node to the center of the screen |
| `s` | Symmetrical Forces | Create mirror symmetry between nodes |
| `p` | Spiral Motion | Make knots move in spiral patterns (Press to cycle Low/Mid/Off) |
| `Shift+p` | Spiral Intensity | Adjust the intensity of spiral motion |
| `m` | Mirror Mode | Mirror visualization across horizontal axis |
| `n` | 4-Corner Symmetry | Create 4-way symmetry across the corners |
| `e` | Echo Burst | Creates a dramatic trail effect that gradually fades away |
| `t` | Turbo Mode | Doubles animation speed for faster motion |
| `z` | Speed Control | Hold Z and move mouse up/down or use scroll wheel to adjust speed |
| `u` | Pulse Mode | Make knot weights pulsate rhythmically (Press to cycle Low/Mid/High/Off) |
| `Shift+u` | Pulse Intensity | Adjust the intensity of the pulsating effect |
| `a` | Orbit Mode | Makes knots orbit around the center point (Press to cycle direction/off) |
| `^` | Hand Tracking | Toggle hand tracking (Shift+6). Full rate alone, overlay with head/body |
| `&` | Head Tracking | Toggle face/head tracking (Shift+7). Up to 4 people each get an embossed face in the visuals; a room moving together drives it harder. Combine with `*` for auto-switch |
| `*` | Body Tracking | Toggle full-body tracking (Shift+8). Combine with `&` for auto-switch |
| `(` | Camera On/Off | Toggle all camera tracking on/off (Shift+9). Remembers last config |
| `q` | Debug Panel | Toggle the debug information panel |
| `` ` `` | Debug Mode | Toggle verbose console logging for development |
| `h` | Help | Display the keyboard command reference |
| `Escape` | Close / clear | Dismiss the current menu or panel first; with UI closed, clear visual effects. Library search clears its text before closing. |
| `⌘L` / `Ctrl+L` | Full Library (Electron) | Open the full music Library directly, including from the mini player. |

You can combine multiple effects by pressing different keys sequentially. Press the same key again to toggle an effect off or cycle through its intensity levels.

### Debugging Features

Psychodeli+ includes several debugging features to assist with development:

1. **Debug Panel**: Press `q` to show a panel with current state information including:
   - FPS counter
   - Active knot count
   - Audio status
   - Available functions
   - Active commands

2. **Debug Mode**: Press the backtick key (`` ` ``) to enable verbose console logging for:
   - Function replacements and modifications
   - Command execution and state changes
   - Detailed function chain execution

3. **Command Help**: Press `h` to view a complete list of available commands

4. **Clear All Commands**: Press `.` to quickly remove all active effects

These tools are particularly helpful when developing new visual effects or diagnosing issues with the command system.

### Performance Controls

Psychodeli+ is designed to be used as a visual performance instrument, similar to how DJs manipulate audio. The multi-layered control system allows for expressive, real-time manipulation:

1. **Activation Commands**: Single key presses to activate/deactivate visual effects
   - Press a key once to toggle an effect on
   - Press again (after releasing) to toggle it off
   - Holding a key won't accidentally toggle the effect - perfect for performance
2. **Continuous Adjustments**: Multiple intuitive ways to control parameters:
   - **Direct Hold Method**: Hold any adjustable key (P, R, U, Z) to enter adjustment mode
   - **Shift+Key Method**: For traditional keyboard layouts, use Shift+key combinations
   - **Dual Input Control**: Once in adjustment mode, use either:
     - Mouse movement (up/down) for smooth, expressive adjustment
     - Scroll wheel for precise, incremental control
3. **Trigger Actions**: Some keys perform immediate actions when pressed:
   - Press R repeatedly to rotate through color palettes (like a DJ scratching colors)
4. **Direct Manipulation**: Click and drag on the visualization to shape the patterns
5. **Audio Reactivity**: Microphone input creates real-time connections between sound and visuals
6. **Control Panel**: GUI for precision adjustments and monitoring active effects

This multi-modal approach enables a scratching-like control system where visual parameters can be rapidly modified during a performance, creating dynamic, responsive visuals that complement music and performance art.

### Interactive Parameter Adjustment

Every command with adjustable parameters provides continuous control:

1. **Enter Adjustment Mode** through either:
   - Hold the key (e.g., Z, P, U, R) to activate the adjustment mode directly
   - Hold Shift + the key for the same effect
2. **While in Adjustment Mode**:
   - Move your mouse up/down to adjust the parameter with wide range control
   - OR use the scroll wheel for fine, precise incremental adjustment
3. **Release the key** to keep the current value and exit adjustment mode

A visual slider appears during adjustment, showing the current value and parameter range for immediate feedback.

### Enhanced Key Interaction

Psychodeli+ uses an enhanced keyboard interaction model designed specifically for live performance:

1. **Press-and-Hold Safety**: When activating toggle effects (like Mirror Mode or Center Node):
   - The first key press activates the effect
   - The system tracks that the key is being held down
   - Holding the key doesn't accidentally toggle the effect off
   - You must release the key and press it again to deactivate the effect

2. **Dual Input Methods**: For continuous parameters:
   - Mouse movement provides smooth, expressive control with wide range
   - Scroll wheel offers precise, incremental adjustments
   - Both input methods work simultaneously while holding the key

3. **Multiple Activation States**: Commands can have multiple states:
   - Toggle on/off (like Mirror Mode)
   - Continuous adjustment (like Speed Control)
   - Trigger actions (like parameter reset)

4. **Smart Event Management**: When in adjustment mode:
   - Default scroll wheel behavior (frequency adjustment) is temporarily suspended
   - System captures and redirects mouse wheel events to the active parameter
   - Normal scroll behavior automatically resumes when adjustment completes
   - Prevents conflict between different control systems
   - Events are captured at the document level to ensure proper isolation
   - Long clicks and mouse movements won't accidentally open the control panel
   - Multi-layered event interception ensures consistent behavior across browsers

This interaction model prevents accidental toggling during performances and allows for more deliberate control, making it ideal for live visual performances where stability and intent are crucial.

## Audio Reactivity

Enable audio input to make the visualization react to sound:

1. Click "Enable Audio Input" in the control panel
2. Allow microphone access when prompted
3. Bass frequencies affect animation speed
4. Mid frequencies affect frequency parameters
5. High frequencies affect amplitude parameters

## Gaze & Algorithmic Exploration

Psychodeli+ features an intelligent "Co-Pilot" system called **Algorithmic Exploration (AE)** that can perform the visuals for you, learning from your reactions.

### Gaze Control (Experimental)
Gaze control requires **Head Mode** (`&`, Shift+7) to be active — it uses the same camera feed to track your eyes alongside head movement. Once head tracking is running, these gestures work automatically:

| Gesture | Meaning | Action |
|---------|---------|--------|
| **Right Wink** 😉 | "I like this!" | **Sustain & Learn**: Extends the current effect duration and teaches the AI to prefer this vibration. |
| **Left Wink** 👁️ | "Next!" | **Evolve**: Immediately triggers a transition to a new visual phase and avoids similar patterns for a while. |
| **Rolling Blink** 🙄 | "Surprise Me!" | **Jukebox Shuffle**: Trigger a "Rolling Blink" (close Left then Right eye quickly, <150ms apart) to instantly shuffle to a random Jukebox level. |

### How It Learns
The system uses a reinforcement learning loop:
1.  **Motion Mapping**: If you move to the beat (head bobs/body sway) while winking **Right**, the system learns that *this* motion should trigger *this* visual effect more often.
2.  **Pattern Memory**: It remembers "Significant Moments" (Right Winks) and adds "Bad Vibes" (Left Winks) to an avoidance list.

👉 **[Read the Full User Guide](docs/ALGORITHMIC_EXPLORATION_USER_GUIDE.md)** for detailed strategies and tips.


## Camera & Motion Controls

Psychodeli+ offers two primary camera-based control modes plus a hand overlay, letting you influence the visuals with your body movement — and on the **iOS app**, simply **moving your phone** drives them too, with no camera or mic.

### Move Your Phone (iOS app)

The iOS app **feels how you move the phone** — no camera, no microphone, no setup, no permission prompt. Move or sway it and the visuals come alive (more energy, more motion); hold it still and they settle back.

And it **learns**. Move a certain way during a moment you love, and Psychodeli starts bringing that look back when you move that way again — your motion becomes part of the visual's memory, not a fixed knob. The more you play, the more it associates *how you move* with *what you see*. Just pick up the phone and move.

Open **Live EQ** to see **Motion Resonance**, Psychodeli's live synchronization
score. It rises only when the system can prove that your input is locking to the
music. Its own bar uses the live Energy palette's complementary hue, so Energy
and synchronization remain separate measurements. Live EQ leads with the universal relationship—for example
`you ↔ music · phone movement`—and names the strongest current input only as
supporting evidence. The score is shared across every input method; separate
device scores never compete or stack.

The gaze-aware gyro experiment is deliberately a different layer: looking at the
screen can give phone movement more visual weight, but attention alone never earns
Resonance. The score still requires actual synchronization with the music.

### Active Modes

| Mode | Key | Type | Best For | Description |
|------|-----|------|----------|-------------|
| **Hand** | `^` (Shift+6) | Primary or Overlay | Any | Hand and finger tracking. Full frame rate when alone, ~3Hz overlay alongside head/body. Pinch, spread, fist to control visuals. |
| **Head** | `&` (Shift+7) | Primary | Sitting | Uses head bobbing/nodding to drive rhythm. Great for desk use. |
| **Body** | `*` (Shift+8) | Primary | Standing | Uses full arm movements to "conduct" the visuals. Great for standing performance. |
| **On/Off** | `(` (Shift+9) | Toggle | — | Toggle all camera tracking on/off. Remembers last configuration. |

### Head appearance and Self performance

In the Live EQ panel, the **Head** pill's main area toggles tracking and its
chevron chooses how the tracked face appears: Motion, Face, Self, Mask, or
Eyes. Motion and Face are the free listening views; Self, Mask, and Eyes are
part of Touch where the platform uses entitlements.

**Self** turns the living node field into your face and answers complete
gestures with short connected phrases:

| Gesture | Visual response |
|---|---|
| Open/close mouth | four-node mouth arc follows continuously, then recoils |
| Raise brow | the corresponding brow lifts; the face answers with a crown arc |
| Lean in/out | the constellation gains or releases depth |
| Nod | direction-aware bounce after the nod completes |
| Shake | three-lobe sway after a real two-sided shake |
| Mouth wide + brows up | full-face expansion and release |

Ordinary head rotation still follows continuously. Gesture punctuation is
interruptible, so changing expression mid-phrase retargets the live motion
instead of snapping. Technical architecture and the Camerastein validation
workflow: [Face Performance Architecture](docs/FACE_PERFORMANCE_ARCHITECTURE.md).

### Auto-Switching vs. Manual Permission

The system uses a **Permission-based** logic for camera control:

- **Head Permission (`&`)**: Toggles face tracking.
- **Body Permission (`*`)**: Toggles body tracking.
- **Hand (`^`)**: Toggles hand tracking. Runs as primary (full rate) alone, or overlay alongside head/body.
- **On/Off (`(`)**: Toggles all tracking. Restores previous config when turned back on.

**Behavior:**
- **Both ON**: Auto-Switching is active (System chooses based on activity).
- **Only Head ON**: Locked to Head Mode.
- **Only Body ON**: Locked to Body Mode.
- **Both OFF**: Camera tracking disabled.
- **Hand Overlay**: Can be toggled ON/OFF independently of the above.

### Hand Gestures (Hand Overlay)

| Gesture | Action |
|---------|--------|
| **Pinch** | Thumb-to-index pinch zooms the fractal (scales `postScale`). |
| **Finger Spread** | Spreading fingers adjusts spiral pitch. |
| **Palm Rotation** | Rotating your palm twists the spiral. |
| **Hand Height** | Raising/lowering your hand modulates amplitude. |
| **Fist Clench** | Clenching a fist triggers speed stop (edge trigger, 1.5s cooldown). |
| **Palm Flash** | Rapidly opening your hand triggers a random effect. |
| **Two-Hand Spread** | Moving both hands apart/together controls zoom. |
| **Thumbs Up / Down** 👍👎 | Hold a thumbs-up (or thumbs-down) **centered near the camera** for ~1s — the visuals lean in as a progressive confirmation, then commit — to register "I like this" / "not this." Feeds the same sentiment + learning loop as the Right/Left Winks. |

### Body Gestures (Body Mode)

| Gesture | Action |
|---------|--------|
| **Arm Movement** | Move arms naturally — bigger movements increase visual energy and complexity. |
| **Peeking** | Briefly switching to Body mode (via Auto-Switch) checks for activity; if none is found (e.g., just typing), it reverts to Head mode to prevent getting stuck. |

## Development

The visualization uses:
- WebGL for rendering
- GLSL shaders for the visual effects
- Web Audio API for sound analysis
- Lorenz attractor equations for node movement

### Key Components:

- **Lorenz Attractor**: Controls the movement of nodes in 3D space
- **Fragment Shader**: Calculates the force field and colors based on node positions
- **Palette Manager**: Generates and rotates color palettes
- **Audio Analyzer**: Processes microphone input into frequency bands
- **Command Registry**: Manages keyboard commands and visual effects

### Command System Architecture

The command system uses a function modifier pattern that allows multiple commands to safely modify the same core functions without conflicts:

1. **Command Registration**: Each command is registered with a key binding, name, and description
2. **Function Chaining**: Commands can modify core functions by adding to a function chain
3. **Statefulness**: Commands maintain their own state and can be toggled on/off
4. **UI Integration**: Active commands are displayed in the control panel and help dialog
5. **Parameter Adjustment**: Continuous parameters can be adjusted using mouse movement or scroll wheel
6. **Event Interception**: Global event handlers intercept and manage conflicts between different input methods

To add new commands, use the `CommandRegistry.register()` method with a key and an object containing `activate()` and `deactivate()` methods.

### Function Replacement System

The core of the command system is the ability to safely modify WebGL functions without conflicts:

```javascript
// Register a new toggle command
CommandRegistry.register('x', {
    name: 'My Effect',
    description: 'Description of what this effect does',
    type: 'toggle', // Can be 'toggle', 'continuous', or 'action'
    
    activate: function() {
        // If already active, deactivate when pressed again
        if (CommandRegistry.isActive(this.name)) {
            return false; // Return false to signal deactivation
        }

        // Modify a core function (e.g., updateKnots) using the decorator pattern
        CommandRegistry.replaceFunction('updateKnots', 'x', function(next, knots, knotWeights, knotPosZ, KNOT_COUNT, width, height) {
            // First, call the next function in the chain (original or other modifiers)
            next();
            
            // Then perform your modifications on the parameters
            for (let i = 0; i < KNOT_COUNT; i++) {
                // Example: modify knot positions
                const idx = i * 2;
                knots[idx] = /* your calculation */;
                knots[idx + 1] = /* your calculation */;
            }
            
            return knots; // Return the modified result
        });
        
        showParameterIndicator('My Effect: ON');
        return true;
    },
    
    deactivate: function() {
        // Restore original function when command is deactivated
        CommandRegistry.restoreFunction('updateKnots', 'x');
        showParameterIndicator('My Effect: OFF');
    }
});
```

Available functions that can be modified:
- `updateKnots` - Controls node movement and positions
- `updateOscillationEffects` - Controls oscillation and pulse effects
- `render` - The main rendering function for post-processing effects

The function modifier system provides these key benefits:
1. **Chaining** - Multiple commands can modify the same function in sequence
2. **Safety** - Commands can be toggled on/off without conflicts
3. **Composition** - Effects can be combined in any order
4. **Restoration** - Original functions are preserved and can be restored

### Performance-Oriented Architecture

The system is architected as a performance instrument with several key design patterns:

1. **Multi-Modal Interaction**: Multiple input methods (keyboard, mouse, audio) work together to provide a rich control system
2. **Continuous Parameter Control**: Every visual effect can have continuous real-time adjustment
3. **Visual Feedback Loop**: All adjustments provide immediate visual feedback
4. **Layered Effects**: Multiple effects can be combined, each with its own intensity control
5. **State Preservation**: The system maintains state for all active effects and parameters

The interaction design allows for both dramatic "performance moves" (toggling effects on/off) and subtle adjustments (fine-tuning effect parameters), similar to how DJs might use both dramatic drops and subtle EQ adjustments in musical performances.

## Command System Documentation

The application features a powerful command system that allows various types of controls. Here are the different command types and how to implement them:

### Command Registration

Commands are registered through the CommandRegistry which manages keyboard mappings, command state, and function modifications. To register a new command:

```javascript
// Basic command registration syntax
CommandRegistry.register('keyLetter', {
    name: 'Command Name',             // Display name for UI
    description: 'What it does',      // Description shown in help
    type: 'toggle',                   // Command type
    
    // Required methods
    activate: function() {
        // Code that runs when command is activated
        return true;  // Return true to indicate successful activation
    },
    
    deactivate: function() {
        // Code that runs when command is deactivated
    }
});
```

### Function Replacement System

The core of the command system is the ability to safely modify WebGL functions without conflicts:

```javascript
// Modify a core function (e.g., updateKnots) using decorator pattern
CommandRegistry.replaceFunction('functionName', 'yourCommandKey', function(next, ...originalArgs) {
    // Call the next function in the chain (may be the original or another modifier)
    // You can call it before or after your modifications, or not at all
    next();
    
    // Perform your modifications on the parameters or results
    // ...your code here...
    
    // Return the modified result
    return modifiedResult;
});

// Restore original function when command is deactivated
CommandRegistry.restoreFunction('functionName', 'yourCommandKey');
```

Available functions that can be modified:
- `updateKnots` - Controls node movement
- `updateOscillationEffects` - Controls oscillation effects
- `render` - The main rendering function
