# Project export: VODKA

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: IRL minecraft note blocks
- Devpost: https://devpost.com/software/vodka
- GitHub: https://github.com/jasukej/vodka
- Video: https://www.youtube.com/embed/552fefd924a74072bdfa681c382fae2c?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Kezia (10 commits), Connor Yang (5 commits)

## Devpost submission (written by the team)

### Inspiration

We've all tried air drumming to our favorite songs. But what if any surface could become a drum? What if your kitchen table, desk, or even textbooks could transform into a full drum kit? We watched street performers turn buckets into instruments and got inspired. We wanted to democratize music creation by removing the barrier of expensive equipment. With just drumsticks, a webcam, and our app, anyone can start drumming anywhere.

### What it does

VODKA (Virtual Online Drum Kit App) transforms any ordinary surface into a virtual drum kit using computer vision and accelerometer data. the workflow: Point your webcam at any surface (table, floor, pillows). Our model segments the frame into distinct regions. Use our ESP32-powered drumsticks with motion sensors Hit any surface and hear appropriate drum sounds with velocity sensitivity Capture your performance and share it with friends!

### How we built it

Hardware Stack: ESP32 microcontrollers (2x) - one per drumstick MPU6050 6-axis sensors Electrical tape CV/ML Pipeline: YOLOV8nano (drumstick tip detection) and FastSAM trained on material segmentation Backend: Flask, Python services, pygame.mixer Frontend: React + Vite, SocketIO

### Challenges we ran into

We've never touched hardware Pushed a commit that killed all processes somehow at some point

### Accomplishments we're proud of

Got our hardware component to work! Working around a convoluted workflow (drum hit -> sound) with multiple ingestion streams

### What we learned

How to (not) solder stuff + hardware in general Surface segmentation is pretty hard

### What's next

Should have probably hosted drumstick tip inference on Baseten since inference time is critical

## README (from the GitHub repository)

# Virtual Online Drum Kit App (VODKA)

Transform any surface into a drum kit using computer vision and accelerometer data!

## System Architecture
```
┌─────────────┐
│  Drumstick  │
│ ESP32+MPU   │
└──────┬──────┘
       │ USB
       ↓
┌──────────────────┐      ┌────────────┐
│  Python Backend  │←────→│  Webcam    │
│  - Hit Detection │      │  (CV)      │
│  - Sound Engine  │      └────────────┘
└────────┬─────────┘
         │ WebSocket
         ↓
┌──────────────────┐
│   React Frontend │
│  - Visualization │
│  - Controls      │
└──────────────────┘
```

## Quick Start

### 1. Hardware Setup
See `firmware/esp32_sensor/README.md`

### 2. Backend Setup
```bash
cd backend
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env
# Edit .env with your configuration
python app.py
```

### 3. Frontend Setup
```bash
cd frontend
npm install
npm run dev
```

### 4. Upload Firmware
Open `firmware/esp32_sensor/esp32_sensor.ino` in Arduino IDE and upload to ESP32.

## Project Structure
```
virtual-drum-kit/
├── firmware/           # ESP32 code
├── backend/           # Python Flask server
│   ├── services/      # Core logic modules
│   ├── utils/         # Helper functions
│   └── app.py         # Main server
├── frontend/          # React web app
│   └── src/
│       ├── components/
│       └── services/
├── sounds/            # Audio samples
├── config/            # Configuration files
└── docs/              # Documentation
```

## Team Roles

- **Person 1:** Hardware + Sensor Integration
- **Person 2:** Computer Vision + ML
- **Person 3:** Web UI + Sound Engine

## Model Integration - YOLO/FastSAM

### 🚀 Quick Start: Test Locally (No Deployment)

Open http://localhost:5173 in your browser and click "Start Streaming".

### Model Options

**Option 1: Local YOLO (Development)**
```bash
pip install ultralytics
python app.py
```
- ✅ No deployment needed
- ✅ Real segmentation
- ✅ Fast iteration

