tuanchris 3 miesięcy temu
rodzic
commit
e37e9d3c24

+ 10 - 0
modules/core/pattern_manager.py

@@ -691,6 +691,16 @@ async def run_theta_rho_file(file_path, is_playlist=False):
         ball_tracking_active = False
         logger.info(f"Ball tracking mode: {state.ball_tracking_mode}, manager exists: {state.ball_tracking_manager is not None}")
 
+        # Clear ball tracking position data for fresh start (if manager exists and is active or will be active)
+        if state.ball_tracking_manager and (state.ball_tracking_manager._active or state.ball_tracking_mode == "playing_only"):
+            logger.info("Clearing ball tracking position data for new pattern")
+            if state.ball_tracking_manager._use_buffer and state.ball_tracking_manager.position_buffer:
+                state.ball_tracking_manager.position_buffer.clear()
+            else:
+                state.ball_tracking_manager._current_position = None
+            state.ball_tracking_manager._update_count = 0
+            state.ball_tracking_manager._skipped_updates = 0
+
         if state.ball_tracking_mode == "playing_only" and state.ball_tracking_manager:
             logger.info("Starting ball tracking (playing_only mode)")
             state.ball_tracking_manager.start()

+ 2 - 2
modules/core/state.py

@@ -78,7 +78,7 @@ class AppState:
         self.ball_tracking_led_offset = 0  # LED offset (0 to num_leds-1)
         self.ball_tracking_reversed = False  # Reverse LED direction
         self.ball_tracking_spread = 3  # Number of adjacent LEDs (1-10)
-        self.ball_tracking_lookback = 5  # Coordinates to look back (0-15)
+        self.ball_tracking_lookback = 0  # Coordinates to look back (0-15) - 0 for instant tracking
         self.ball_tracking_brightness = 50  # Brightness 0-100
         self.ball_tracking_color = "#ffffff"  # Hex color for tracking
         self.ball_tracking_trail_enabled = False  # Enable fade trail
@@ -342,7 +342,7 @@ class AppState:
         self.ball_tracking_led_offset = data.get("ball_tracking_led_offset", 0)
         self.ball_tracking_reversed = data.get("ball_tracking_reversed", False)
         self.ball_tracking_spread = data.get("ball_tracking_spread", 3)
-        self.ball_tracking_lookback = data.get("ball_tracking_lookback", 5)
+        self.ball_tracking_lookback = data.get("ball_tracking_lookback", 0)  # Default to 0 for instant response
         self.ball_tracking_brightness = data.get("ball_tracking_brightness", 50)
         self.ball_tracking_color = data.get("ball_tracking_color", "#ffffff")
         self.ball_tracking_trail_enabled = data.get("ball_tracking_trail_enabled", False)

+ 73 - 12
modules/led/ball_tracking_manager.py

@@ -37,8 +37,19 @@ class BallTrackingManager:
         self.num_leds = num_leds
         self.config = config
 
-        # Coordinate history buffer (max 15 coordinates)
-        self.position_buffer = deque(maxlen=15)
+        # Position storage (buffer only if lookback > 0)
+        lookback = config.get("lookback", 0)
+        if lookback > 0:
+            # Use buffer for lookback delay
+            self.position_buffer = deque(maxlen=min(15, lookback + 5))
+            self._use_buffer = True
+            logger.info(f"Using position buffer (size={lookback + 5}) for lookback={lookback}")
+        else:
+            # No lookback, just store current position
+            self.position_buffer = None
+            self._current_position = None  # (theta, rho, timestamp)
+            self._use_buffer = False
+            logger.info("Direct tracking (no lookback buffer)")
 
         # Tracking state
         self._active = False
@@ -46,6 +57,7 @@ class BallTrackingManager:
         self._last_led_index = None
         self._lock = threading.Lock()  # Thread safety for LED index updates
         self._update_count = 0  # Counter for debug logging
+        self._skipped_updates = 0  # Track how many updates were skipped
 
         logger.info(f"BallTrackingManager initialized with {num_leds} LEDs")
 
@@ -64,7 +76,10 @@ class BallTrackingManager:
             return
 
         self._active = False
-        self.position_buffer.clear()
+        if self._use_buffer and self.position_buffer:
+            self.position_buffer.clear()
+        else:
+            self._current_position = None
         self._last_led_index = None
 
         logger.info("Ball tracking stopped")
@@ -80,16 +95,57 @@ class BallTrackingManager:
         if not self._active:
             return
 
-        # Add to buffer
-        self.position_buffer.append((theta, rho, time.time()))
+        # Store position
+        timestamp = time.time()
+        if self._use_buffer:
+            self.position_buffer.append((theta, rho, timestamp))
+        else:
+            self._current_position = (theta, rho, timestamp)
+
         self._update_count += 1
 
-        # Debug logging (every 50th update)
-        if self._update_count % 50 == 0:
-            logger.info(f"Position update #{self._update_count}: theta={theta:.1f}°, rho={rho:.2f}, buffer_size={len(self.position_buffer)}")
+        # Debug logging (every 100th update)
+        if self._update_count % 100 == 0:
+            buffer_info = f"buffer_size={len(self.position_buffer)}" if self._use_buffer else "direct"
+            logger.info(f"Position update #{self._update_count}: theta={theta:.1f}°, rho={rho:.2f}, {buffer_info}, skipped={self._skipped_updates}")
+
+        # Trigger LED update (with optimization)
+        self._update_leds_optimized(theta, rho)
+
+    def _update_leds_optimized(self, current_theta: float, current_rho: float):
+        """
+        Optimized LED update - only recalculates if LED zone changed
+
+        Args:
+            current_theta: Most recent theta value
+            current_rho: Most recent rho value
+        """
+        if not self._active:
+            return
 
-        # Trigger LED update
-        self._update_leds()
+        # If using lookback buffer, get the delayed position
+        if self._use_buffer:
+            position = self._get_tracked_position()
+            if position is None:
+                return
+            theta, rho, _ = position
+        else:
+            # Direct tracking - use current position
+            theta = current_theta
+            rho = current_rho
+
+        # Calculate new LED index
+        new_led_index = self._theta_to_led(theta)
+
+        # OPTIMIZATION: Only update if LED index actually changed
+        with self._lock:
+            if new_led_index == self._last_led_index:
+                # LED zone hasn't changed, skip update
+                self._skipped_updates += 1
+                return
+
+            # LED zone changed, update it
+            self._last_led_index = new_led_index
 
     def _update_leds(self):
         """Update LED tracking state (rendering is done by effect loop)"""
@@ -117,11 +173,16 @@ class BallTrackingManager:
 
     def _get_tracked_position(self) -> Optional[Tuple[float, float, float]]:
         """Get position to track (accounting for lookback delay)"""
-        lookback = self.config.get("lookback", 0)
+        if not self._use_buffer:
+            # Direct mode - return current position
+            return self._current_position
 
-        if len(self.position_buffer) == 0:
+        # Buffer mode - apply lookback
+        if not self.position_buffer or len(self.position_buffer) == 0:
             return None
 
+        lookback = self.config.get("lookback", 0)
+
         # Clamp lookback to buffer size
         lookback = min(lookback, len(self.position_buffer) - 1)
         lookback = max(0, lookback)

+ 2 - 1
modules/led/dw_leds/effects/basic_effects.py

@@ -1096,7 +1096,8 @@ def mode_ball_tracking(seg: Segment) -> int:
 
     # Log tracking data occasionally
     if seg.call % 100 == 0:
-        logger.info(f"Ball tracking effect: LED index={tracking_data['led_index']}, spread={tracking_data['spread']}, buffer_size={len(manager.position_buffer)}")
+        buffer_info = f"buffer_size={len(manager.position_buffer)}" if manager._use_buffer and manager.position_buffer else "direct"
+        logger.info(f"Ball tracking effect: LED index={tracking_data['led_index']}, spread={tracking_data['spread']}, {buffer_info}")
 
     center_led = tracking_data['led_index']
     spread = tracking_data['spread']

+ 1 - 10
static/js/settings.js

@@ -1641,8 +1641,6 @@ async function initBallTracking() {
     const ballTrackingMode = document.getElementById('ballTrackingMode');
     const ballTrackingSpread = document.getElementById('ballTrackingSpread');
     const ballTrackingSpreadValue = document.getElementById('ballTrackingSpreadValue');
-    const ballTrackingLookback = document.getElementById('ballTrackingLookback');
-    const ballTrackingLookbackValue = document.getElementById('ballTrackingLookbackValue');
     const ballTrackingBrightness = document.getElementById('ballTrackingBrightness');
     const ballTrackingBrightnessValue = document.getElementById('ballTrackingBrightnessValue');
     const ballTrackingColor = document.getElementById('ballTrackingColor');
@@ -1658,8 +1656,6 @@ async function initBallTracking() {
             ballTrackingMode.value = data.mode;
             ballTrackingSpread.value = data.config.spread;
             ballTrackingSpreadValue.textContent = `${data.config.spread} LED${data.config.spread > 1 ? 's' : ''}`;
-            ballTrackingLookback.value = data.config.lookback;
-            ballTrackingLookbackValue.textContent = `${data.config.lookback} coord${data.config.lookback !== 1 ? 's' : ''}`;
             ballTrackingBrightness.value = data.config.brightness;
             ballTrackingBrightnessValue.textContent = `${data.config.brightness}%`;
             ballTrackingColor.value = data.config.color;
@@ -1688,11 +1684,6 @@ async function initBallTracking() {
         ballTrackingSpreadValue.textContent = `${value} LED${value > 1 ? 's' : ''}`;
     });
 
-    ballTrackingLookback.addEventListener('input', () => {
-        const value = parseInt(ballTrackingLookback.value);
-        ballTrackingLookbackValue.textContent = `${value} coord${value !== 1 ? 's' : ''}`;
-    });
-
     ballTrackingBrightness.addEventListener('input', () => {
         const value = parseInt(ballTrackingBrightness.value);
         ballTrackingBrightnessValue.textContent = `${value}%`;
@@ -1709,7 +1700,7 @@ async function initBallTracking() {
                 enabled: ballTrackingEnabled.checked,
                 mode: ballTrackingMode.value,
                 spread: parseInt(ballTrackingSpread.value),
-                lookback: parseInt(ballTrackingLookback.value),
+                lookback: 0,  // Lookback disabled - always use instant tracking
                 brightness: parseInt(ballTrackingBrightness.value),
                 color: ballTrackingColor.value
             };

+ 0 - 21
templates/settings.html

@@ -818,27 +818,6 @@ input:checked + .slider:before {
             </p>
           </label>
 
-          <!-- Look-back Delay -->
-          <label class="flex flex-col gap-1.5">
-            <span class="text-slate-700 text-sm font-medium leading-normal">Look-back Delay</span>
-            <input
-              id="ballTrackingLookback"
-              type="range"
-              min="0"
-              max="15"
-              value="5"
-              class="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer"
-            />
-            <div class="flex justify-between text-xs text-slate-500">
-              <span>0</span>
-              <span id="ballTrackingLookbackValue">5 coords</span>
-              <span>15</span>
-            </div>
-            <p class="text-xs text-slate-500">
-              Compensate for motion lag by tracking past position
-            </p>
-          </label>
-
           <!-- Brightness -->
           <label class="flex flex-col gap-1.5">
             <span class="text-slate-700 text-sm font-medium leading-normal">Brightness</span>