Visualizer Optimization Plan #7

Closed
opened 2026-04-10 15:12:44 -07:00 by dallasgroot · 0 comments
Owner

AI Agent Directives: iOS Visualizer Refactor & Optimization

Role & Context

You are an expert Apple platforms performance engineer specializing in high-performance SwiftUI, memory management, and vDSP. We are optimizing an iOS audio visualizer (MitsuhaVisualizerView) that reads pre-computed offline FFT data.

The core issue is severe memory allocation overhead. The app is dropping frames because we are heavily allocating [Float] arrays both during ingestion and inside the 60fps render loop.

🛑 Strict Invariants

  1. Zero-Allocation Render Loop: Once playback starts, the TimelineView / Canvas render loop and updateDisplayLevels MUST allocate zero new memory. Mutate existing buffers in-place.
  2. Environment: Modern Xcode App Project. No PlaygroundSupport.
  3. Shadow Wave: Do not alter the temporal ring-buffer logic (levelHistoryBuf). It perfectly matches the desired visual style.

Execution Plan

Execute these tasks sequentially.

Phase 1: Flat Memory-Mapped Storage (VisualizerStorageManager.swift)

The current loadCache method reads the file and allocates an expensive [[Float]] array of arrays.

  • Task: Rewrite loadCache to use Data(contentsOf: url, options: .alwaysMapped).
  • Task: Instead of returning [[Float]], return a highly efficient struct or tuple containing:
    1. A flat ContiguousArray<Float> (or directly expose the mapped UnsafeBufferPointer if safe to do so).
    2. frameCount (UInt32)
    3. pointsPerFrame (UInt32)
  • Task: Update saveCache to accept and write this flat format if necessary, though it currently writes sequentially perfectly fine.

Phase 2: Zero-Allocation Display Updates (MitsuhaVisualizerView.swift)

The updateDisplayLevels method currently creates a new var targetLevels = [Float]() array 60 times a second.

  • Task: Refactor VisualizerLevelBox to pre-allocate targetLevels matching settings.numberOfPoints on initialization.
  • Task: Rewrite updateDisplayLevels so that it mutates this pre-allocated targetLevels buffer in-place using standard for loops.
  • Task: If the newRawLevels array size differs from count, perform the linear interpolation directly into the pre-allocated buffer without instantiating any intermediate collections.

Phase 3: The View Invalidation Audit

  • Task: Review MitsuhaVisualizerView. Ensure that the @ObservedObject var settings and @StateObject private var box are not causing the parent view to invalidate on every timeline tick. The Canvas should be the only thing redrawing. Ensure variables captured by the Canvas closure are tight and efficient.
# AI Agent Directives: iOS Visualizer Refactor & Optimization ## Role & Context You are an expert Apple platforms performance engineer specializing in high-performance SwiftUI, memory management, and vDSP. We are optimizing an iOS audio visualizer (`MitsuhaVisualizerView`) that reads pre-computed offline FFT data. The core issue is severe memory allocation overhead. The app is dropping frames because we are heavily allocating `[Float]` arrays both during ingestion and inside the 60fps render loop. ## 🛑 Strict Invariants 1. **Zero-Allocation Render Loop:** Once playback starts, the `TimelineView` / `Canvas` render loop and `updateDisplayLevels` MUST allocate zero new memory. Mutate existing buffers in-place. 2. **Environment:** Modern Xcode App Project. No `PlaygroundSupport`. 3. **Shadow Wave:** Do not alter the temporal ring-buffer logic (`levelHistoryBuf`). It perfectly matches the desired visual style. ## Execution Plan Execute these tasks sequentially. ### Phase 1: Flat Memory-Mapped Storage (`VisualizerStorageManager.swift`) The current `loadCache` method reads the file and allocates an expensive `[[Float]]` array of arrays. * **Task:** Rewrite `loadCache` to use `Data(contentsOf: url, options: .alwaysMapped)`. * **Task:** Instead of returning `[[Float]]`, return a highly efficient struct or tuple containing: 1. A flat `ContiguousArray<Float>` (or directly expose the mapped `UnsafeBufferPointer` if safe to do so). 2. `frameCount` (UInt32) 3. `pointsPerFrame` (UInt32) * **Task:** Update `saveCache` to accept and write this flat format if necessary, though it currently writes sequentially perfectly fine. ### Phase 2: Zero-Allocation Display Updates (`MitsuhaVisualizerView.swift`) The `updateDisplayLevels` method currently creates a new `var targetLevels = [Float]()` array 60 times a second. * **Task:** Refactor `VisualizerLevelBox` to pre-allocate `targetLevels` matching `settings.numberOfPoints` on initialization. * **Task:** Rewrite `updateDisplayLevels` so that it mutates this pre-allocated `targetLevels` buffer in-place using standard `for` loops. * **Task:** If the `newRawLevels` array size differs from `count`, perform the linear interpolation directly into the pre-allocated buffer without instantiating any intermediate collections. ### Phase 3: The View Invalidation Audit * **Task:** Review `MitsuhaVisualizerView`. Ensure that the `@ObservedObject var settings` and `@StateObject private var box` are not causing the parent view to invalidate on every timeline tick. The `Canvas` should be the only thing redrawing. Ensure variables captured by the `Canvas` closure are tight and efficient.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: dallasgroot/NavidromeApp#7
No description provided.