**Option 2: Baseten (Production)**
```bash
# Deploy your model to Baseten
# Update .env with endpoint
python app.py
```
- ✅ GPU acceleration
- ✅ Scalable
- ✅ Production ready

### Architecture

- Webcam captures frames at 10fps
- Frame buffer keeps last 2 seconds
- Calibration runs: once, 2s after clicking "Start Streaming"
- Segments stored in memory for hit localization
- Hits map to nearest segment → drum pad

## Testing

### Hit Mapping & Segmentation Store Test
```bash
cd backend
python3 test/test_hit_mapping.py
```

Verifies:
- Segmentation store saves/retrieves segments
- Hit localizer maps coordinates to objects
- Object class names are properly associated

### Simulate Hits via Browser Console
```javascript
socketService.emit('simulate_hit', {
  intensity: 500,
  timestamp: Date.now()
});
```

## Troubleshooting

### ESP32 not detected
- Check USB cable (must support data transfer)
- Install CH340 drivers if needed
- Try different USB port

### No sound playing
- Check `sounds/` directory has .wav files
- Verify pygame.mixer initialized correctly
- Check system audio isn't muted

### High latency
- Reduce webcam resolution
- Disable CV and use accelerometer only
- Check network latency if using hosted model
EOF

## Detected evidence (automated analysis)

Indexed codebase: 49 recognized source files, 157 KB.
- CSS (language) — detected in the code
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- AI coding agent: Cursor — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (79 of 79)

```
.cursorrules
.gitignore
backend/app.py
backend/config.py
backend/enhanced_config.py
backend/esp32_ble_fixed.ino
backend/esp32_ble.ino
backend/esp32.ino
backend/integration_guide.py
backend/requirements_ble.txt
backend/requirements.txt
backend/services/__init__.py
backend/services/accuracy_tools.py
backend/services/audio_player.py
backend/services/ble_drumstick_service.py
backend/services/cv_localizer.py
backend/services/drumstick_detector.py
backend/services/frame_buffer.py
backend/services/hit_detector.py
backend/services/hit_localizer.py
backend/services/model_service.py
backend/services/segmentation_store.py
backend/services/sensor_ingestion.py
backend/services/sound_mapper.py
backend/services/yolo_enhanced.py
backend/services/yolo_local.py
backend/test/__init__.py
backend/test/README.md
backend/test/test_hit_mapping.py
backend/utils/__init__.py
backend/utils/serial_reader.py
frontend/.gitignore
frontend/eslint.config.js
frontend/index.html
frontend/package.json
frontend/postcss.config.cjs
frontend/src/App.css
frontend/src/App.jsx
frontend/src/components/BLEControlPanel.jsx
frontend/src/components/CalibrationIndicator.jsx
frontend/src/components/Controls.jsx
frontend/src/components/DrumPad.jsx
frontend/src/components/HitIndicator.jsx
frontend/src/components/MaterialClassificationDebug.jsx
frontend/src/components/StopStreamModal.jsx
frontend/src/components/Visualizer.jsx
frontend/src/components/WebcamStream.jsx
frontend/src/hooks/useSocket.js
frontend/src/hooks/useWebcam.js
frontend/src/index.css
frontend/src/main.jsx
frontend/src/services/audioService.js
frontend/src/services/socketService.js
frontend/tailwind.config.js
frontend/vite.config.js
model/drumsticks/config.yaml
model/drumsticks/data/best.pt
model/drumsticks/model/__init__.py
model/drumsticks/model/model.py
model/weights/yolov8n.pt
README.md
sounds/banjo.ogg
sounds/bass.ogg
sounds/bassattack.ogg
sounds/bd.ogg
sounds/bell.ogg
sounds/bit.ogg
sounds/cow_bell.ogg
sounds/didgeridoo.ogg
sounds/flute.ogg
sounds/guitar.ogg
sounds/harp.ogg
sounds/harp2.ogg
sounds/hat.ogg
sounds/icechime.ogg
sounds/iron_xylophone.ogg
sounds/pling.ogg
sounds/snare.ogg
sounds/xylobone.ogg
```

