Skip to content

feat: Custom Acrylic backdrop + Win2D 3D visualizer#3

Merged
devohmycode merged 5 commits intomainfrom
0.0.3
Mar 12, 2026
Merged

feat: Custom Acrylic backdrop + Win2D 3D visualizer#3
devohmycode merged 5 commits intomainfrom
0.0.3

Conversation

@devohmycode
Copy link
Owner

@devohmycode devohmycode commented Mar 12, 2026

Summary

  • Custom Acrylic backdrop: "Acrylic personnalisé" option in settings flyout with inline sliders for tint opacity, luminosity, tint/fallback colors, and Base/Thin style using DesktopAcrylicController
  • Win2D 3D audio visualizer: 4 modes (Classic, Bars, Circle, Wave) with GPU-accelerated rendering via CanvasAnimatedControl, configurable color via ColorPicker, glow toggle, and persistent settings

Test plan

  • Open settings flyout → verify "Custom Acrylic" appears below Acrylic
  • Select it → verify inline sliders replace flyout content, live preview works
  • Navigate to Visualizer → verify Classic mode works as before
  • Switch to Bars/Circle/Wave → verify GPU-rendered visualizations
  • Toggle glow, change color via ColorPicker, verify Reset button
  • Close and reopen → verify all settings persisted

Summary by CodeRabbit

  • New Features

    • GPU-accelerated visualizer with multiple new modes (Bars, Circle, Wave) and a mode selector.
    • Visualizer customization: color picker, glow toggle, dark-background option, and persistent settings.
    • Custom acrylic backdrop UI with live-preview controls for tint, luminosity, color, and style.
  • Chores

    • Installer version updated to 0.0.3
  • Tweaks

    • Backdrop default values adjusted for tint and luminosity.

devohmycode and others added 4 commits March 12, 2026 12:17
Adds "Acrylic personnalisé" option to the Backdrop section that replaces
the flyout content with sliders for tint opacity, luminosity, tint/fallback
colors, and Base/Thin style. Defaults to max tint opacity and min luminosity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@qodo-code-review
Copy link

Review Summary by Qodo

Win2D 3D audio visualizer + custom Acrylic backdrop with inline settings

✨ Enhancement 🧪 Tests

Grey Divider

Walkthroughs

Description
• Add Win2D GPU-accelerated 3D audio visualizer with 4 modes (Classic, Bars, Circle, Wave)
• Implement custom Acrylic backdrop with inline settings (tint opacity, luminosity, colors, style)
• Create VisualizerRenderer orchestrating mode switching, effects pipeline (glow, reflection)
• Add visualizer settings persistence (mode, color, glow, dark background)
Diagram
flowchart LR
  A["SpectrumAnalyzer<br/>Audio data"] -->|GetSpectrum| B["VisualizerRenderer<br/>Orchestrator"]
  B -->|IsClassicMode| C["DispatcherTimer<br/>+ WaveformCanvas"]
  B -->|Win2D modes| D["CanvasAnimatedControl<br/>GPU rendering"]
  D -->|Render| E["BarsMode<br/>CircleMode<br/>WaveMode"]
  E -->|Effects| F["EffectsHelper<br/>Glow, Reflection"]
  G["DesktopAcrylicController<br/>Custom backdrop"] -->|Apply| H["MainWindow<br/>System backdrop"]
  I["Settings UI<br/>Sliders, ColorPicker"] -->|Persist| J["SettingsManager<br/>AppSettings"]
Loading

Grey Divider

File Changes

1. Audiomatic/MainWindow.xaml.cs ✨ Enhancement +364/-19

Integrate VisualizerRenderer and custom Acrylic backdrop

Audiomatic/MainWindow.xaml.cs


2. Audiomatic/SettingsManager.cs ⚙️ Configuration changes +6/-2

Add visualizer and Acrylic settings fields

Audiomatic/SettingsManager.cs


3. Audiomatic/Visualizer/BarsMode.cs ✨ Enhancement +77/-0

Implement perspective bars with depth rows

Audiomatic/Visualizer/BarsMode.cs


View more (9)
4. Audiomatic/Visualizer/CircleMode.cs ✨ Enhancement +64/-0

Implement radial 360° bar layout with rotation

Audiomatic/Visualizer/CircleMode.cs


5. Audiomatic/Visualizer/EffectsHelper.cs ✨ Enhancement +59/-0

Shared Win2D effects pipeline for glow and reflection

Audiomatic/Visualizer/EffectsHelper.cs