### Dependencies

- backend/requirements.txt: flask@>=3.0.0, flask-cors@>=4.0.0, flask-sock@>=0.7.0, flask-socketio@>=5.3.0, numpy@>=1.26.0, opencv-python@>=4.10.0, pillow@>=10.1.0, pygame@>=2.6.0, pyserial@>=3.5, python-dotenv@>=1.0.0, requests@>=2.31.0, scipy@==1.11.4, simple-websocket@>=1.0.0, ultralytics@>=8.3.0
- frontend/package.json: @eslint/js@^9.36.0, @types/react@^19.1.16, @types/react-dom@^19.1.9, @vitejs/plugin-react@^5.0.4, autoprefixer@^10.4.21, axios@^1.12.2, eslint@^9.36.0, eslint-plugin-react-hooks@^5.2.0, eslint-plugin-react-refresh@^0.4.22, globals@^16.4.0, postcss@^8.5.6, react@^19.1.1, react-dom@^19.1.1, socket.io-client@^4.8.1, tailwindcss@^3.4.18, vite@^7.1.7

### Recent commits (newest first)

- merge
- bluetooth
- lower threshold
- update materials weights
- maybe adjust classification with claude
- fixed things
- missed fe comp
- made some changes
- add sound mapping
- hook up drumstick model
- add endpoint for detecting stick
- id + mpu integration
- Merge pull request #2 from jasukej/kez/hit-mapping
- calibrate videocam frames
- esp32 stuff
- frontend
- backend
- add cursorrules
- setup

## Key source files (fetched from GitHub, selected and truncated for size)

### backend/requirements.txt

```
# Web Framework
flask>=3.0.0
flask-sock>=0.7.0
flask-socketio>=5.3.0
flask-cors>=4.0.0
simple-websocket>=1.0.0

# Computer Vision
opencv-python>=4.10.0
numpy>=1.26.0
pillow>=10.1.0

# Hardware Communication
pyserial>=3.5

# Audio
pygame>=2.6.0

# ML
requests>=2.31.0
python-dotenv>=1.0.0

# YOLO for object and material detection
ultralytics>=8.3.0  # YOLOv8 support

# Utilities
scipy==1.11.4

```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios": "^1.12.2",
    "react": "^19.1.1",
    "react-dom": "^19.1.1",
    "socket.io-client": "^4.8.1"
  },
  "devDependencies": {
    "@eslint/js": "^9.36.0",
    "@types/react": "^19.1.16",
    "@types/react-dom": "^19.1.9",
    "@vitejs/plugin-react": "^5.0.4",
    "autoprefixer": "^10.4.21",
    "eslint": "^9.36.0",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.22",
    "globals": "^16.4.0",
    "postcss": "^8.5.6",
    "tailwindcss": "^3.4.18",
    "vite": "^7.1.7"
  }
}

```

### frontend/src/main.jsx

```javascript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

```

### backend/app.py

```python
from flask import Flask, render_template, request
from flask_cors import CORS
import os
import time
import logging
from dotenv import load_dotenv
import asyncio
import cv2
import json
from threading import Thread
from flask_sock import Sock
from flask_socketio import SocketIO, emit

from config import Config
from services.sound_mapper import SoundMapper
from services.sensor_ingestion import SensorIngestion
from services.model_service import model_service
from services.segmentation_store import segmentation_store
from services.frame_buffer import frame_buffer
from services.hit_localizer import hit_localizer
from services.drumstick_detector import drumstick_detector

# BLE Support
try:
    from services.ble_drumstick_service import ble_drumstick_service
    BLE_AVAILABLE = True
except ImportError:
    BLE_AVAILABLE = False
    ble_drumstick_service = None
    print("⚠️  BLE support not available. Install with: pip install bleak")

load_dotenv()

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'your-secret-key')
CORS(app)
sock = Sock(app)
socketio = SocketIO(app, cors_allowed_origins="*")

# Initialize services
sound_mapper = SoundMapper()
sensor_ingestion = SensorIngestion()

async def handle_esp32_hit(impact_data: dict):
    """Handle hit detection from ESP32 sensor and trigger vision-based localization"""
    try:
        timestamp = impact_data.get('timestamp', int(time.time() * 1000))
        velocity = impact_data.get('velocity', 0)
        magnitude = impact_data.get('magnitude', 0)

        # Calculate intensity from velocity (normalize to 0-1 range)
        intensity = min(velocity / 100.0, 1.0) if velocity > 0 else 0.5

        logger.info(f'🥁 ESP32 HIT DETECTED: velocity={velocity}, magnitude={magnitude}, intensity={intensity:.2f}')

        # Trigger the same logic as simulate_hit
        if not segmentation_store.is_calibrated():
            logger.warning('System not calibrated - cannot localize hit')
            return

        latest_frame = frame_buffer.get_latest_frame()
        if not latest_frame:
            logger.warning('No frame available in buffer')
            return

        frame_timestamp = latest_frame.get('timestamp', 0)
        frame_data_size = len(latest_frame.get('frame', ''))
        frame_data_hash = hash(latest_frame.get('frame', '')) % 1000000
        logger.info(f'📸 Using frame from buffer: timestamp={frame_timestamp:.3f}, size={frame_data_size} bytes, hash={frame_data_hash}')

        segments = segmentation_store.get_segments()
        segment_count = len(segments.get('segments', []))
        logger.info(f'Using calibration with {segment_count} segments')

        logger.info('🥢 Running YOLOv8nano inference to detect drumstick...')
        hit_result = hit_localizer.localize_hit(
            latest_frame,
            segments,
            timestamp / 1000.0,
            None
        )

        if hit_result:
            drum = hit_result['drum_pad']
            conf = hit_result['confidence']
            pos = hit_result['position']
            segment_id = hit_result.get('segment_id', -1)
            bbox = hit_result.get('bbox', [])

            segment_list = segments.get('segments', [])
            class_name = 'unknown'
            if segment_id >= 0 and segment_id < len(segment_list):
                class_name = segment_list[segment_id].get('class_name', 'unknown')

            logger.info(f'HIT LOCALIZED:')
            logger.info(f'   Material: {class_name.upper()}')
            logger.info(f'   Drum Pad: {drum.upper()}')
            logger.info(f'   Confidence: {conf:.2f}')
            logger.info(f'   Position: ({pos.get("x", 0):.0f}, {pos.get("y", 0):.0f})')

            # Play sound based on detected drum pad (already mapped from material in hit_localizer)
            sound_mapper.audio_player.play_drum_sound(drum, intensity)

            # Emit to connected clients via SocketIO
            socketio.emit('hit_localized', {
                'status': 'success',
                'drum_pad': drum,
                'position': pos,
                'confidence': conf,
                'intensity': intensity,
                'timestamp': timestamp,
                'segment_id': segment_id,
                'bbox': bbox,
                'class_name': class_name,
                'drumstick_position': hit_result.get('drumstick_position'),
                'source': 'esp32'
            })
        else:
            logger.error('Hit localization failed')

        logger.info('=' * 70)

    except Exception as e:
        logger.error(f'Error handling ESP32 hit: {e}')
        import traceback
        traceback.print_exc()

# Connect sensor ingestion callbacks
sensor_ingestion.set_hit_detected_callback(handle_esp32_hit)

# Setup BLE callbacks if available
if BLE_AVAILABLE and ble_drumstick_service:
    def handle_ble_impact(impact_data):
        """Handle impact from BLE drumstick"""
        logger.info(f"🔵 BLE Impact received: {impact_data}")
        # Use create_task instead of asyncio.run to avoid event loop conflict
        try:
            loop = asyncio.get_running_loop()
            loop.create_task(handle_esp32_hit(impact_data))
        except RuntimeError:
            # No event loop running, create one
            asyncio.run(handle_esp32_hit(impact_data))

    def handle_ble_status(status_data):
        """Handle status from BLE drumstick"""
        logger.info(f"📊 BLE Status: {status_data}")
        # Broadcast status to connected clients
        socketio.emit('drumstick_status', status_data)

    def handle_ble_connect():
        """Handle BLE drumstick connection"""
        logger.info("🟢 BLE Drumstick connected!")
        socketio.emit('sensor_connected', {'status': 'connected', 'type': 'BLE'})

    def handle_ble_disconnect():
        """Handle BLE drumstick disconnection"""
        logger.info("🔴 BLE Drumstick disconnected!")
    
[truncated — 19583 more characters]
```

### frontend/src/App.jsx

```javascript
import { useEffect, useState } from 'react';
import './App.css';
import BLEControlPanel from './components/BLEControlPanel';
import DrumPad from './components/DrumPad';
import HitIndicator from './components/HitIndicator';
import Visualizer from './components/Visualizer';
import WebcamStream from './components/WebcamStream';
import { useSocket } from './hooks/useSocket';