6. Audiomatic/Visualizer/IVisualizerMode.cs ✨ Enhancement +16/-0

Define visualizer mode interface and settings record

Audiomatic/Visualizer/IVisualizerMode.cs


7. Audiomatic/Visualizer/VisualizerRenderer.cs ✨ Enhancement +268/-0

Orchestrate mode switching, canvas, and selector UI

Audiomatic/Visualizer/VisualizerRenderer.cs


8. Audiomatic/Visualizer/WaveMode.cs ✨ Enhancement +82/-0

Implement wireframe surface grid with perspective

Audiomatic/Visualizer/WaveMode.cs


9. Audiomatic/Audiomatic.csproj Dependencies +1/-0

Add Microsoft.Graphics.Win2D NuGet dependency

Audiomatic/Audiomatic.csproj


10. Audiomatic/MainWindow.xaml ✨ Enhancement +7/-1

Add VisualizerSelector and CanvasHost grid rows

Audiomatic/MainWindow.xaml


11. docs/superpowers/specs/2026-03-12-win2d-3d-visualizer-design.md 📝 Documentation +107/-0

Design specification for Win2D visualizer architecture

docs/superpowers/specs/2026-03-12-win2d-3d-visualizer-design.md


12. docs/superpowers/plans/2026-03-12-win2d-3d-visualizer.md 📝 Documentation +1020/-0

Detailed implementation plan with step-by-step tasks

docs/superpowers/plans/2026-03-12-win2d-3d-visualizer.md


Grey Divider

Qodo Logo

@qodo-code-review
Copy link

qodo-code-review bot commented Mar 12, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Remediation recommended

1. Visualizer color parse crash 🐞 Bug ⛯ Reliability
Description
VisualizerRenderer initializes the color preview and ColorPicker with EffectsHelper.GetRenderColor,
which calls EffectsHelper.ParseColor for any 7-char '#xxxxxx' setting. Because ParseColor uses
Convert.ToByte(...,16) without validation, a persisted value like "#GGGGGG" in settings.json will
throw and crash when opening the visualizer.
Code

Audiomatic/Visualizer/EffectsHelper.cs[R10-18]