function App() {
  const { connected, socketService } = useSocket();
  const [streaming, setStreaming] = useState(false);
  const [hits, setHits] = useState([]);
  const [lastHit, setLastHit] = useState(null);
  const [frameCount, setFrameCount] = useState(0);

  useEffect(() => {
    if (socketService.socket) {
      socketService.on('hit_detected', (data) => {
        console.log('Hit detected:', data);
        const newHit = { ...data, timestamp: Date.now() };
        setHits(prev => [...prev, newHit]);
        setLastHit(newHit);
      });

      socketService.on('calibration_result', (data) => {
        console.log('%c CALIBRATION RESULT', 'font-size: 16px; font-weight: bold; color: blue');
        console.log('Status:', data.status);
        console.log('Segments:', data.segment_count);

        if (data.status === 'success' && data.segments && data.segments.length > 0) {
          console.log('%c📊 Detected Materials:', 'font-weight: bold');
          console.table(data.segments.map(s => ({
            ID: s.id,
            Material: s.class_name || 'unknown',
            X: s.bbox[0],
            Y: s.bbox[1],
            Width: s.bbox[2],
            Height: s.bbox[3],
            Confidence: (s.confidence * 100).toFixed(1) + '%'
          })));

          console.log('%c🎯 Materials found:', 'color: green; font-weight: bold');
          data.segments.forEach((s, i) => {
            console.log(`   ${i + 1}. ${s.class_name || 'unknown'} (${(s.confidence * 100).toFixed(0)}% confident)`);
          });
        } else if (data.status === 'error') {
          console.error('Calibration failed:', data.message);
        }
      });

      socketService.on('hit_localized', (data) => {
        if (data.status === 'success') {
          const source = data.source === 'esp32' ? '🎛️ ESP32' : '🖱️ MANUAL';
          console.log(`%c🥁 HIT LOCALIZED (${source})`, 'font-size: 16px; font-weight: bold; color: green');

          if (data.class_name) {
            console.log('Material Hit:', data.class_name.toUpperCase());
          }
          console.log('Drum Pad:', data.drum_pad.toUpperCase());
          console.log('Position:', `(${Math.round(data.position.x)}, ${Math.round(data.position.y)})`);
          console.log('Confidence:', (data.confidence * 100).toFixed(1) + '%');
          console.log('Source:', data.source === 'esp32' ? 'ESP32 Sensor' : 'Manual Trigger');

          if (data.segment_id !== undefined) {
            console.log('Segment ID:', data.segment_id);
          }
          if (data.bbox) {
            console.log('Bounding Box:', `[${data.bbox[0]}, ${data.bbox[1]}, ${data.bbox[2]}, ${data.bbox[3]}]`);
          }

          if (data.source === 'esp32' && data.intensity !== undefined) {
            console.log('Hit Intensity:', (data.intensity * 100).toFixed(1) + '%');
          }

          if (data.drumstick_position) {
            console.log('%c🥢 YOLOv8nano Detection:', 'font-size: 14px; font-weight: bold; color: orange');
            console.log('Drumstick Position:', `(${Math.round(data.drumstick_position.x)}, ${Math.round(data.drumstick_position.y)})`);
            console.log('Drumstick Confidence:', (data.drumstick_position.confidence * 100).toFixed(1) + '%');
            console.log('Drumstick Class:', data.drumstick_position.class_name || 'unknown');
          } else {
            console.log('%c🥢 YOLOv8nano Detection:', 'font-size: 14px; font-weight: bold; color: orange');
            console.log('⚠️ No drumstick detected - using fallback to largest segment');
          }

          const newHit = {
            drum: data.drum_pad,
            position: data.position,
            intensity: data.intensity,
            timestamp: data.timestamp,
            segment_id: data.segment_id,
            class_name: data.class_name,
            drumstick_position: data.drumstick_position
          };
          setHits(prev => [...prev, newHit]);
          setLastHit(newHit);
        } else {
          console.error('%c❌ Hit localization failed', 'color: red; font-weight: bold');
          console.error('Error:', data.message);
        }
      });

      socketService.on('drum_position', (data) => {
        console.log('Drum position:', data);
      });
    }

    return () => {
      if (socketService.socket) {
        socketService.off('hit_detected');
        socketService.off('calibration_result');
        socketService.off('hit_localized');
        socketService.off('drum_position');
      }
    };
  }, [socketService]);

  const handleToggleStream = () => {
    setStreaming(!streaming);
  };

  const handleFrameCapture = () => {
    setFrameCount(prev => prev + 1);
  };

  return (
    <div className="relative h-screen w-screen overflow-hidden bg-black">
      {/* Material Classification Debug Panel */}
      <MaterialClassificationDebug
        calibrationData={calibrationData}
        lastHit={lastHit}
      />

      {/* BLE Control Panel */}
      <BLEControlPanel
        socketService={socketService}
        connected={connected}
      />

      <header className="absolute top-0 left-0 right-0 z-50 flex items-center justify-between p-6 bg-gradient-to-b from-black/80 to-transparent">
        <h1 className="text-lg font-bold text-white">🥁 VODKA - Virtual Offline Drum Kit Application</h1>

        <div className="flex items-center gap-4">
          <div className="flex items-center gap-2">
            <div className={`w-2 h-2 rounded-full ${connected ? 'bg-green-500' : 'bg-red-500'}`} />
            <span className="text-sm text-white/90">
              {connected ? 'Connected' : 'Disconnec
[truncated — 1836 more characters]
```

### frontend/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {
      fontFamily: {
        sans: ['"Space Mono"', 'monospace'],
      },
    },
  },
  plugins: [],
}


```

### frontend/vite.config.js

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {
    port: 3000,
    proxy: {
      '/socket.io': {
        target: 'http://localhost:5001',
        ws: true,
        changeOrigin: true
      },
      '/api': {
        target: 'http://localhost:5001',
        changeOrigin: true
      }
    }
  }
})

```

### frontend/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
    <title>VODKA</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

```

### frontend/eslint.config.js

```javascript
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
  globalIgnores(['dist']),
  {
    files: ['**/*.{js,jsx}'],
    extends: [
      js.configs.recommended,
      reactHooks.configs['recommended-latest'],
      reactRefresh.configs.vite,
    ],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parserOptions: {
        ecmaVersion: 'latest',
        ecmaFeatures: { jsx: true },
        sourceType: 'module',
      },
    },
    rules: {
      'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
    },
  },
])

```

### backend/enhanced_config.py

```python
"""
Enhanced configuration for improved material detection accuracy
Uses custom-trained YOLO model for direct material classification
"""

import os
from typing import Dict, List, Tuple

class EnhancedConfig:
    # ============ ENHANCED DETECTION CONFIG ============

    # YOLO Model Configuration
    YOLO_CONFIDENCE_THRESHOLD = float(os.getenv('YOLO_CONFIDENCE_THRESHOLD', '0.10'))
    YOLO_IOU_THRESHOLD = float(os.getenv('YOLO_IOU_THRESHOLD', '0.45'))
    YOLO_MIN_AREA = int(os.getenv('YOLO_MIN_AREA', '300'))
    
    # Material Detection Configuration
    MATERIAL_CONFIDENCE_THRESHOLD = float(os.getenv('MATERIAL_CONFIDENCE_THRESHOLD', '0.15'))

    # ============ IMAGE ENHANCEMENT CONFIG ============

    # Image preprocessing
    ENHANCE_CONTRAST = float(os.getenv('ENHANCE_CONTRAST', '1.2'))  # 1.0 = no change
    ENHANCE_SHARPNESS = float(os.getenv('ENHANCE_SHARPNESS', '1.1'))
    ENHANCE_BRIGHTNESS = float(os.getenv('ENHANCE_BRIGHTNESS', '1.0'))

    # Target image size for YOLO (larger = better accuracy, slower)
    YOLO_INPUT_SIZE = int(os.getenv('YOLO_INPUT_SIZE', '640'))

    # ============ ACCURACY MONITORING CONFIG ============

    # Enable data collection for training
    COLLECT_TRAINING_DATA = os.getenv('COLLECT_TRAINING_DATA', 'false').lower() == 'true'
    TRAINING_DATA_DIR = os.getenv('TRAINING_DATA_DIR', 'training_data')

    # Accuracy monitoring
    ENABLE_ACCURACY_MONITORING = os.getenv('ENABLE_ACCURACY_MONITORING', 'true').lower() == 'true'

    # Auto-correction features
    USE_TEMPORAL_CONSISTENCY = os.getenv('USE_TEMPORAL_CONSISTENCY', 'true').lower() == 'true'
    TEMPORAL_WINDOW_SIZE = int(os.getenv('TEMPORAL_WINDOW_SIZE', '5'))  # frames

    # ============ MATERIAL TO DRUM MAPPING ============
    
    MATERIAL_TO_DRUM = {
        "wood": "kick",
        "metal": "cymbal",
        "plastic": "tom",
        "glass": "hihat",
        "ceramic": "tom",
        "fabric": "snare",
        "paper": "snare",
        "rubber": "tom",
        "stone": "kick",
        "brick": "kick",
        "carpet": "snare",
        "foliage": "snare",
        "food": "tom",
        "hair": "snare",
        "leather": "snare",
        "mirror": "hihat",
        "other": "snare",
        "painted": "tom",
        "polished_stone": "kick",
        "skin": "snare",
        "sky": "hihat",
        "tile": "kick",
        "wallpaper": "snare",
        "water": "hihat",
        "unknown": "snare"
    }

    # ============ PERFORMANCE OPTIMIZATION ============

    # Batch processing for multiple materials
    ENABLE_BATCH_PROCESSING = os.getenv('ENABLE_BATCH_PROCESSING', 'true').lower() == 'true'
    MAX_BATCH_SIZE = int(os.getenv('MAX_BATCH_SIZE', '8'))

    # Multi-threading for parallel processing
    USE_PARALLEL_PROCESSING = os.getenv('USE_PARALLEL_PROCESSING', 'false').lower() == 'true'
    MAX_WORKERS = int(os.getenv('MAX_WORKERS', '4'))

# Usage example in your app.py:
# from enhanced_config import EnhancedConfig
# enhanced_config = EnhancedConfig()
```

[34 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]