+    internal static Color ParseColor(string hex)
+    {
+        hex = (hex ?? "").TrimStart('#');
+        if (hex.Length != 6)
+            return Color.FromArgb(255, 96, 165, 250);
+        return Color.FromArgb(255,
+            Convert.ToByte(hex[..2], 16),
+            Convert.ToByte(hex[2..4], 16),
+            Convert.ToByte(hex[4..6], 16));
Evidence
A 7-character string starting with '#' reaches EffectsHelper.ParseColor via GetRenderColor;
Convert.ToByte will throw if the characters are not valid hex digits, and VisualizerRenderer calls
GetRenderColor during selector construction (no exception handling there).

Audiomatic/Visualizer/VisualizerRenderer.cs[108-124]
Audiomatic/Visualizer/EffectsHelper.cs[21-25]
Audiomatic/Visualizer/EffectsHelper.cs[10-18]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
If `settings.json` contains a malformed 7-char color string starting with `#` but with invalid hex digits (e.g. `#GGGGGG`), `EffectsHelper.ParseColor()` throws `FormatException`, crashing when building the visualizer selector.

### Issue Context
`VisualizerRenderer.BuildSelector()` calls `EffectsHelper.GetRenderColor(_settings)` to initialize a `SolidColorBrush` and `ColorPicker.Color` with no exception handling.

### Fix Focus Areas
- Audiomatic/Visualizer/EffectsHelper.cs[10-25]
- Audiomatic/Visualizer/VisualizerRenderer.cs[108-124]

### Suggested change
- Implement non-throwing parsing (TryParse per component or try/catch inside ParseColor) and return the fallback color on failure.
- Optionally make `GetRenderColor` validate hex digits in addition to length/`#`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Duplicate spectrum decoding tasks 🐞 Bug ➹ Performance
Description
Mode switches call UpdateSpectrumTimer via VisualizerRenderer.OnModeChanged, and UpdateSpectrumTimer
always calls PrepareSpectrumForCurrentTrack. SpectrumAnalyzer.PrepareAsync only short-circuits when
_decoded is non-null, so switching modes while a decode is in progress (_decoded null, _decoding
true) starts another concurrent Task.Run decode for the same file.
Code

Audiomatic/MainWindow.xaml.cs[R1734-1744]

            PrepareSpectrumForCurrentTrack();
-            int ms = _vizFps >= 60 ? 16 : 33;
-            _spectrumTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(ms) };
-            _spectrumTimer.Tick += (_, _) =>
+
+            // Initialize renderer once
+            if (_vizRenderer == null)
+            {
+                _vizRenderer = new VisualizerRenderer(
+                    _spectrum,
+                    () => _player.Position,
+                    () => _player.CurrentTrack != null);
+                _vizRenderer.OnModeChanged = () => UpdateSpectrumTimer();
+                var selector = _vizRenderer.BuildSelector();
Evidence
UpdateSpectrumTimer is invoked on every mode change and triggers PrepareSpectrumForCurrentTrack;
PrepareAsync explicitly sets _decoded=null and does not return early when _decoding is already true,
allowing re-entrancy that spawns redundant background decode work.

Audiomatic/MainWindow.xaml.cs[1732-1744]
Audiomatic/MainWindow.xaml.cs[1809-1814]
Audiomatic/Services/SpectrumAnalyzer.cs[29-36]
Audiomatic/Services/SpectrumAnalyzer.cs[67-68]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Switching visualizer modes triggers `UpdateSpectrumTimer()` via `OnModeChanged`, which calls `PrepareSpectrumForCurrentTrack()` each time. While a decode is in progress, `_decoded` is null, so `SpectrumAnalyzer.PrepareAsync()` does not early-return and can start multiple concurrent `Task.Run` decodes for the same track.

### Issue Context
This is a new behavior because mode switching now calls back into `UpdateSpectrumTimer()`.

### Fix Focus Areas
- Audiomatic/MainWindow.xaml.cs[1732-1795]
- Audiomatic/MainWindow.xaml.cs[1809-1814]
- Audiomatic/Services/SpectrumAnalyzer.cs[29-68]

### Suggested change
- In `PrepareAsync`, add a guard like:
 - `if (filePath == _currentPath &amp;&amp; (_decoded != null || _decoding)) return;`
 and/or
 - use a `SemaphoreSlim` to ensure only one decode runs at a time.
- Optionally change `PrepareSpectrumForCurrentTrack` to return `Task` and keep a per-track in-flight task to avoid reentrancy.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. UI-thread writes & controller churn 🐞 Bug ➹ Performance
Description
Custom acrylic sliders/textboxes call SettingsManager.SaveBackdrop and ApplyBackdrop on every
ValueChanged/TextChanged event, and ApplyBackdrop disposes/recreates DesktopAcrylicController each
time. Because SettingsManager.Save performs synchronous File.WriteAllText, dragging sliders can
cause UI jank and excessive resource churn.
Code

Audiomatic/MainWindow.xaml.cs[R2585-2605]

+        // Apply changes helper
+        void ApplyChanges()
+        {
+            if (suppressChanges) return;
+            var bd = new BackdropSettings(
+                Type: "acrylic_custom",
+                TintOpacity: tintOpacitySlider.Value,
+                LuminosityOpacity: luminositySlider.Value,
+                TintColor: tintColorBox.Text,
+                FallbackColor: fallbackColorBox.Text,
+                Kind: currentKind);
+            SettingsManager.SaveBackdrop(bd);
+            ApplyBackdrop(bd);
+        }
+
+        // Wire events
+        tintOpacitySlider.ValueChanged += (_, _) =>
+        {
+            tintOpacityValue.Text = tintOpacitySlider.Value.ToString("F2");
+            ApplyChanges();
+        };
Evidence
The handlers apply on every incremental change and immediately persist to disk and rebuild the
acrylic controller. SettingsManager.Save uses synchronous disk I/O, and ApplyBackdrop always
disposes the existing controller before re-creating it.

Audiomatic/MainWindow.xaml.cs[2585-2598]
Audiomatic/MainWindow.xaml.cs[2601-2609]
Audiomatic/MainWindow.xaml.cs[2360-2362]
Audiomatic/SettingsManager.cs[57-65]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The custom acrylic UI persists settings and reapplies the backdrop on every slider tick / relevant text change. This triggers synchronous disk writes and disposes/recreates `DesktopAcrylicController` repeatedly, which can stutter the UI.

### Issue Context
`Slider.ValueChanged` can fire many times per second during drag, and `SettingsManager.Save` uses synchronous `File.WriteAllText`.

### Fix Focus Areas
- Audiomatic/MainWindow.xaml.cs[2585-2622]
- Audiomatic/MainWindow.xaml.cs[2358-2385]
- Audiomatic/SettingsManager.cs[57-66]

### Suggested change
- Debounce `SaveBackdrop` (e.g., DispatcherTimer/Task.Delay + cancellation) so disk writes happen after the user stops dragging for ~200-500ms.
- When `settings.Type == &quot;acrylic_custom&quot;` and `_acrylicController` already exists, update `_acrylicController.TintOpacity/LuminosityOpacity/TintColor/FallbackColor/Kind` instead of disposing and recreating.
- Optionally persist only on PointerReleased for sliders (or when flyout closes).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 32e94879-d437-4e42-ab23-d4b7acde2710

📥 Commits

Reviewing files that changed from the base of the PR and between d8d031b and 7ecdb19.

📒 Files selected for processing (2)
  • Audiomatic/MainWindow.xaml.cs
  • installer.iss

📝 Walkthrough

Walkthrough

Adds a Win2D-based GPU audio visualizer system: new VisualizerRenderer, IVisualizerMode API, three Win2D modes (Bars, Circle, Wave), UI selector and controls, SettingsManager extensions, MainWindow integration to support classic and Win2D rendering, and a Win2D NuGet dependency.

Changes

Cohort / File(s) Summary
Project
Audiomatic/Audiomatic.csproj
Added Microsoft.Graphics.Win2D NuGet reference (v1.3.2).
Main UI
Audiomatic/MainWindow.xaml, Audiomatic/MainWindow.xaml.cs
Inserted VisualizerSelector and Win2D canvas host; integrated VisualizerRenderer, added dual rendering paths (classic DispatcherTimer vs Win2D), backdrop acrylic custom settings and flyout UI.
Settings
Audiomatic/SettingsManager.cs
Added visualizer settings to AppSettings: VisualizerMode, VisualizerColor, VisualizerGlow, VisualizerDarkBg; updated BackdropSettings defaults (TintOpacity 0.8→1.0, LuminosityOpacity 0.9→0.0).
Visualizer API & Helpers
Audiomatic/Visualizer/IVisualizerMode.cs, Audiomatic/Visualizer/EffectsHelper.cs
Introduced IVisualizerMode interface and VisualizerSettings record; added EffectsHelper for color parsing, dark background, glow and reflection utilities.
Visualizer Modes
Audiomatic/Visualizer/BarsMode.cs, Audiomatic/Visualizer/CircleMode.cs, Audiomatic/Visualizer/WaveMode.cs
Added three Win2D visualizer mode implementations (Bars, Circle, Wave) implementing IVisualizerMode, each with band-count logic, history buffers, rendering and optional glow/reflection.
Renderer
Audiomatic/Visualizer/VisualizerRenderer.cs
Added VisualizerRenderer to manage mode factory, selector UI, color picker, glow toggle, CanvasAnimatedControl lifecycle, per-frame rendering dispatch, and settings persistence.
Docs & Installer
docs/.../2026-03-12-win2d-3d-visualizer*.md, installer.iss
Added design/spec/plans documentation for the Win2D visualizer; bumped installer AppVersion to 0.0.3.

Sequence Diagram

sequenceDiagram
    participant User
    participant MainWindow
    participant VisualizerRenderer
    participant SettingsManager
    participant SpectrumAnalyzer
    participant CanvasAnimatedControl
    participant IVisualizerMode

    User->>MainWindow: open app
    MainWindow->>VisualizerRenderer: initialize
    VisualizerRenderer->>SettingsManager: load visualizer settings
    VisualizerRenderer->>VisualizerRenderer: build selector UI

    User->>VisualizerRenderer: choose mode / change color / toggle glow
    VisualizerRenderer->>SettingsManager: save settings
    VisualizerRenderer->>CanvasAnimatedControl: set visibility (Win2D modes)

    loop per-frame
        SpectrumAnalyzer->>VisualizerRenderer: provide bands
        CanvasAnimatedControl->>VisualizerRenderer: draw frame
        VisualizerRenderer->>IVisualizerMode: Render(session, bands, w, h, settings)
        IVisualizerMode->>CanvasAnimatedControl: draw visuals
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I nibble on pixels, hop through the frames,
Bars and circles ignite like tiny flames.
Glow on the canvas, reflections that play,
Classic or Win2D — I'll dance either way.
A rabbit’s cheer for visuals made bright 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main changes: custom Acrylic backdrop and Win2D 3D visualizer, both prominently represented throughout the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 0.0.3
📝 Coding Plan for PR comments
  • Generate coding plan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@devohmycode devohmycode merged commit d462e7e into main Mar 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant