Compare commits

...

1 Commits

Author SHA1 Message Date
Niels Laute
43f71f73a5 Push 2026-04-07 11:00:43 +02:00
30 changed files with 3079 additions and 3 deletions

View File

@@ -44,6 +44,7 @@
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" />
<PackageVersion Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageVersion Include="ComputeSharp.D2D1.WinUI" Version="3.2.0" />
<PackageVersion Include="Microsoft.Windows.CppWinRT" Version="2.0.250303.1" />
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="9.9.1" />

View File

@@ -340,6 +340,21 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
}
}
public int ShaderEffectIndex
{
get => (int)_settingsService.Settings.ShaderEffect;
set
{
var newEffect = (ShaderEffectType)value;
if (_settingsService.Settings.ShaderEffect != newEffect)
{
_settingsService.UpdateSettings(s => s with { ShaderEffect = newEffect });
OnPropertyChanged();
DebouncedReapply();
}
}
}
/// <summary>
/// Gets a value indicating whether the backdrop opacity slider should be visible.
/// </summary>

View File

@@ -14,6 +14,7 @@ namespace Microsoft.CmdPal.UI.ViewModels.Dock;
public partial class DockWindowViewModel : ObservableObject, IDisposable
{
private readonly IThemeService _themeService;
private readonly ISettingsService _settingsService;
private readonly DispatcherQueue _uiDispatcherQueue = DispatcherQueue.GetForCurrentThread()!;
[ObservableProperty]
@@ -49,9 +50,13 @@ public partial class DockWindowViewModel : ObservableObject, IDisposable
[ObservableProperty]
public partial double ColorizationOpacity { get; private set; }
public DockWindowViewModel(IThemeService themeService)
[ObservableProperty]
public partial ShaderEffectType ShaderEffect { get; private set; }
public DockWindowViewModel(IThemeService themeService, ISettingsService settingsService)
{
_themeService = themeService;
_settingsService = settingsService;
_themeService.ThemeChanged += ThemeService_ThemeChanged;
UpdateFromThemeSnapshot();
}
@@ -80,6 +85,8 @@ public partial class DockWindowViewModel : ObservableObject, IDisposable
ShowColorizationOverlay = snapshot.Backdrop == DockBackdrop.Transparent && snapshot.TintIntensity > 0;
ColorizationColor = snapshot.Tint;
ColorizationOpacity = snapshot.TintIntensity;
ShaderEffect = _settingsService.Settings.DockSettings.ShaderEffect;
}
public void Dispose()

View File

@@ -61,6 +61,21 @@ public sealed partial class DockAppearanceSettingsViewModel : ObservableObject,
set => Backdrop = (DockBackdrop)value;
}
public int ShaderEffectIndex
{
get => (int)_settingsService.Settings.DockSettings.ShaderEffect;
set
{
var newEffect = (ShaderEffectType)value;
if (_settingsService.Settings.DockSettings.ShaderEffect != newEffect)
{
_settingsService.UpdateSettings(s => s with { DockSettings = s.DockSettings with { ShaderEffect = newEffect } });
OnPropertyChanged();
DebouncedReapply();
}
}
}
public DockBackdrop Backdrop
{
get => _settingsService.Settings.DockSettings.Backdrop;

View File

@@ -13,6 +13,7 @@ namespace Microsoft.CmdPal.UI.ViewModels;
public partial class MainWindowViewModel : ObservableObject, IDisposable
{
private readonly IThemeService _themeService;
private readonly ISettingsService _settingsService;
private readonly DispatcherQueue _uiDispatcherQueue = DispatcherQueue.GetForCurrentThread()!;
[ObservableProperty]
@@ -50,6 +51,9 @@ public partial class MainWindowViewModel : ObservableObject, IDisposable
[NotifyPropertyChangedFor(nameof(EffectiveImageOpacity))]
public partial float BackdropOpacity { get; private set; } = 1.0f;
[ObservableProperty]
public partial ShaderEffectType ShaderEffect { get; private set; }
// Returns null when no transparency needed (BlurImageControl uses this to decide source type)
public BackdropStyle? EffectiveBackdropStyle =>
BackdropStyle == BackdropStyle.Clear ||
@@ -64,9 +68,10 @@ public partial class MainWindowViewModel : ObservableObject, IDisposable
? BackgroundImageOpacity * Math.Sqrt(BackdropOpacity)
: BackgroundImageOpacity;
public MainWindowViewModel(IThemeService themeService)
public MainWindowViewModel(IThemeService themeService, ISettingsService settingsService)
{
_themeService = themeService;
_settingsService = settingsService;
_themeService.ThemeChanged += ThemeService_ThemeChanged;
}
@@ -86,6 +91,8 @@ public partial class MainWindowViewModel : ObservableObject, IDisposable
BackdropStyle = _themeService.Current.BackdropParameters.Style;
BackdropOpacity = _themeService.Current.BackdropOpacity;
ShaderEffect = _settingsService.Settings.ShaderEffect;
ShowBackgroundImage = BackgroundImageSource != null;
});
}

View File

@@ -45,6 +45,8 @@ public record DockSettings
public string? BackgroundImagePath { get; init; }
public ShaderEffectType ShaderEffect { get; init; }
// </Theme settings>
public ImmutableList<DockBandSettings> StartBands { get; init; } = ImmutableList.Create(
new DockBandSettings

View File

@@ -89,6 +89,8 @@ public record SettingsModel
public int BackdropOpacity { get; init; } = 100;
public ShaderEffectType ShaderEffect { get; init; }
// </Theme settings>
// END SETTINGS
@@ -170,6 +172,7 @@ public record SettingsModel
[JsonSerializable(typeof(ImmutableDictionary<string, CommandAlias>), TypeInfoPropertyName = "ImmutableAliasDictionary")]
[JsonSerializable(typeof(ImmutableList<TopLevelHotkey>), TypeInfoPropertyName = "ImmutableTopLevelHotkeyList")]
[JsonSerializable(typeof(Dictionary<string, object>), TypeInfoPropertyName = "Dictionary")]
[JsonSerializable(typeof(ShaderEffectType))]
[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true, IncludeFields = true, PropertyNameCaseInsensitive = true, AllowTrailingCommas = true)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Just used here")]
internal sealed partial class JsonSerializationContext : JsonSerializerContext

View File

@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels;
/// <summary>
/// Specifies which GPU pixel shader effect to display.
/// </summary>
public enum ShaderEffectType
{
Off,
AudioGlow,
Alchemy,
MilkDrop,
WaveformTunnel,
RadialSpectrum,
Kaleidoscope,
KaleidoTunnel,
WaveformSpace,
}

View File

@@ -0,0 +1,124 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Runtime.InteropServices;
namespace Microsoft.CmdPal.UI.Controls.AmbientEffects.Audio;
#pragma warning disable SA1401
#pragma warning disable SA1307
#pragma warning disable SA1310
#pragma warning disable SA1402
#pragma warning disable SA1649
internal static class AudioConstants
{
public const int AUDCLNT_STREAMFLAGS_LOOPBACK = 0x00020000;
public const int AUDCLNT_BUFFERFLAGS_SILENT = 0x2;
public const uint CLSCTX_ALL = 0x17;
public const int STGM_READ = 0;
public const int EDataFlow_eRender = 0;
public const int ERole_eConsole = 0;
}
[StructLayout(LayoutKind.Sequential)]
internal struct WaveFormatEx
{
public ushort wFormatTag;
public ushort nChannels;
public uint nSamplesPerSec;
public uint nAvgBytesPerSec;
public ushort nBlockAlign;
public ushort wBitsPerSample;
public ushort cbSize;
}
[ComImport]
[Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
internal class MMDeviceEnumeratorClass
{
}
[ComImport]
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IMMDeviceEnumerator
{
[PreserveSig]
int EnumAudioEndpoints(int dataFlow, uint stateMask, out IntPtr devices);
[PreserveSig]
int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice device);
}
[ComImport]
[Guid("D666063F-1587-4E43-81F1-B948E807363F")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IMMDevice
{
[PreserveSig]
int Activate(ref Guid iid, uint clsCtx, IntPtr activationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
}
[ComImport]
[Guid("1CB9AD4C-DBFA-4c32-B178-C2F568A703B2")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IAudioClient
{
[PreserveSig]
int Initialize(int shareMode, int streamFlags, long bufferDuration, long periodicity, IntPtr pFormat, IntPtr audioSessionGuid);
[PreserveSig]
int GetBufferSize(out uint bufferFrameCount);
[PreserveSig]
int GetStreamLatency(out long latency);
[PreserveSig]
int GetCurrentPadding(out uint numPaddingFrames);
[PreserveSig]
int IsFormatSupported(int shareMode, IntPtr pFormat, out IntPtr closestMatch);
[PreserveSig]
int GetMixFormat(out IntPtr pFormat);
[PreserveSig]
int GetDevicePeriod(out long defaultDevicePeriod, out long minimumDevicePeriod);
[PreserveSig]
int Start();
[PreserveSig]
int Stop();
[PreserveSig]
int Reset();
[PreserveSig]
int SetEventHandle(IntPtr eventHandle);
[PreserveSig]
int GetService(ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppv);
}
[ComImport]
[Guid("C8ADBD64-E71E-48a0-A4DE-185C395CD317")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IAudioCaptureClient
{
[PreserveSig]
int GetBuffer(out IntPtr dataPtr, out uint numFramesAvailable, out int flags, out long devicePosition, out long qpcPosition);
[PreserveSig]
int ReleaseBuffer(uint numFramesRead);
[PreserveSig]
int GetNextPacketSize(out uint numFramesInNextPacket);
}
#pragma warning restore SA1310
#pragma warning restore SA1307
#pragma warning restore SA1401
#pragma warning restore SA1402
#pragma warning restore SA1649

View File

@@ -0,0 +1,306 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Microsoft.CmdPal.UI.Controls.AmbientEffects.Audio;
/// <summary>
/// Captures system audio via WASAPI loopback, runs FFT, and exposes
/// real-time frequency band levels. Runs on a dedicated background thread.
/// </summary>
internal sealed class AudioLoopbackService : IDisposable
{
private const int FFTSize = 1024;
private const int HalfFFT = FFTSize / 2;
private const float AttackCoeff = 0.8f;
private const float DecayCoeff = 0.12f;
private static readonly Guid AudioClientGuid = new("1CB9AD4C-DBFA-4c32-B178-C2F568A703B2");
private static readonly Guid AudioCaptureClientGuid = new("C8ADBD64-E71E-48a0-A4DE-185C395CD317");
private readonly object _lock = new();
private readonly float[] _smoothedBands;
private readonly int _bandCount;
private readonly int[] _bandBinEdges;
private IAudioClient? _audioClient;
private IAudioCaptureClient? _captureClient;
private Thread? _captureThread;
private volatile bool _running;
private uint _sampleRate;
private ushort _channels;
private bool _isFloat;
public bool IsCapturing => _running;
public AudioLoopbackService(int bandCount = 48)
{
_bandCount = bandCount;
_smoothedBands = new float[bandCount];
_bandBinEdges = ComputeLogBandEdges(bandCount, HalfFFT, 20f, 20000f);
}
public bool Start()
{
try
{
var enumerator = (IMMDeviceEnumerator)new MMDeviceEnumeratorClass();
var hr = enumerator.GetDefaultAudioEndpoint(
AudioConstants.EDataFlow_eRender,
AudioConstants.ERole_eConsole,
out var device);
if (hr != 0 || device == null)
{
return false;
}
var audioClientGuid = AudioClientGuid;
hr = device.Activate(ref audioClientGuid, AudioConstants.CLSCTX_ALL, IntPtr.Zero, out var clientObj);
if (hr != 0 || clientObj == null)
{
return false;
}
_audioClient = (IAudioClient)clientObj;
hr = _audioClient.GetMixFormat(out var formatPtr);
if (hr != 0 || formatPtr == IntPtr.Zero)
{
return false;
}
var format = Marshal.PtrToStructure<WaveFormatEx>(formatPtr);
_sampleRate = format.nSamplesPerSec;
_channels = format.nChannels;
_isFloat = format.wFormatTag == 3 || (format.wFormatTag == 0xFFFE && format.wBitsPerSample == 32);
hr = _audioClient.Initialize(
0, // AUDCLNT_SHAREMODE_SHARED
AudioConstants.AUDCLNT_STREAMFLAGS_LOOPBACK,
1000000, // 100ms in 100-ns units
0,
formatPtr,
IntPtr.Zero);
Marshal.FreeCoTaskMem(formatPtr);
if (hr != 0)
{
return false;
}
var captureGuid = AudioCaptureClientGuid;
hr = _audioClient.GetService(ref captureGuid, out var captureObj);
if (hr != 0 || captureObj == null)
{
return false;
}
_captureClient = (IAudioCaptureClient)captureObj;
_audioClient.Start();
_running = true;
_captureThread = new Thread(CaptureLoop) { IsBackground = true, Name = "AudioLoopback" };
_captureThread.Start();
return true;
}
catch (Exception ex)
{
Debug.WriteLine($"Failed to initialize audio loopback: {ex.Message}");
return false;
}
}
public void GetBandLevels(float[] output)
{
lock (_lock)
{
var count = Math.Min(output.Length, _smoothedBands.Length);
Array.Copy(_smoothedBands, output, count);
}
}
public void Dispose()
{
_running = false;
_captureThread?.Join(500);
_captureThread = null;
try
{
_audioClient?.Stop();
}
catch
{
}
if (_captureClient is IDisposable captureDisp)
{
captureDisp.Dispose();
}
if (_audioClient is IDisposable clientDisp)
{
clientDisp.Dispose();
}
_captureClient = null;
_audioClient = null;
}
private void CaptureLoop()
{
var fftBuffer = new float[FFTSize * 2];
var magnitudes = new float[HalfFFT];
var sampleAccumulator = new float[FFTSize];
var accumulatedCount = 0;
while (_running)
{
Thread.Sleep(30);
if (_captureClient == null)
{
break;
}
try
{
DrainSamples(sampleAccumulator, ref accumulatedCount);
if (accumulatedCount >= FFTSize)
{
ProcessFFT(sampleAccumulator, fftBuffer, magnitudes);
UpdateBands(magnitudes);
accumulatedCount = 0;
}
}
catch
{
break;
}
}
}
private void DrainSamples(float[] accumulator, ref int count)
{
if (_captureClient == null)
{
return;
}
while (true)
{
var hr = _captureClient.GetNextPacketSize(out var packetSize);
if (hr != 0 || packetSize == 0)
{
break;
}
hr = _captureClient.GetBuffer(out var dataPtr, out var numFrames, out var flags, out _, out _);
if (hr != 0)
{
break;
}
var isSilent = (flags & AudioConstants.AUDCLNT_BUFFERFLAGS_SILENT) != 0;
var channels = Math.Max(_channels, (ushort)1);
for (uint f = 0; f < numFrames && count < FFTSize; f++)
{
if (isSilent)
{
accumulator[count++] = 0f;
}
else if (_isFloat)
{
var sum = 0f;
for (var ch = 0; ch < channels; ch++)
{
sum += Marshal.PtrToStructure<float>(dataPtr + ((((int)f * channels) + ch) * sizeof(float)));
}
accumulator[count++] = sum / channels;
}
else
{
var sum = 0f;
for (var ch = 0; ch < channels; ch++)
{
var sample = Marshal.PtrToStructure<short>(dataPtr + ((((int)f * channels) + ch) * sizeof(short)));
sum += sample / 32768f;
}
accumulator[count++] = sum / channels;
}
}
_captureClient.ReleaseBuffer(numFrames);
}
}
private static void ProcessFFT(float[] samples, float[] fftBuffer, float[] magnitudes)
{
SimpleFFT.ApplyHanningWindow(samples, FFTSize);
Array.Clear(fftBuffer);
for (var i = 0; i < FFTSize; i++)
{
fftBuffer[2 * i] = samples[i];
}
SimpleFFT.ComputeFFT(fftBuffer, FFTSize);
SimpleFFT.GetMagnitudes(fftBuffer, magnitudes, FFTSize);
}
private void UpdateBands(float[] magnitudes)
{
lock (_lock)
{
for (var b = 0; b < _bandCount; b++)
{
var startBin = _bandBinEdges[b];
var endBin = _bandBinEdges[b + 1];
endBin = Math.Max(endBin, startBin + 1);
var sum = 0f;
for (var i = startBin; i < endBin && i < magnitudes.Length; i++)
{
sum += magnitudes[i];
}
var raw = sum / (endBin - startBin) * 25f;
raw = Math.Clamp(raw, 0f, 1f);
if (raw > _smoothedBands[b])
{
_smoothedBands[b] += (raw - _smoothedBands[b]) * AttackCoeff;
}
else
{
_smoothedBands[b] += (raw - _smoothedBands[b]) * DecayCoeff;
}
}
}
}
private static int[] ComputeLogBandEdges(int bandCount, int totalBins, float minFreq, float maxFreq)
{
var edges = new int[bandCount + 1];
var logMin = MathF.Log10(minFreq);
var logMax = MathF.Log10(maxFreq);
for (var i = 0; i <= bandCount; i++)
{
var logFreq = logMin + ((logMax - logMin) * i / bandCount);
var freq = MathF.Pow(10, logFreq);
var bin = (int)(freq / 20000f * totalBins);
edges[i] = Math.Clamp(bin, 0, totalBins);
}
return edges;
}
}

View File

@@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.Controls.AmbientEffects.Audio;
/// <summary>
/// Minimal in-place Cooley-Tukey radix-2 FFT.
/// Operates on interleaved real/imaginary float arrays.
/// </summary>
internal static class SimpleFFT
{
public static void ApplyHanningWindow(float[] samples, int count)
{
for (var i = 0; i < count; i++)
{
var multiplier = 0.5f * (1f - MathF.Cos((2f * MathF.PI * i) / (count - 1)));
samples[i] *= multiplier;
}
}
/// <summary>
/// Computes an in-place FFT on interleaved real/imaginary data.
/// data must have length = 2 * N where N is a power of 2.
/// data[2*k] = real part, data[2*k+1] = imaginary part.
/// </summary>
public static void ComputeFFT(float[] data, int n)
{
var j = 0;
for (var i = 0; i < n - 1; i++)
{
if (i < j)
{
(data[2 * i], data[2 * j]) = (data[2 * j], data[2 * i]);
(data[(2 * i) + 1], data[(2 * j) + 1]) = (data[(2 * j) + 1], data[(2 * i) + 1]);
}
var m = n >> 1;
while (m >= 1 && j >= m)
{
j -= m;
m >>= 1;
}
j += m;
}
for (var step = 1; step < n; step <<= 1)
{
var angleStep = -MathF.PI / step;
var wR = MathF.Cos(angleStep);
var wI = MathF.Sin(angleStep);
for (var group = 0; group < n; group += step << 1)
{
var twR = 1f;
var twI = 0f;
for (var pair = 0; pair < step; pair++)
{
var even = group + pair;
var odd = even + step;
var oddR = data[2 * odd];
var oddI = data[(2 * odd) + 1];
var tR = (twR * oddR) - (twI * oddI);
var tI = (twR * oddI) + (twI * oddR);
data[2 * odd] = data[2 * even] - tR;
data[(2 * odd) + 1] = data[(2 * even) + 1] - tI;
data[2 * even] += tR;
data[(2 * even) + 1] += tI;
var newTwR = (twR * wR) - (twI * wI);
twI = (twR * wI) + (twI * wR);
twR = newTwR;
}
}
}
}
/// <summary>
/// Extracts magnitude spectrum from interleaved FFT result.
/// Returns N/2 magnitudes (only positive frequencies).
/// </summary>
public static void GetMagnitudes(float[] fftData, float[] magnitudes, int n)
{
for (var i = 0; i < n / 2; i++)
{
var re = fftData[2 * i];
var im = fftData[(2 * i) + 1];
magnitudes[i] = MathF.Sqrt((re * re) + (im * im)) / n;
}
}
}

View File

@@ -0,0 +1,138 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.UI.Controls.ShaderEffects;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.Graphics.Canvas.UI;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.CmdPal.UI.Controls;
/// <summary>
/// Hosts a CanvasAnimatedControl that renders GPU pixel shader effects
/// driven by system audio. Drop into any layout as a non-interactive overlay.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Disposed in Unloaded handler")]
internal sealed partial class ShaderEffectControl : UserControl
{
public static readonly DependencyProperty EffectTypeProperty =
DependencyProperty.Register(
nameof(EffectType),
typeof(ShaderEffectType),
typeof(ShaderEffectControl),
new PropertyMetadata(ShaderEffectType.Off, OnEffectTypeChanged));
private ShaderRenderer? _renderer;
private CanvasAnimatedControl? _canvas;
private bool _isLoaded;
public ShaderEffectType EffectType
{
get => (ShaderEffectType)GetValue(EffectTypeProperty);
set => SetValue(EffectTypeProperty, value);
}
public ShaderEffectControl()
{
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
_isLoaded = true;
EnsureCanvas();
ApplyEffect();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
_isLoaded = false;
CleanupCanvas();
}
private static void OnEffectTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ShaderEffectControl control && control._isLoaded)
{
control.ApplyEffect();
}
}
private void ApplyEffect()
{
if (EffectType == ShaderEffectType.Off)
{
CleanupCanvas();
Visibility = Visibility.Collapsed;
return;
}
EnsureCanvas();
Visibility = Visibility.Visible;
if (_renderer != null)
{
_renderer.CurrentEffect = EffectType switch
{
ShaderEffectType.AudioGlow => ShaderEffects.EffectType.AudioGlow,
ShaderEffectType.Alchemy => ShaderEffects.EffectType.Alchemy,
ShaderEffectType.MilkDrop => ShaderEffects.EffectType.WinampScope,
ShaderEffectType.WaveformTunnel => ShaderEffects.EffectType.WaveformTunnel,
ShaderEffectType.RadialSpectrum => ShaderEffects.EffectType.RadialSpectrum,
ShaderEffectType.Kaleidoscope => ShaderEffects.EffectType.Kaleidoscope,
ShaderEffectType.KaleidoTunnel => ShaderEffects.EffectType.KaleidoTunnel,
ShaderEffectType.WaveformSpace => ShaderEffects.EffectType.WaveformSpace,
_ => ShaderEffects.EffectType.AudioGlow,
};
}
}
private void EnsureCanvas()
{
if (_canvas != null)
{
return;
}
_canvas = new CanvasAnimatedControl
{
ClearColor = global::Windows.UI.Color.FromArgb(0, 0, 0, 0),
IsFixedTimeStep = false,
};
_canvas.CreateResources += OnCreateResources;
_canvas.Draw += OnDraw;
Content = _canvas;
}
private void CleanupCanvas()
{
if (_canvas != null)
{
_canvas.CreateResources -= OnCreateResources;
_canvas.Draw -= OnDraw;
_canvas.RemoveFromVisualTree();
_canvas = null;
}
_renderer?.Dispose();
_renderer = null;
Content = null;
}
private void OnCreateResources(CanvasAnimatedControl sender, CanvasCreateResourcesEventArgs args)
{
_renderer = new ShaderRenderer();
_renderer.OnCreateResources(sender, args);
ApplyEffect();
}
private void OnDraw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
{
_renderer?.OnDraw(sender, args);
}
}

View File

@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1407:ArithmeticExpressionsMustDeclarePrecedence", Justification = "Shader arithmetic", Scope = "namespaceanddescendants", Target = "~N:Microsoft.CmdPal.UI.Controls.ShaderEffects")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1407:ArithmeticExpressionsMustDeclarePrecedence", Justification = "Shader arithmetic", Scope = "namespaceanddescendants", Target = "~N:Microsoft.CmdPal.UI.Controls.ShaderEffects.Shaders")]
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1214:ReadonlyElementsMustAppearBeforeNonReadonlyElements", Justification = "Field ordering", Scope = "namespaceanddescendants", Target = "~N:Microsoft.CmdPal.UI.Controls.ShaderEffects")]
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1505:OpeningBracesMustNotBeFollowedByBlankLine", Justification = "Shader code style", Scope = "namespaceanddescendants", Target = "~N:Microsoft.CmdPal.UI.Controls.ShaderEffects")]
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1505:OpeningBracesMustNotBeFollowedByBlankLine", Justification = "Shader code style", Scope = "namespaceanddescendants", Target = "~N:Microsoft.CmdPal.UI.Controls.ShaderEffects.Shaders")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1107:CodeMustNotContainMultipleStatementsOnOneLine", Justification = "Shader code compactness", Scope = "namespaceanddescendants", Target = "~N:Microsoft.CmdPal.UI.Controls.ShaderEffects.Shaders")]

View File

@@ -0,0 +1,396 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
using ComputeSharp;
using ComputeSharp.D2D1.WinUI;
using Microsoft.CmdPal.UI.Controls.AmbientEffects.Audio;
using Microsoft.CmdPal.UI.Controls.ShaderEffects.Shaders;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.UI;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.Controls.ShaderEffects;
/// <summary>
/// Active effect type.
/// </summary>
public enum EffectType
{
AudioGlow,
Alchemy,
WinampScope,
WaveformTunnel,
RadialSpectrum,
Kaleidoscope,
KaleidoTunnel,
WaveformSpace,
}
/// <summary>
/// Manages the rendering loop, audio capture, and shader execution.
/// Bridges the CanvasAnimatedControl (Win2D) with ComputeSharp pixel shaders.
/// </summary>
public sealed class ShaderRenderer : IDisposable
{
private const int BandCount = 12;
private const float HueRotationSpeed = 0.0033f;
private AudioLoopbackService? _audioService;
private readonly float[] _levels = new float[BandCount];
private readonly Stopwatch _clock = new();
private bool _audioAvailable;
private float _hueOffset;
private Rect _cropRect;
private PixelShaderEffect<AudioGlowShader>? _audioGlowEffect;
private PixelShaderEffect<AlchemyShader>? _alchemyEffect;
private PixelShaderEffect<WinampScopeShader>? _winampScopeEffect;
private PixelShaderEffect<WaveformTunnelShader>? _waveformTunnelEffect;
private PixelShaderEffect<RadialSpectrumShader>? _radialSpectrumEffect;
private PixelShaderEffect<KaleidoscopeShader>? _kaleidoscopeEffect;
private PixelShaderEffect<KaleidoTunnelShader>? _kaleidoTunnelEffect;
private PixelShaderEffect<WaveformSpaceShader>? _waveformSpaceEffect;
public EffectType CurrentEffect { get; set; } = EffectType.AudioGlow;
public void OnCreateResources(CanvasAnimatedControl sender, CanvasCreateResourcesEventArgs args)
{
_audioGlowEffect = new PixelShaderEffect<AudioGlowShader>();
_alchemyEffect = new PixelShaderEffect<AlchemyShader>();
_winampScopeEffect = new PixelShaderEffect<WinampScopeShader>();
_waveformTunnelEffect = new PixelShaderEffect<WaveformTunnelShader>();
_radialSpectrumEffect = new PixelShaderEffect<RadialSpectrumShader>();
_kaleidoscopeEffect = new PixelShaderEffect<KaleidoscopeShader>();
_kaleidoTunnelEffect = new PixelShaderEffect<KaleidoTunnelShader>();
_waveformSpaceEffect = new PixelShaderEffect<WaveformSpaceShader>();
_audioService = new AudioLoopbackService(BandCount);
_audioAvailable = _audioService.Start();
if (!_audioAvailable)
{
_audioService.Dispose();
_audioService = null;
}
_clock.Start();
}
public void OnDraw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
{
var size = sender.Size;
// D2D.GetScenePosition() returns pixel coordinates, so scale by DPI
float dpiScale = sender.Dpi / 96f;
int w = (int)Math.Round(size.Width * dpiScale);
int h = (int)Math.Round(size.Height * dpiScale);
if (w <= 0 || h <= 0)
{
return;
}
UpdateAudio();
float elapsed = (float)_clock.Elapsed.TotalSeconds;
// CropEffect SourceRectangle must match shader pixel dimensions
_cropRect = new Rect(0, 0, w, h);
switch (CurrentEffect)
{
case EffectType.AudioGlow:
DrawAudioGlow(args, w, h, elapsed);
break;
case EffectType.Alchemy:
DrawAlchemy(args, w, h, elapsed);
break;
case EffectType.WinampScope:
DrawWinampScope(args, w, h, elapsed);
break;
case EffectType.WaveformTunnel:
DrawWaveformTunnel(args, w, h, elapsed);
break;
case EffectType.RadialSpectrum:
DrawRadialSpectrum(args, w, h, elapsed);
break;
case EffectType.Kaleidoscope:
DrawKaleidoscope(args, w, h, elapsed);
break;
case EffectType.KaleidoTunnel:
DrawKaleidoTunnel(args, w, h, elapsed);
break;
case EffectType.WaveformSpace:
DrawWaveformSpace(args, w, h, elapsed);
break;
}
}
private void DrawAudioGlow(CanvasAnimatedDrawEventArgs args, int w, int h, float elapsed)
{
if (_audioGlowEffect == null)
{
return;
}
// Advance hue for continuous color morphing
_hueOffset += HueRotationSpeed;
if (_hueOffset >= 1f)
{
_hueOffset -= 1f;
}
// Compute overall energy
float overall = 0f;
for (int i = 0; i < _levels.Length; i++)
{
overall += _levels[i];
}
overall /= _levels.Length;
_audioGlowEffect.ConstantBuffer = new AudioGlowShader(
time: elapsed,
hueOffset: _hueOffset,
overallEnergy: overall,
band0: _levels[0],
band1: _levels[1],
band2: _levels[2],
band3: _levels[3],
band4: _levels[4],
width: w,
height: h);
using var crop = new CropEffect
{
Source = _audioGlowEffect,
SourceRectangle = _cropRect,
};
args.DrawingSession.DrawImage(crop);
}
private void DrawAlchemy(CanvasAnimatedDrawEventArgs args, int w, int h, float elapsed)
{
if (_alchemyEffect == null)
{
return;
}
// Bass = average of lowest 2 bands
float bass = (_levels[0] + _levels[1]) * 0.5f;
// Overall energy
float overall = 0f;
for (int i = 0; i < _levels.Length; i++)
{
overall += _levels[i];
}
overall /= _levels.Length;
_alchemyEffect.ConstantBuffer = new AlchemyShader(
time: elapsed,
bassLevel: bass,
overallEnergy: overall,
width: w,
height: h,
outerBands0: new float4(_levels[0], _levels[1], _levels[2], _levels[3]),
outerBands1: new float4(_levels[4], _levels[5], _levels[6], _levels[7]),
innerBands: new float4(_levels[8], _levels[9], _levels[10], _levels[11]));
using var crop = new CropEffect
{
Source = _alchemyEffect,
SourceRectangle = _cropRect,
};
args.DrawingSession.DrawImage(crop);
}
private void DrawWinampScope(CanvasAnimatedDrawEventArgs args, int w, int h, float elapsed)
{
if (_winampScopeEffect == null)
{
return;
}
_winampScopeEffect.ConstantBuffer = new WinampScopeShader(
time: elapsed,
width: w,
height: h,
bands0: new float4(_levels[0], _levels[1], _levels[2], _levels[3]),
bands1: new float4(_levels[4], _levels[5], _levels[6], _levels[7]),
bands2: new float4(_levels[8], _levels[9], _levels[10], _levels[11]));
using var crop = new CropEffect
{
Source = _winampScopeEffect,
SourceRectangle = _cropRect,
};
args.DrawingSession.DrawImage(crop);
}
private void DrawWaveformTunnel(CanvasAnimatedDrawEventArgs args, int w, int h, float elapsed)
{
if (_waveformTunnelEffect == null)
{
return;
}
_waveformTunnelEffect.ConstantBuffer = new WaveformTunnelShader(
time: elapsed,
width: w,
height: h,
bands0: new float4(_levels[0], _levels[1], _levels[2], _levels[3]),
bands1: new float4(_levels[4], _levels[5], _levels[6], _levels[7]),
bands2: new float4(_levels[8], _levels[9], _levels[10], _levels[11]));
using var crop = new CropEffect
{
Source = _waveformTunnelEffect,
SourceRectangle = _cropRect,
};
args.DrawingSession.DrawImage(crop);
}
private void DrawRadialSpectrum(CanvasAnimatedDrawEventArgs args, int w, int h, float elapsed)
{
if (_radialSpectrumEffect == null)
{
return;
}
_radialSpectrumEffect.ConstantBuffer = new RadialSpectrumShader(
time: elapsed,
width: w,
height: h,
bands0: new float4(_levels[0], _levels[1], _levels[2], _levels[3]),
bands1: new float4(_levels[4], _levels[5], _levels[6], _levels[7]),
bands2: new float4(_levels[8], _levels[9], _levels[10], _levels[11]));
using var crop = new CropEffect
{
Source = _radialSpectrumEffect,
SourceRectangle = _cropRect,
};
args.DrawingSession.DrawImage(crop);
}
private void DrawKaleidoscope(CanvasAnimatedDrawEventArgs args, int w, int h, float elapsed)
{
if (_kaleidoscopeEffect == null)
{
return;
}
_kaleidoscopeEffect.ConstantBuffer = new KaleidoscopeShader(
time: elapsed,
width: w,
height: h,
bands0: new float4(_levels[0], _levels[1], _levels[2], _levels[3]),
bands1: new float4(_levels[4], _levels[5], _levels[6], _levels[7]),
bands2: new float4(_levels[8], _levels[9], _levels[10], _levels[11]));
using var crop = new CropEffect
{
Source = _kaleidoscopeEffect,
SourceRectangle = _cropRect,
};
args.DrawingSession.DrawImage(crop);
}
private void DrawKaleidoTunnel(CanvasAnimatedDrawEventArgs args, int w, int h, float elapsed)
{
if (_kaleidoTunnelEffect == null)
{
return;
}
_kaleidoTunnelEffect.ConstantBuffer = new KaleidoTunnelShader(
time: elapsed,
width: w,
height: h,
bands0: new float4(_levels[0], _levels[1], _levels[2], _levels[3]),
bands1: new float4(_levels[4], _levels[5], _levels[6], _levels[7]),
bands2: new float4(_levels[8], _levels[9], _levels[10], _levels[11]));
using var crop = new CropEffect
{
Source = _kaleidoTunnelEffect,
SourceRectangle = _cropRect,
};
args.DrawingSession.DrawImage(crop);
}
private void DrawWaveformSpace(CanvasAnimatedDrawEventArgs args, int w, int h, float elapsed)
{
if (_waveformSpaceEffect == null)
{
return;
}
_waveformSpaceEffect.ConstantBuffer = new WaveformSpaceShader(
time: elapsed,
width: w,
height: h,
bands0: new float4(_levels[0], _levels[1], _levels[2], _levels[3]),
bands1: new float4(_levels[4], _levels[5], _levels[6], _levels[7]),
bands2: new float4(_levels[8], _levels[9], _levels[10], _levels[11]));
using var crop = new CropEffect
{
Source = _waveformSpaceEffect,
SourceRectangle = _cropRect,
};
args.DrawingSession.DrawImage(crop);
}
private void UpdateAudio()
{
if (_audioAvailable && _audioService != null)
{
_audioService.GetBandLevels(_levels);
}
else
{
// Fallback: moderate idle animation
float time = (float)(Environment.TickCount64 / 1000.0);
for (int i = 0; i < _levels.Length; i++)
{
_levels[i] = 0.1f + 0.08f * MathF.Sin((time * 1.5f) + (i * 0.5f));
}
}
}
public void Dispose()
{
_clock.Stop();
_audioService?.Dispose();
_audioService = null;
_audioGlowEffect?.Dispose();
_alchemyEffect?.Dispose();
_winampScopeEffect?.Dispose();
_waveformTunnelEffect?.Dispose();
_radialSpectrumEffect?.Dispose();
_kaleidoscopeEffect?.Dispose();
_kaleidoTunnelEffect?.Dispose();
_waveformSpaceEffect?.Dispose();
_audioGlowEffect = null;
_alchemyEffect = null;
_winampScopeEffect = null;
_waveformTunnelEffect = null;
_radialSpectrumEffect = null;
_kaleidoscopeEffect = null;
_kaleidoTunnelEffect = null;
_waveformSpaceEffect = null;
}
}

View File

@@ -0,0 +1,274 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ComputeSharp;
using ComputeSharp.D2D1;
namespace Microsoft.CmdPal.UI.Controls.ShaderEffects.Shaders;
/// <summary>
/// WMP "Alchemy" starburst visualization — concentric geometric rays rotating
/// in opposite directions, pulsing with audio, plus a breathing ring and
/// bass-reactive center orb. All rendered in a single pixel shader.
/// </summary>
[D2DInputCount(0)]
[D2DRequiresScenePosition]
[D2DShaderProfile(D2D1ShaderProfile.PixelShader50)]
[D2DGeneratedPixelShaderDescriptor]
public readonly partial struct AlchemyShader(
float time,
float bassLevel,
float overallEnergy,
int width,
int height,
float4 outerBands0,
float4 outerBands1,
float4 innerBands) : ID2D1PixelShader
{
public float4 Execute()
{
float2 pos = D2D.GetScenePosition().XY;
float w = (float)width;
float h = (float)height;
float2 center = new float2(w * 0.5f, h * 0.5f);
float dx = pos.X - center.X;
float dy = pos.Y - center.Y;
float r = Hlsl.Sqrt(dx * dx + dy * dy);
float theta = Hlsl.Atan2(dy, dx);
float span = Hlsl.Min(w, h);
float outerLen = span * 0.45f;
float innerLen = outerLen * 0.5f;
float4 result = new float4(0f, 0f, 0f, 0f);
// 1. Outer ring
float ringRadius = outerLen * 1.25f;
float ringScale = 0.85f + 0.4f * overallEnergy;
float ringDist = r / (ringRadius * ringScale);
float ringAlpha = SampleRingGradient(ringDist);
float ringOp = (0.1f + 0.6f * overallEnergy) * ringAlpha;
result += new float4(0.392f * ringOp, 0.706f * ringOp, 1f * ringOp, ringOp);
// 2. Outer rays — 24 rays, clockwise rotation
float cwAngle = time * 6.28318f / 30f;
result += ComputeOuterRays(theta, r, cwAngle, outerLen);
// 3. Inner rays — 14 rays, counter-clockwise
float ccwAngle = -time * 6.28318f / 18f;
result += ComputeInnerRays(theta, r, ccwAngle, innerLen);
// 4. Center orb
float orbRadius = span * 0.2f;
float orbScale = Hlsl.Max(0.2f + 3.5f * bassLevel, 0.1f);
float orbDist = r / (orbRadius * orbScale);
float orbA = SampleOrbAlpha(orbDist) * (0.3f + 0.7f * bassLevel);
float3 orbCol = SampleOrbColor(orbDist);
result += new float4(orbCol.X * orbA, orbCol.Y * orbA, orbCol.Z * orbA, orbA);
// Boost with audio: baseline visible, audio intensifies
float intensityMul = 0.35f + 1.5f * overallEnergy;
return Hlsl.Saturate(result * intensityMul);
}
private float4 ComputeOuterRays(float theta, float r, float rotAngle, float maxLength)
{
float sectorAngle = 6.28318f / 24f;
float adjusted = theta - rotAngle + 628.318f;
float rayIdxF = Hlsl.Floor(adjusted / sectorAngle);
float inSector = adjusted - rayIdxF * sectorAngle;
float dTheta = inSector - sectorAngle * 0.5f;
float wrapped = rayIdxF - Hlsl.Floor(rayIdxF / 24f) * 24f;
int nearestRay = (int)wrapped;
int bandGroup = (int)Hlsl.Floor(wrapped / 3f);
float level = GetOuterBand(bandGroup);
float basePulse = 0.15f + 0.1f * Hlsl.Sin(time * 6.28318f / (3f + nearestRay * 0.15f));
level = Hlsl.Max(level, basePulse);
float rayWidth = 5f * (0.2f + 4f * level);
float rayLength = maxLength * Hlsl.Max(level * 3f, 0.15f);
float visible = Hlsl.Step(1f, r) * Hlsl.Step(r, rayLength);
if (visible < 0.5f)
{
return new float4(0f, 0f, 0f, 0f);
}
float halfAngle = rayWidth / (2f * r);
float angularFalloff = 1f - Hlsl.SmoothStep(halfAngle * 0.3f, halfAngle, Hlsl.Abs(dTheta));
if (angularFalloff <= 0f)
{
return new float4(0f, 0f, 0f, 0f);
}
float t = r / rayLength;
// Alternate colors: even=OuterColor(0.392, 0.706, 1.0), odd=AccentColor(0.392, 1.0, 0.706)
float isEven = 1f - Hlsl.Step(0.5f, Hlsl.Frac(nearestRay * 0.5f));
float3 primaryRGB = new float3(
0.392f,
Hlsl.Lerp(1f, 0.706f, isEven),
Hlsl.Lerp(0.706f, 1f, isEven));
float4 gradColor = SampleRayGradient(t, primaryRGB, 0.706f);
return gradColor * angularFalloff * level;
}
private float4 ComputeInnerRays(float theta, float r, float rotAngle, float maxLength)
{
float sectorAngle = 6.28318f / 14f;
float adjusted = theta - rotAngle + 628.318f;
float rayIdxF = Hlsl.Floor(adjusted / sectorAngle);
float inSector = adjusted - rayIdxF * sectorAngle;
float dTheta = inSector - sectorAngle * 0.5f;
float wrapped = rayIdxF - Hlsl.Floor(rayIdxF / 14f) * 14f;
int nearestRay = (int)wrapped;
int bandGroup = Hlsl.Min((int)Hlsl.Floor(wrapped / 4f), 3);
float level = innerBands[bandGroup];
float basePulse = 0.15f + 0.1f * Hlsl.Sin(time * 6.28318f / (3f + nearestRay * 0.2f));
level = Hlsl.Max(level, basePulse);
float rayWidth = 6f * (0.15f + 5f * level);
float rayLength = maxLength * Hlsl.Max(level * 4f, 0.15f);
float visible = Hlsl.Step(1f, r) * Hlsl.Step(r, rayLength);
if (visible < 0.5f)
{
return new float4(0f, 0f, 0f, 0f);
}
float halfAngle = rayWidth / (2f * r);
float angularFalloff = 1f - Hlsl.SmoothStep(halfAngle * 0.3f, halfAngle, Hlsl.Abs(dTheta));
if (angularFalloff <= 0f)
{
return new float4(0f, 0f, 0f, 0f);
}
float t = r / rayLength;
// Alternate InnerColor(1.0, 0.392, 0.863) / OuterColor(0.392, 0.706, 1.0)
float isEven = 1f - Hlsl.Step(0.5f, Hlsl.Frac(nearestRay * 0.5f));
float3 primaryRGB = new float3(
Hlsl.Lerp(0.392f, 1f, isEven),
Hlsl.Lerp(0.706f, 0.392f, isEven),
Hlsl.Lerp(1f, 0.863f, isEven));
float primaryAlpha = Hlsl.Lerp(0.706f, 0.784f, isEven);
float4 gradColor = SampleRayGradient(t, primaryRGB, primaryAlpha);
return gradColor * angularFalloff * level;
}
private float GetOuterBand(int group)
{
// 8 bands packed in 2 float4s
float result = outerBands0.X;
result = (group == 1) ? outerBands0.Y : result;
result = (group == 2) ? outerBands0.Z : result;
result = (group == 3) ? outerBands0.W : result;
result = (group == 4) ? outerBands1.X : result;
result = (group == 5) ? outerBands1.Y : result;
result = (group == 6) ? outerBands1.Z : result;
result = (group == 7) ? outerBands1.W : result;
return result;
}
private static float SampleRingGradient(float dist)
{
float belowMin = Hlsl.Step(dist, 0.6f);
float aboveMax = Hlsl.Step(1f, dist);
float seg1 = Hlsl.Lerp(0f, 0.392f, Hlsl.Saturate((dist - 0.6f) / 0.2f));
float seg2 = Hlsl.Lerp(0.392f, 0.196f, Hlsl.Saturate((dist - 0.8f) / 0.12f));
float seg3 = Hlsl.Lerp(0.196f, 0f, Hlsl.Saturate((dist - 0.92f) / 0.08f));
float s1 = Hlsl.Step(0.8f, dist);
float s2 = Hlsl.Step(0.92f, dist);
float val = Hlsl.Lerp(seg1, seg2, s1);
val = Hlsl.Lerp(val, seg3, s2);
return val * (1f - belowMin) * (1f - aboveMax);
}
private static float4 SampleRayGradient(float t, float3 primaryRGB, float primaryAlpha)
{
float s1 = Hlsl.Step(0.05f, t);
float s2 = Hlsl.Step(0.2f, t);
float s3 = Hlsl.Step(0.5f, t);
float f0 = Hlsl.Saturate(t / 0.05f);
float3 col0 = new float3(
Hlsl.Lerp(1f, primaryRGB.X, f0),
Hlsl.Lerp(1f, primaryRGB.Y, f0),
Hlsl.Lerp(1f, primaryRGB.Z, f0));
float a0 = Hlsl.Lerp(0.314f, primaryAlpha, f0);
float3 col1 = primaryRGB;
float a1 = Hlsl.Lerp(primaryAlpha, primaryAlpha * 0.7f, Hlsl.Saturate((t - 0.05f) / 0.15f));
float a2 = Hlsl.Lerp(primaryAlpha * 0.7f, primaryAlpha * 0.3f, Hlsl.Saturate((t - 0.2f) / 0.3f));
float a3 = Hlsl.Lerp(primaryAlpha * 0.3f, 0f, Hlsl.Saturate((t - 0.5f) / 0.5f));
float3 color = new float3(
Hlsl.Lerp(col0.X, col1.X, s1),
Hlsl.Lerp(col0.Y, col1.Y, s1),
Hlsl.Lerp(col0.Z, col1.Z, s1));
float alpha = Hlsl.Lerp(a0, a1, s1);
alpha = Hlsl.Lerp(alpha, a2, s2);
alpha = Hlsl.Lerp(alpha, a3, s3);
return new float4(color.X * alpha, color.Y * alpha, color.Z * alpha, alpha);
}
private static float SampleOrbAlpha(float dist)
{
float s1 = Hlsl.Step(0.15f, dist);
float s2 = Hlsl.Step(0.4f, dist);
float s3 = Hlsl.Step(0.7f, dist);
float s4 = Hlsl.Step(1f, dist);
float a0 = Hlsl.Lerp(0.784f, 0.627f, Hlsl.Saturate(dist / 0.15f));
float a1 = Hlsl.Lerp(0.627f, 0.314f, Hlsl.Saturate((dist - 0.15f) / 0.25f));
float a2 = Hlsl.Lerp(0.314f, 0.118f, Hlsl.Saturate((dist - 0.4f) / 0.3f));
float a3 = Hlsl.Lerp(0.118f, 0f, Hlsl.Saturate((dist - 0.7f) / 0.3f));
float val = Hlsl.Lerp(a0, a1, s1);
val = Hlsl.Lerp(val, a2, s2);
val = Hlsl.Lerp(val, a3, s3);
return val * (1f - s4);
}
private static float3 SampleOrbColor(float dist)
{
float s1 = Hlsl.Step(0.15f, dist);
float s2 = Hlsl.Step(0.4f, dist);
float s3 = Hlsl.Step(0.7f, dist);
float3 c0 = new float3(
Hlsl.Lerp(1f, 0.863f, Hlsl.Saturate(dist / 0.15f)),
Hlsl.Lerp(1f, 0.784f, Hlsl.Saturate(dist / 0.15f)),
1f);
float3 c1 = new float3(
Hlsl.Lerp(0.863f, 0.627f, Hlsl.Saturate((dist - 0.15f) / 0.25f)),
Hlsl.Lerp(0.784f, 0.47f, Hlsl.Saturate((dist - 0.15f) / 0.25f)),
1f);
float3 c2 = new float3(
Hlsl.Lerp(0.627f, 0.392f, Hlsl.Saturate((dist - 0.4f) / 0.3f)),
Hlsl.Lerp(0.47f, 0.314f, Hlsl.Saturate((dist - 0.4f) / 0.3f)),
Hlsl.Lerp(1f, 0.863f, Hlsl.Saturate((dist - 0.4f) / 0.3f)));
float3 c3 = new float3(
Hlsl.Lerp(0.392f, 0.235f, Hlsl.Saturate((dist - 0.7f) / 0.3f)),
Hlsl.Lerp(0.314f, 0.157f, Hlsl.Saturate((dist - 0.7f) / 0.3f)),
Hlsl.Lerp(0.863f, 0.706f, Hlsl.Saturate((dist - 0.7f) / 0.3f)));
float3 val = new float3(
Hlsl.Lerp(c0.X, c1.X, s1), Hlsl.Lerp(c0.Y, c1.Y, s1), Hlsl.Lerp(c0.Z, c1.Z, s1));
val = new float3(
Hlsl.Lerp(val.X, c2.X, s2), Hlsl.Lerp(val.Y, c2.Y, s2), Hlsl.Lerp(val.Z, c2.Z, s2));
val = new float3(
Hlsl.Lerp(val.X, c3.X, s3), Hlsl.Lerp(val.Y, c3.Y, s3), Hlsl.Lerp(val.Z, c3.Z, s3));
return val;
}
}

View File

@@ -0,0 +1,134 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ComputeSharp;
using ComputeSharp.D2D1;
namespace Microsoft.CmdPal.UI.Controls.ShaderEffects.Shaders;
/// <summary>
/// Audio-reactive full-width glow along the top edge.
/// Replaces 5 CompositionRadialGradientBrush layers + 1 linear pulse
/// with a single GPU pixel shader.
/// </summary>
[D2DInputCount(0)]
[D2DRequiresScenePosition]
[D2DShaderProfile(D2D1ShaderProfile.PixelShader50)]
[D2DGeneratedPixelShaderDescriptor]
public readonly partial struct AudioGlowShader(
float time,
float hueOffset,
float overallEnergy,
float band0,
float band1,
float band2,
float band3,
float band4,
int width,
int height) : ID2D1PixelShader
{
public float4 Execute()
{
float2 pos = D2D.GetScenePosition().XY;
float w = (float)width;
float h = (float)height;
float glowH = Hlsl.Min(150f, h);
// Use time for subtle shimmer on the pulse
float shimmer = 1f + 0.02f * Hlsl.Sin(time * 3f);
float4 result = new float4(0f, 0f, 0f, 0f);
// Full-width pulse layer
float pulseScaleY = 0.3f + 1.0f * overallEnergy;
float pulseOpacity = 0.15f + 0.85f * overallEnergy;
float3 pulseColor = HueToRGB(hueOffset);
float pulseT = pos.Y / (glowH * pulseScaleY);
float pulseA = SampleLinearGradient(pulseT) * pulseOpacity * shimmer * Hlsl.Step(0f, pulseT) * Hlsl.Step(pulseT, 1f);
result += new float4(pulseColor.X * pulseA, pulseColor.Y * pulseA, pulseColor.Z * pulseA, pulseA);
// Layer 0
result += ComputeLayer(pos, w, glowH, 0, band0);
// Layer 1
result += ComputeLayer(pos, w, glowH, 1, band1);
// Layer 2
result += ComputeLayer(pos, w, glowH, 2, band2);
// Layer 3
result += ComputeLayer(pos, w, glowH, 3, band3);
// Layer 4
result += ComputeLayer(pos, w, glowH, 4, band4);
// Boost with audio: baseline visible, audio intensifies
float intensity = 0.35f + 1.5f * overallEnergy;
return Hlsl.Saturate(result * intensity);
}
private float4 ComputeLayer(float2 pos, float w, float glowH, int layerIndex, float level)
{
float opacity = 0.1f + 0.9f * level;
float scaleY = 0.3f + 1.2f * level;
float effectiveH = glowH * scaleY;
float inBounds = Hlsl.Step(0f, pos.Y) * Hlsl.Step(pos.Y, effectiveH);
if (inBounds < 0.5f)
{
return new float4(0f, 0f, 0f, 0f);
}
float hue = Hlsl.Frac(layerIndex * 0.2f + hueOffset);
float3 color = HueToRGB(hue);
// Radial gradient — wide ellipse so glow spans most of the width
float dx = (pos.X / w - 0.5f) / 1.5f;
float dy = (pos.Y / effectiveH) / 0.9f;
float dist = Hlsl.Sqrt(dx * dx + dy * dy);
float alpha = SampleRadialGradient(dist);
float a = alpha * opacity;
return new float4(color.X * a, color.Y * a, color.Z * a, a);
}
private static float SampleRadialGradient(float dist)
{
// Steeper falloff: bright at top, fades fast
float s1 = Hlsl.Step(0.1f, dist);
float s2 = Hlsl.Step(0.25f, dist);
float s3 = Hlsl.Step(1f, dist);
float seg0 = Hlsl.Lerp(1f, 0.7f, Hlsl.Saturate(dist / 0.1f));
float seg1 = Hlsl.Lerp(0.7f, 0.15f, Hlsl.Saturate((dist - 0.1f) / 0.15f));
float seg2 = Hlsl.Lerp(0.15f, 0f, Hlsl.Saturate((dist - 0.25f) / 0.75f));
float val = Hlsl.Lerp(seg0, seg1, s1);
val = Hlsl.Lerp(val, seg2, s2);
return val * (1f - s3);
}
private static float SampleLinearGradient(float t)
{
float s1 = Hlsl.Step(0.2f, t);
float s2 = Hlsl.Step(0.5f, t);
float s3 = Hlsl.Step(1f, t);
float seg0 = Hlsl.Lerp(0.784f, 0.47f, Hlsl.Saturate(t / 0.2f));
float seg1 = Hlsl.Lerp(0.47f, 0.157f, Hlsl.Saturate((t - 0.2f) / 0.3f));
float seg2 = Hlsl.Lerp(0.157f, 0f, Hlsl.Saturate((t - 0.5f) / 0.5f));
float val = Hlsl.Lerp(seg0, seg1, s1);
val = Hlsl.Lerp(val, seg2, s2);
return val * (1f - s3);
}
private static float3 HueToRGB(float hue)
{
float r = Hlsl.Abs(Hlsl.Frac(hue + 0f) * 6f - 3f) - 1f;
float g = Hlsl.Abs(Hlsl.Frac(hue + 0.6667f) * 6f - 3f) - 1f;
float b = Hlsl.Abs(Hlsl.Frac(hue + 0.3333f) * 6f - 3f) - 1f;
return new float3(Hlsl.Saturate(r), Hlsl.Saturate(g), Hlsl.Saturate(b));
}
}

View File

@@ -0,0 +1,187 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ComputeSharp;
using ComputeSharp.D2D1;
namespace Microsoft.CmdPal.UI.Controls.ShaderEffects.Shaders;
/// <summary>
/// Kaleidoscope tunnel: infinite zoom into a mirrored fractal corridor.
/// Combines tunnel depth mapping (1/r) with kaleidoscope folds so the
/// pattern repeats infinitely as you fly forward. Audio drives the
/// wall patterns and color intensity while movement stays smooth.
/// </summary>
[D2DInputCount(0)]
[D2DRequiresScenePosition]
[D2DShaderProfile(D2D1ShaderProfile.PixelShader50)]
[D2DGeneratedPixelShaderDescriptor]
public readonly partial struct KaleidoTunnelShader(
float time,
int width,
int height,
float4 bands0,
float4 bands1,
float4 bands2) : ID2D1PixelShader
{
public float4 Execute()
{
float2 pos = D2D.GetScenePosition().XY;
float w = (float)width;
float h = (float)height;
float minDim = Hlsl.Min(w, h);
// Stretch to fill the window
float2 uv = new float2(
(pos.X / w - 0.5f) * 2f,
(pos.Y / h - 0.5f) * 2f);
float bass = (bands0.X + bands0.Y + bands0.Z) * 0.333f;
float mid = (bands0.W + bands1.X + bands1.Y + bands1.Z + bands1.W) * 0.2f;
float high = (bands2.X + bands2.Y + bands2.Z + bands2.W) * 0.25f;
float energy = (bass + mid + high) * 0.333f;
// Slow rotation of the view
float viewRot = time * 0.1f;
float vc = Hlsl.Cos(viewRot);
float vs = Hlsl.Sin(viewRot);
float rx = uv.X * vc - uv.Y * vs;
float ry = uv.X * vs + uv.Y * vc;
float radius = Hlsl.Sqrt(rx * rx + ry * ry);
radius = Hlsl.Max(radius, 0.001f);
float angle = Hlsl.Atan2(ry, rx);
// === TUNNEL DEPTH ===
float depth = 1f / radius;
float tunnelZ = depth + time * 2.5f;
// === KALEIDOSCOPE FOLD on the angular coordinate ===
float kAngle = angle + tunnelZ * 0.15f + time * 0.12f;
float2 kp = KaleidoFold(kAngle, radius, 8f);
// Second fold at different depth rate for layering
float kAngle2 = angle - tunnelZ * 0.1f + time * 0.08f;
float2 kp2 = KaleidoFold(kAngle2, radius, 6f);
// === WARPED PATTERN in kaleidoscope space ===
float warpStr = 0.2f + mid * 0.25f;
float wpx = kp.X + warpStr * Hlsl.Sin(kp.Y * 2.3f + tunnelZ * 0.3f + time * 0.3f);
float wpy = kp.Y + warpStr * Hlsl.Cos(kp.X * 1.9f + tunnelZ * 0.2f + time * 0.25f);
wpx += warpStr * 0.4f * Hlsl.Sin(wpy * 3.5f + time * 0.4f);
wpy += warpStr * 0.4f * Hlsl.Cos(wpx * 3.1f + time * 0.35f);
// === FRACTAL TEXTURE (depth-tiled) ===
float tileZ = Hlsl.Frac(tunnelZ * 0.08f);
float pattern1 = Hlsl.Sin(wpx * 1.5f + tileZ * 12f + time * 0.2f)
* Hlsl.Cos(wpy * 1.3f + tileZ * 10f + time * 0.15f) * 0.5f + 0.5f;
float nx = wpx * 0.866f - wpy * 0.5f;
float ny = wpx * 0.5f + wpy * 0.866f;
float pattern2 = Hlsl.Sin(nx * 2.4f + tileZ * 8f + time * 0.25f + 1.5f)
* Hlsl.Cos(ny * 2.1f - tileZ * 6f + time * 0.2f) * 0.5f + 0.5f;
float pattern = pattern1 + pattern2 * 0.5f;
// === SECOND LAYER PATTERN ===
float wp2x = kp2.X + warpStr * 0.6f * Hlsl.Sin(kp2.Y * 2f + tunnelZ * 0.2f + 2f);
float wp2y = kp2.Y + warpStr * 0.6f * Hlsl.Cos(kp2.X * 1.7f + tunnelZ * 0.15f + 2f);
float pattern2nd = Hlsl.Sin(wp2x * 1.8f + tileZ * 9f + time * 0.3f)
* Hlsl.Cos(wp2y * 1.5f + tileZ * 7f) * 0.5f + 0.5f;
// === TUNNEL RINGS (depth markers) ===
float ringPhase = Hlsl.Frac(tunnelZ * 0.15f);
float ring = Hlsl.Exp(-ringPhase * ringPhase * 120f)
+ Hlsl.Exp(-(1f - ringPhase) * (1f - ringPhase) * 120f);
ring *= 0.5f + 0.5f * energy;
// === RADIAL LINES in kaleidoscope space ===
float foldedAngle = kp.X;
float ribPhase = Hlsl.Frac(Hlsl.Atan2(kp.Y, kp.X) * 2.387f + 0.5f);
float rib = Hlsl.Exp(-(ribPhase - 0.5f) * (ribPhase - 0.5f) * 50f) * 0.4f;
rib *= 0.3f + 0.7f * mid;
// === DEPTH FOG ===
float fog = Hlsl.Exp(-depth * 0.06f);
float fogNear = Hlsl.Lerp(0.3f, 1f, fog);
// === COLORING — hue from kaleidoscope angle + depth ===
float hue1 = Hlsl.Frac(Hlsl.Atan2(kp.Y, kp.X) * 0.159f + tunnelZ * 0.02f + time * 0.02f);
float hue2 = Hlsl.Frac(Hlsl.Atan2(kp2.Y, kp2.X) * 0.159f - tunnelZ * 0.015f + time * 0.03f + 0.33f);
float hueRing = Hlsl.Frac(tunnelZ * 0.03f + time * 0.015f + 0.6f);
float3 col1 = HueToRGB(hue1);
float3 col2 = HueToRGB(hue2);
float3 colRing = HueToRGB(hueRing);
// Composite color
float i1 = pattern * 0.45f * (0.4f + bass * 0.6f);
float i2 = pattern2nd * 0.25f;
float iRing = ring * 0.5f;
float iRib = rib;
float3 color = new float3(
col1.X * i1 + col2.X * i2 + colRing.X * iRing + col1.X * iRib,
col1.Y * i1 + col2.Y * i2 + colRing.Y * iRing + col1.Y * iRib,
col1.Z * i1 + col2.Z * i2 + colRing.Z * iRing + col1.Z * iRib);
// Apply fog
color = new float3(color.X * fogNear, color.Y * fogNear, color.Z * fogNear);
// === CENTER LIGHT (vanishing point glow) ===
float centerGlow = Hlsl.Exp(-radius * radius * 2f) * (0.3f + energy * 0.5f);
float3 glowCol = HueToRGB(Hlsl.Frac(time * 0.02f + 0.5f));
color = new float3(
color.X + glowCol.X * centerGlow,
color.Y + glowCol.Y * centerGlow,
color.Z + glowCol.Z * centerGlow);
// === SHIMMER on high frequencies ===
float shimmer = Hlsl.Sin(tunnelZ * 15f + angle * 8f + time * 1.5f) * 0.5f + 0.5f;
shimmer = Hlsl.Step(0.88f, shimmer) * high * 0.3f;
float3 shimCol = HueToRGB(Hlsl.Frac(hue1 + 0.2f));
color = new float3(
color.X + shimCol.X * shimmer * fog,
color.Y + shimCol.Y * shimmer * fog,
color.Z + shimCol.Z * shimmer * fog);
// Energy boost
float boost = 1f + energy * 0.6f;
color = new float3(color.X * boost, color.Y * boost, color.Z * boost);
// Vignette
float vig = 1f - Hlsl.Saturate((radius - 0.6f) * 0.8f);
vig = vig * vig;
color = new float3(color.X * vig, color.Y * vig, color.Z * vig);
float cr = Hlsl.Saturate(color.X);
float cg = Hlsl.Saturate(color.Y);
float cb = Hlsl.Saturate(color.Z);
float ca = Hlsl.Max(cr, Hlsl.Max(cg, cb));
// Boost with audio: baseline visible, audio intensifies
float intensity = 0.35f + 1.5f * energy;
return new float4(Hlsl.Saturate(cr * intensity), Hlsl.Saturate(cg * intensity), Hlsl.Saturate(cb * intensity), Hlsl.Saturate(ca * intensity));
}
/// <summary>
/// Kaleidoscope fold: mirrors angle into a segment, returns
/// new coordinates in the folded space at the given radius.
/// </summary>
private static float2 KaleidoFold(float angle, float radius, float segments)
{
float a = angle + 628.318f;
float segAngle = 6.28318f / segments;
float folded = a - Hlsl.Floor(a / segAngle) * segAngle;
folded = Hlsl.Abs(folded - segAngle * 0.5f);
return new float2(Hlsl.Cos(folded) * radius, Hlsl.Sin(folded) * radius);
}
private static float3 HueToRGB(float hue)
{
float rv = Hlsl.Abs(Hlsl.Frac(hue) * 6f - 3f) - 1f;
float g = Hlsl.Abs(Hlsl.Frac(hue + 0.6667f) * 6f - 3f) - 1f;
float b = Hlsl.Abs(Hlsl.Frac(hue + 0.3333f) * 6f - 3f) - 1f;
return new float3(Hlsl.Saturate(rv), Hlsl.Saturate(g), Hlsl.Saturate(b));
}
}

View File

@@ -0,0 +1,214 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ComputeSharp;
using ComputeSharp.D2D1;
namespace Microsoft.CmdPal.UI.Controls.ShaderEffects.Shaders;
/// <summary>
/// Psychedelic MilkDrop-style kaleidoscope: mirrored segments, flowing warped
/// textures, smooth motion, neon bloom. Optimized for fluid 60fps rendering.
/// </summary>
[D2DInputCount(0)]
[D2DRequiresScenePosition]
[D2DShaderProfile(D2D1ShaderProfile.PixelShader50)]
[D2DGeneratedPixelShaderDescriptor]
public readonly partial struct KaleidoscopeShader(
float time,
int width,
int height,
float4 bands0,
float4 bands1,
float4 bands2) : ID2D1PixelShader
{
public float4 Execute()
{
float2 pos = D2D.GetScenePosition().XY;
float w = (float)width;
float h = (float)height;
float minDim = Hlsl.Min(w, h);
// Stretch to fill the window — each axis normalized independently
float2 uv = new float2(
(pos.X / w - 0.5f) * 2f,
(pos.Y / h - 0.5f) * 2f);
float bass = (bands0.X + bands0.Y + bands0.Z) * 0.333f;
float mid = (bands0.W + bands1.X + bands1.Y + bands1.Z + bands1.W) * 0.2f;
float high = (bands2.X + bands2.Y + bands2.Z + bands2.W) * 0.25f;
float energy = (bass + mid + high) * 0.333f;
// Gentle bass zoom
float zoom = 1f - bass * 0.08f;
float px = uv.X * zoom;
float py = uv.Y * zoom;
// Slow smooth rotation
float gRot = time * 0.08f;
float gc = Hlsl.Cos(gRot);
float gs = Hlsl.Sin(gRot);
float rx = px * gc - py * gs;
float ry = px * gs + py * gc;
float3 color = new float3(0f, 0f, 0f);
// === PASS 1: Primary kaleidoscope (fixed 8-fold, slowly rotating) ===
float2 k1 = Kaleidoscope(rx, ry, 8f, time * 0.18f);
float warpStr1 = 0.25f + mid * 0.35f;
float2 w1 = Warp(k1.X, k1.Y, time * 0.4f, warpStr1);
float f1 = FractalField(w1.X, w1.Y, time, bass);
float shimmer = Hlsl.Sin(w1.X * 12f + time * 2f) * Hlsl.Sin(w1.Y * 10f - time * 1.5f);
shimmer = shimmer * high * 0.08f;
float intensity1 = f1 + shimmer;
float hue1 = Hlsl.Frac(Hlsl.Atan2(w1.Y, w1.X) * 0.159f + time * 0.025f + f1 * 0.1f);
float3 c1 = HueToRGB(hue1);
color = new float3(
color.X + c1.X * intensity1 * 0.5f,
color.Y + c1.Y * intensity1 * 0.5f,
color.Z + c1.Z * intensity1 * 0.5f);
// === PASS 2: Second layer (fixed 6-fold, counter-rotate) ===
float rot2 = -time * 0.05f;
float c2r = Hlsl.Cos(rot2);
float s2r = Hlsl.Sin(rot2);
float rx2 = px * c2r - py * s2r;
float ry2 = px * s2r + py * c2r;
float2 k2 = Kaleidoscope(rx2, ry2, 6f, -time * 0.12f);
float warpStr2 = 0.2f + bass * 0.3f;
float2 w2 = Warp(k2.X, k2.Y, time * 0.3f + 3f, warpStr2);
float f2 = FractalField(w2.X, w2.Y, time * 0.6f + 5f, mid);
float hue2 = Hlsl.Frac(Hlsl.Atan2(w2.Y, w2.X) * 0.159f - time * 0.03f + f2 * 0.12f + 0.33f);
float3 c2 = HueToRGB(hue2);
color = new float3(
color.X + c2.X * f2 * 0.3f,
color.Y + c2.Y * f2 * 0.3f,
color.Z + c2.Z * f2 * 0.3f);
// === PASS 3: Flowing blob shapes ===
float2 k3 = Kaleidoscope(rx * 1.1f, ry * 1.1f, 4f, time * 0.09f);
float blobAngle = Hlsl.Atan2(k3.Y, k3.X);
float blobR = Hlsl.Sqrt(k3.X * k3.X + k3.Y * k3.Y);
float blobShape = 0.3f + 0.12f * Hlsl.Sin(blobAngle * 3f + time * 0.6f)
+ 0.07f * Hlsl.Sin(blobAngle * 5f - time * 0.4f) * mid
+ bass * 0.1f;
float blob = 1f - Hlsl.SmoothStep(blobShape - 0.1f, blobShape + 0.1f, blobR);
float blobEdge = Hlsl.Exp(-(blobR - blobShape) * (blobR - blobShape) * 40f) * 0.4f;
float hue3 = Hlsl.Frac(time * 0.03f + blobAngle * 0.159f + 0.66f);
float3 c3 = HueToRGB(hue3);
color = new float3(
color.X + c3.X * (blob * 0.15f + blobEdge),
color.Y + c3.Y * (blob * 0.15f + blobEdge),
color.Z + c3.Z * (blob * 0.15f + blobEdge));
// === PASS 4: Spiral overlay ===
float sR = Hlsl.Sqrt(rx * rx + ry * ry);
float sTheta = Hlsl.Atan2(ry, rx);
float spiral = Hlsl.Sin(sTheta * 5f + sR * (6f + bass * 3f) - time * 0.8f) * 0.5f + 0.5f;
spiral *= Hlsl.Exp(-sR * 1.2f);
float3 sCol = HueToRGB(Hlsl.Frac(sTheta * 0.159f + time * 0.02f));
color = new float3(
color.X + sCol.X * spiral * 0.15f,
color.Y + sCol.Y * spiral * 0.15f,
color.Z + sCol.Z * spiral * 0.15f);
// === FEEDBACK: single lightweight ghost ===
float fbA = time * 0.04f;
float fbc = Hlsl.Cos(fbA);
float fbs = Hlsl.Sin(fbA);
float fbx = (rx * 0.9f) * fbc - (ry * 0.9f) * fbs;
float fby = (rx * 0.9f) * fbs + (ry * 0.9f) * fbc;
float2 fk = Kaleidoscope(fbx, fby, 8f, time * 0.18f);
float2 fw = Warp(fk.X, fk.Y, time * 0.4f - 0.4f, warpStr1 * 0.5f);
float ff = FractalField(fw.X, fw.Y, time - 0.4f, bass * 0.5f);
float3 fc = HueToRGB(Hlsl.Frac(hue1 + 0.15f));
color = new float3(
color.X + fc.X * ff * 0.1f,
color.Y + fc.Y * ff * 0.1f,
color.Z + fc.Z * ff * 0.1f);
// === BLOOM + VIGNETTE ===
float r = Hlsl.Sqrt(px * px + py * py);
float centerGlow = Hlsl.Exp(-r * r * 1.5f) * energy * 0.3f;
float3 bloomCol = HueToRGB(Hlsl.Frac(time * 0.025f + 0.2f));
color = new float3(
color.X + bloomCol.X * centerGlow,
color.Y + bloomCol.Y * centerGlow,
color.Z + bloomCol.Z * centerGlow);
float boost = 1f + energy * 0.8f;
color = new float3(color.X * boost, color.Y * boost, color.Z * boost);
float vig = 1f - Hlsl.Saturate((r - 0.7f) * 0.9f);
vig = vig * vig;
color = new float3(color.X * vig, color.Y * vig, color.Z * vig);
float cr = Hlsl.Saturate(color.X);
float cg = Hlsl.Saturate(color.Y);
float cb = Hlsl.Saturate(color.Z);
float ca = Hlsl.Max(cr, Hlsl.Max(cg, cb));
// Boost with audio: baseline visible, audio intensifies
float intensity = 0.35f + 1.5f * energy;
return new float4(Hlsl.Saturate(cr * intensity), Hlsl.Saturate(cg * intensity), Hlsl.Saturate(cb * intensity), Hlsl.Saturate(ca * intensity));
}
private static float2 Kaleidoscope(float x, float y, float segments, float rotation)
{
float angle = Hlsl.Atan2(y, x) + 628.318f + rotation;
float rad = Hlsl.Sqrt(x * x + y * y);
float segAngle = 6.28318f / segments;
float folded = angle - Hlsl.Floor(angle / segAngle) * segAngle;
folded = Hlsl.Abs(folded - segAngle * 0.5f);
return new float2(Hlsl.Cos(folded) * rad, Hlsl.Sin(folded) * rad);
}
private static float2 Warp(float x, float y, float t, float strength)
{
float wx = x + strength * Hlsl.Sin(y * 2.3f + t);
float wy = y + strength * Hlsl.Cos(x * 1.9f + t * 0.8f);
wx += strength * 0.4f * Hlsl.Sin(wy * 3.7f + t * 1.3f);
wy += strength * 0.4f * Hlsl.Cos(wx * 3.1f + t * 1.1f);
return new float2(wx, wy);
}
private static float FractalField(float px, float py, float t, float audioMod)
{
float val = 0f;
float amp = 1f;
float speed = 0.3f + audioMod * 0.15f;
float x = px;
float y = py;
val += amp * (Hlsl.Sin(x * 1.5f + t * speed) * Hlsl.Cos(y * 1.3f + t * speed * 0.7f) * 0.5f + 0.5f);
float nx = x * 0.866f - y * 0.5f;
float ny = x * 0.5f + y * 0.866f;
x = nx * 1.8f; y = ny * 1.8f;
amp *= 0.5f;
val += amp * (Hlsl.Sin(x * 1.2f + t * speed * 1.1f + 1.5f) * Hlsl.Cos(y * 1.4f - t * speed * 0.9f) * 0.5f + 0.5f);
nx = x * 0.707f - y * 0.707f;
ny = x * 0.707f + y * 0.707f;
x = nx * 1.7f; y = ny * 1.7f;
amp *= 0.45f;
val += amp * (Hlsl.Sin(x * 1.1f - t * speed * 1.2f + 3f) * Hlsl.Cos(y * 0.9f + t * speed * 0.5f) * 0.5f + 0.5f);
return val;
}
private static float3 HueToRGB(float hue)
{
float rv = Hlsl.Abs(Hlsl.Frac(hue) * 6f - 3f) - 1f;
float g = Hlsl.Abs(Hlsl.Frac(hue + 0.6667f) * 6f - 3f) - 1f;
float b = Hlsl.Abs(Hlsl.Frac(hue + 0.3333f) * 6f - 3f) - 1f;
return new float3(Hlsl.Saturate(rv), Hlsl.Saturate(g), Hlsl.Saturate(b));
}
}

View File

@@ -0,0 +1,285 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ComputeSharp;
using ComputeSharp.D2D1;
namespace Microsoft.CmdPal.UI.Controls.ShaderEffects.Shaders;
/// <summary>
/// Circular audio spectrum: glowing bars radiate outward from a center ring,
/// layered with neon glow, motion trails, soft chaos, and rotating rings.
/// Early 2000s WMP / Winamp aesthetic with additive blending.
/// </summary>
[D2DInputCount(0)]
[D2DRequiresScenePosition]
[D2DShaderProfile(D2D1ShaderProfile.PixelShader50)]
[D2DGeneratedPixelShaderDescriptor]
public readonly partial struct RadialSpectrumShader(
float time,
int width,
int height,
float4 bands0,
float4 bands1,
float4 bands2) : ID2D1PixelShader
{
public float4 Execute()
{
float2 pos = D2D.GetScenePosition().XY;
float w = (float)width;
float h = (float)height;
float minDim = Hlsl.Min(w, h);
// Stretch to fill the window
float2 uv = new float2(
(pos.X / w - 0.5f) * 2f,
(pos.Y / h - 0.5f) * 2f);
float bass = (bands0.X + bands0.Y + bands0.Z) * 0.333f;
float mid = (bands0.W + bands1.X + bands1.Y + bands1.Z + bands1.W) * 0.2f;
float high = (bands2.X + bands2.Y + bands2.Z + bands2.W) * 0.25f;
float energy = (bass + mid + high) * 0.333f;
float r = Hlsl.Sqrt(uv.X * uv.X + uv.Y * uv.Y);
float theta = Hlsl.Atan2(uv.Y, uv.X);
// Bass-reactive global scale — circle breathes
float globalScale = 1f + bass * 0.12f;
r = r / globalScale;
float3 color = new float3(0f, 0f, 0f);
// ================================================================
// LAYER 1 — PRIMARY SPECTRUM BARS (48 segments)
// ================================================================
float rot1 = time * 0.2f;
float adj1 = theta - rot1 + 628.318f;
float barCount = 48f;
float sectorAngle = 6.28318f / barCount;
float barIdxF = Hlsl.Floor(adj1 / sectorAngle);
float inSector = adj1 - barIdxF * sectorAngle;
float dAngle = Hlsl.Abs(inSector - sectorAngle * 0.5f);
// Map bar index to audio band (48 bars → 12 bands, 4 bars each)
float wrapped = barIdxF - Hlsl.Floor(barIdxF / barCount) * barCount;
int bandGroup = (int)Hlsl.Floor(wrapped * 12f / barCount);
float bandVal = GetBand(bandGroup);
// Bar dimensions
float baseRadius = 0.28f;
float barLength = 0.15f + 0.55f * bandVal;
float barInner = baseRadius;
float barOuter = baseRadius + barLength;
float barHalfWidth = sectorAngle * 0.35f;
// Soft angular falloff
float angularMask = 1f - Hlsl.SmoothStep(barHalfWidth * 0.6f, barHalfWidth, dAngle);
// Radial mask — inside the bar
float radialMask = Hlsl.Step(barInner, r) * (1f - Hlsl.SmoothStep(barOuter - 0.02f, barOuter, r));
// Gradient along bar: bright at tip
float barGrad = Hlsl.Saturate((r - barInner) / Hlsl.Max(barLength, 0.01f));
float barBright = Hlsl.Lerp(0.4f, 1f, barGrad);
// Glow extending beyond bar tip
float tipDist = Hlsl.Max(r - barOuter, 0f);
float tipGlow = Hlsl.Exp(-tipDist * tipDist * 60f) * bandVal * 0.6f;
float barIntensity = (angularMask * radialMask * barBright + tipGlow * angularMask) * (0.3f + bandVal * 0.7f);
// Color — hue from angular position + time cycling
float barHue = Hlsl.Frac(wrapped / barCount + time * 0.03f);
float3 barCol = HueToRGB(barHue);
color = new float3(
color.X + barCol.X * barIntensity,
color.Y + barCol.Y * barIntensity,
color.Z + barCol.Z * barIntensity);
// ================================================================
// LAYER 2 — INNER RING GLOW (base circle outline)
// ================================================================
float ringDist = Hlsl.Abs(r - baseRadius);
float ringGlow = Hlsl.Exp(-ringDist * ringDist * 400f) * (0.4f + energy * 0.6f);
float ringBloom = Hlsl.Exp(-ringDist * ringDist * 30f) * 0.2f * (0.3f + bass);
float3 ringCol = HueToRGB(Hlsl.Frac(time * 0.05f));
color = new float3(
color.X + ringCol.X * (ringGlow + ringBloom),
color.Y + ringCol.Y * (ringGlow + ringBloom),
color.Z + ringCol.Z * (ringGlow + ringBloom));
// ================================================================
// LAYER 3 — OUTER ECHO RING (larger, dimmer, delayed hue)
// ================================================================
float outerRingR = baseRadius + 0.15f + 0.55f * energy;
float outerDist = Hlsl.Abs(r - outerRingR);
float outerRing = Hlsl.Exp(-outerDist * outerDist * 120f) * 0.35f;
float outerBloom = Hlsl.Exp(-outerDist * outerDist * 15f) * 0.1f;
float3 outerCol = HueToRGB(Hlsl.Frac(time * 0.04f + 0.33f));
color = new float3(
color.X + outerCol.X * (outerRing + outerBloom),
color.Y + outerCol.Y * (outerRing + outerBloom),
color.Z + outerCol.Z * (outerRing + outerBloom));
// ================================================================
// LAYER 4 — SECOND SPECTRUM (mirrored inward, dimmer)
// ================================================================
float innerBarOuter = baseRadius;
float innerBarLength = 0.08f + 0.2f * bandVal;
float innerBarInner = Hlsl.Max(baseRadius - innerBarLength, 0.02f);
float innerRadialMask = Hlsl.Step(innerBarInner, r) * Hlsl.Step(r, innerBarOuter);
float innerBarGrad = Hlsl.Saturate((innerBarOuter - r) / Hlsl.Max(innerBarLength, 0.01f));
float innerIntensity = angularMask * innerRadialMask * innerBarGrad * 0.3f * bandVal;
float3 innerCol = HueToRGB(Hlsl.Frac(barHue + 0.5f));
color = new float3(
color.X + innerCol.X * innerIntensity,
color.Y + innerCol.Y * innerIntensity,
color.Z + innerCol.Z * innerIntensity);
// ================================================================
// LAYER 5 — SECONDARY THIN BARS (24 bars, counter-rotating)
// ================================================================
float rot2 = -time * 0.35f;
float adj2 = theta - rot2 + 628.318f;
float bar2Count = 24f;
float sector2 = 6.28318f / bar2Count;
float bar2IdxF = Hlsl.Floor(adj2 / sector2);
float inSec2 = adj2 - bar2IdxF * sector2;
float dAngle2 = Hlsl.Abs(inSec2 - sector2 * 0.5f);
float wrap2 = bar2IdxF - Hlsl.Floor(bar2IdxF / bar2Count) * bar2Count;
int band2 = (int)Hlsl.Floor(wrap2 * 12f / bar2Count);
float bv2 = GetBand(band2);
float bar2Inner = baseRadius + 0.05f;
float bar2Length = 0.1f + 0.3f * bv2;
float bar2Outer = bar2Inner + bar2Length;
float bar2HalfW = sector2 * 0.2f;
float ang2 = 1f - Hlsl.SmoothStep(bar2HalfW * 0.5f, bar2HalfW, dAngle2);
float rad2 = Hlsl.Step(bar2Inner, r) * (1f - Hlsl.SmoothStep(bar2Outer - 0.01f, bar2Outer, r));
float grad2 = Hlsl.Saturate((r - bar2Inner) / Hlsl.Max(bar2Length, 0.01f));
float int2 = ang2 * rad2 * grad2 * 0.25f * bv2;
float3 col2 = HueToRGB(Hlsl.Frac(wrap2 / bar2Count + time * 0.06f + 0.15f));
color = new float3(
color.X + col2.X * int2,
color.Y + col2.Y * int2,
color.Z + col2.Z * int2);
// ================================================================
// LAYER 6 — FEEDBACK TRAIL SIMULATION (smeared ghost at larger scale)
// ================================================================
float2 fbUV = new float2(uv.X * 0.92f, uv.Y * 0.92f);
float fbCos = Hlsl.Cos(time * 0.08f);
float fbSin = Hlsl.Sin(time * 0.08f);
float2 fbRot = new float2(
fbUV.X * fbCos - fbUV.Y * fbSin,
fbUV.X * fbSin + fbUV.Y * fbCos);
float fbR = Hlsl.Sqrt(fbRot.X * fbRot.X + fbRot.Y * fbRot.Y) / globalScale;
float fbTheta = Hlsl.Atan2(fbRot.Y, fbRot.X);
float fbAdj = fbTheta - rot1 + 0.1f + 628.318f;
float fbBarIdx = Hlsl.Floor(fbAdj / sectorAngle);
float fbInSec = fbAdj - fbBarIdx * sectorAngle;
float fbDAngle = Hlsl.Abs(fbInSec - sectorAngle * 0.5f);
float fbWrapped = fbBarIdx - Hlsl.Floor(fbBarIdx / barCount) * barCount;
int fbBand = (int)Hlsl.Floor(fbWrapped * 12f / barCount);
float fbBandVal = GetBand(fbBand);
float fbBarOuter = baseRadius + 0.15f + 0.55f * fbBandVal;
float fbAngular = 1f - Hlsl.SmoothStep(barHalfWidth * 0.8f, barHalfWidth * 1.4f, fbDAngle);
float fbRadial = Hlsl.Step(baseRadius * 0.9f, fbR) * (1f - Hlsl.SmoothStep(fbBarOuter, fbBarOuter + 0.05f, fbR));
float fbIntensity = fbAngular * fbRadial * 0.12f * fbBandVal;
float3 fbCol = HueToRGB(Hlsl.Frac(fbWrapped / barCount + time * 0.03f - 0.1f));
color = new float3(
color.X + fbCol.X * fbIntensity,
color.Y + fbCol.Y * fbIntensity,
color.Z + fbCol.Z * fbIntensity);
// ================================================================
// LAYER 7 — CENTER ORB (pulsing with bass)
// ================================================================
float orbScale = 0.08f + 0.12f * bass;
float orbDist = r / orbScale;
float orbGlow = Hlsl.Exp(-orbDist * orbDist * 2f);
float orbSharp = Hlsl.Exp(-orbDist * orbDist * 8f);
float3 orbCol = HueToRGB(Hlsl.Frac(time * 0.07f + 0.5f));
float3 orbWhite = new float3(0.9f, 0.95f, 1f);
color = new float3(
color.X + Hlsl.Lerp(orbCol.X, orbWhite.X, orbSharp) * (orbGlow * 0.4f + orbSharp * 0.6f),
color.Y + Hlsl.Lerp(orbCol.Y, orbWhite.Y, orbSharp) * (orbGlow * 0.4f + orbSharp * 0.6f),
color.Z + Hlsl.Lerp(orbCol.Z, orbWhite.Z, orbSharp) * (orbGlow * 0.4f + orbSharp * 0.6f));
// ================================================================
// LAYER 8 — CHAOTIC PARTICLE NOISE (subtle sparkle)
// ================================================================
float noiseAngle = theta * 7f + time * 2f + r * 20f;
float noise = Hlsl.Frac(Hlsl.Sin(noiseAngle * 43758.5453f + Hlsl.Floor(r * 30f) * 12.9898f) * 43758.5453f);
float sparkle = Hlsl.Step(0.97f, noise) * energy * 0.5f;
float sparkleR = Hlsl.Step(baseRadius * 0.5f, r) * Hlsl.Step(r, baseRadius + barLength + 0.1f);
color = new float3(
color.X + sparkle * sparkleR,
color.Y + sparkle * sparkleR,
color.Z + sparkle * sparkleR * 0.8f);
// ================================================================
// POST — VIGNETTE
// ================================================================
float vig = 1f - Hlsl.Saturate((r - 0.8f) * 1.2f);
vig = vig * vig;
color = new float3(color.X * vig, color.Y * vig, color.Z * vig);
float cr = Hlsl.Saturate(color.X);
float cg = Hlsl.Saturate(color.Y);
float cb = Hlsl.Saturate(color.Z);
float ca = Hlsl.Max(cr, Hlsl.Max(cg, cb));
// Boost with audio: baseline visible, audio intensifies
float intensity = 0.35f + 1.5f * energy;
return new float4(Hlsl.Saturate(cr * intensity), Hlsl.Saturate(cg * intensity), Hlsl.Saturate(cb * intensity), Hlsl.Saturate(ca * intensity));
}
private float GetBand(int i)
{
float v = bands0.X;
v = (i >= 2) ? bands0.Z : v;
v = (i >= 3) ? bands0.W : v;
v = (i >= 4) ? bands1.X : v;
v = (i >= 5) ? bands1.Y : v;
v = (i >= 6) ? bands1.Z : v;
v = (i >= 7) ? bands1.W : v;
v = (i >= 8) ? bands2.X : v;
v = (i >= 9) ? bands2.Y : v;
v = (i >= 10) ? bands2.Z : v;
v = (i >= 11) ? bands2.W : v;
return v;
}
private static float3 HueToRGB(float hue)
{
float rv = Hlsl.Abs(Hlsl.Frac(hue) * 6f - 3f) - 1f;
float g = Hlsl.Abs(Hlsl.Frac(hue + 0.6667f) * 6f - 3f) - 1f;
float b = Hlsl.Abs(Hlsl.Frac(hue + 0.3333f) * 6f - 3f) - 1f;
return new float3(Hlsl.Saturate(rv), Hlsl.Saturate(g), Hlsl.Saturate(b));
}
}

View File

@@ -0,0 +1,244 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ComputeSharp;
using ComputeSharp.D2D1;
namespace Microsoft.CmdPal.UI.Controls.ShaderEffects.Shaders;
/// <summary>
/// Retro MilkDrop 3D waveform space: glowing sine waves and dotted lines
/// drift through dark space forming a tunnel of oscillating geometry.
/// Deep neon purples/magentas, soft glow, dreamy motion trails.
/// </summary>
[D2DInputCount(0)]
[D2DRequiresScenePosition]
[D2DShaderProfile(D2D1ShaderProfile.PixelShader50)]
[D2DGeneratedPixelShaderDescriptor]
public readonly partial struct WaveformSpaceShader(
float time,
int width,
int height,
float4 bands0,
float4 bands1,
float4 bands2) : ID2D1PixelShader
{
public float4 Execute()
{
float2 pos = D2D.GetScenePosition().XY;
float w = (float)width;
float h = (float)height;
float2 uv = new float2(
(pos.X / w - 0.5f) * 2f,
(pos.Y / h - 0.5f) * 2f);
float bass = (bands0.X + bands0.Y + bands0.Z) * 0.333f;
float mid = (bands0.W + bands1.X + bands1.Y + bands1.Z + bands1.W) * 0.2f;
float high = (bands2.X + bands2.Y + bands2.Z + bands2.W) * 0.25f;
float energy = (bass + mid + high) * 0.333f;
// === CAMERA: wander in random-feeling directions using layered sine ===
float camX = 0.3f * Hlsl.Sin(time * 0.17f) + 0.15f * Hlsl.Sin(time * 0.41f + 2f)
+ 0.08f * Hlsl.Sin(time * 0.73f + 5f);
float camY = 0.3f * Hlsl.Sin(time * 0.13f + 1f) + 0.15f * Hlsl.Sin(time * 0.37f + 4f)
+ 0.08f * Hlsl.Sin(time * 0.67f + 3f);
// Camera also rotates
float camRot = time * 0.12f + 0.2f * Hlsl.Sin(time * 0.09f);
float cc = Hlsl.Cos(camRot);
float cs = Hlsl.Sin(camRot);
float rx = (uv.X - camX) * cc - (uv.Y - camY) * cs;
float ry = (uv.X - camX) * cs + (uv.Y - camY) * cc;
float3 color = new float3(0f, 0f, 0f);
// === TUNNEL DEPTH from polar mapping ===
float radius = Hlsl.Sqrt(rx * rx + ry * ry);
radius = Hlsl.Max(radius, 0.001f);
float angle = Hlsl.Atan2(ry, rx);
float depth = 1f / radius;
float z = depth + time * 2.5f;
// === LAYER 1: Horizontal sine wave layers at different depths ===
// Multiple stacked waveforms flowing through z-space
float waveAccum = 0f;
// Wave layer 1 — slow, wide, BRIGHT
float wz1 = z * 0.8f;
float wave1Y = Hlsl.Sin(wz1 * 3f + angle * 2f + time * 0.3f) * (0.4f + mid * 0.6f);
float wave1Dist = Hlsl.Abs(ry - wave1Y * (1f - radius * 0.5f));
float wave1 = Hlsl.Exp(-wave1Dist * wave1Dist * 60f * depth) * Hlsl.Exp(-depth * 0.04f);
waveAccum += wave1 * 1.3f;
// Wave layer 2 — faster, narrower
float wz2 = z * 1.2f;
float wave2Y = Hlsl.Sin(wz2 * 5f - angle * 3f + time * 0.5f) * (0.3f + bass * 0.5f);
float wave2Dist = Hlsl.Abs(ry - wave2Y * (1f - radius * 0.4f));
float wave2 = Hlsl.Exp(-wave2Dist * wave2Dist * 90f * depth) * Hlsl.Exp(-depth * 0.05f);
waveAccum += wave2;
// Wave layer 3 — high-frequency detail
float wz3 = z * 1.6f;
float wave3Y = Hlsl.Sin(wz3 * 8f + angle + time * 0.7f) * (0.15f + high * 0.35f);
float wave3Dist = Hlsl.Abs(ry - wave3Y * (1f - radius * 0.3f));
float wave3 = Hlsl.Exp(-wave3Dist * wave3Dist * 150f * depth) * Hlsl.Exp(-depth * 0.06f);
waveAccum += wave3 * 0.6f;
// Wave layer 4 — horizontal cross-wave
float wave4Y = Hlsl.Sin(z * 4f + angle * 4f - time * 0.4f) * (0.2f + mid * 0.3f);
float wave4Dist = Hlsl.Abs(rx - wave4Y * (1f - radius * 0.5f));
float wave4 = Hlsl.Exp(-wave4Dist * wave4Dist * 80f * depth) * Hlsl.Exp(-depth * 0.05f);
waveAccum += wave4 * 0.8f;
// Wave layer 5 — diagonal slash
float wave5Val = Hlsl.Sin(z * 6f + angle * 6f + time * 0.6f) * (0.15f + energy * 0.3f);
float wave5Dist = Hlsl.Abs((rx + ry) * 0.707f - wave5Val * (1f - radius * 0.4f));
float wave5 = Hlsl.Exp(-wave5Dist * wave5Dist * 100f * depth) * Hlsl.Exp(-depth * 0.05f);
waveAccum += wave5 * 0.5f;
// Color the waves — deep purple to magenta gradient
float waveHue = Hlsl.Frac(0.8f + depth * 0.01f + time * 0.015f);
float3 waveCol = PurplePalette(waveHue, energy);
color = new float3(
color.X + waveCol.X * waveAccum,
color.Y + waveCol.Y * waveAccum,
color.Z + waveCol.Z * waveAccum);
// === LAYER 2: Dotted particle lines ===
// Rings of dots flowing through the tunnel
float dotRingZ = Hlsl.Frac(z * 0.12f);
float dotRing = Hlsl.Exp(-dotRingZ * dotRingZ * 300f)
+ Hlsl.Exp(-(1f - dotRingZ) * (1f - dotRingZ) * 300f);
// Angular dots around each ring
float dotAngle = angle * 12f + z * 2f;
float dotPhase = Hlsl.Frac(dotAngle * 0.159f);
float dot = Hlsl.Exp(-(dotPhase - 0.5f) * (dotPhase - 0.5f) * 200f);
float dotIntensity = dotRing * dot * 0.5f * Hlsl.Exp(-depth * 0.04f);
dotIntensity *= 0.4f + energy * 0.6f;
float3 dotCol = PurplePalette(Hlsl.Frac(0.85f + angle * 0.05f + time * 0.02f), energy);
color = new float3(
color.X + dotCol.X * dotIntensity,
color.Y + dotCol.Y * dotIntensity,
color.Z + dotCol.Z * dotIntensity);
// === LAYER 3: Flowing wireframe grid in tunnel space ===
float gridU = Hlsl.Frac(angle * 4f * 0.159f + time * 0.05f);
float gridV = Hlsl.Frac(z * 0.06f);
float gridLineU = Hlsl.Exp(-(gridU - 0.5f) * (gridU - 0.5f) * 400f);
float gridLineV = Hlsl.Exp(-gridV * gridV * 200f) + Hlsl.Exp(-(1f - gridV) * (1f - gridV) * 200f);
float grid = (gridLineU + gridLineV * 0.6f) * Hlsl.Exp(-depth * 0.06f) * 0.15f;
grid *= 0.3f + mid * 0.7f;
float3 gridCol = PurplePalette(Hlsl.Frac(0.75f + time * 0.01f), 0.3f);
color = new float3(
color.X + gridCol.X * grid,
color.Y + gridCol.Y * grid,
color.Z + gridCol.Z * grid);
// === LAYER 4: Central morphing shape ===
// Warped blob at center that morphs with audio
float blobAngle = angle + time * 0.2f;
float blobShape = 0.15f + 0.06f * Hlsl.Sin(blobAngle * 3f + time * 0.5f)
+ 0.04f * Hlsl.Sin(blobAngle * 5f - time * 0.3f) * mid
+ 0.03f * Hlsl.Sin(blobAngle * 7f + time * 0.8f) * high
+ bass * 0.05f;
float blobDist = radius - blobShape;
float blobGlow = Hlsl.Exp(-blobDist * blobDist * 60f) * 0.6f;
float blobCore = Hlsl.Exp(-blobDist * blobDist * 300f) * 0.4f;
float3 blobCol = PurplePalette(Hlsl.Frac(0.9f + blobAngle * 0.05f + time * 0.025f), energy);
color = new float3(
color.X + blobCol.X * blobGlow + 0.8f * blobCore,
color.Y + blobCol.Y * blobGlow + 0.6f * blobCore,
color.Z + blobCol.Z * blobGlow + 1f * blobCore);
// === LAYER 5: Floating particle sparkle ===
float sparkleAngle = angle * 8f + z * 5f + time * 1.2f;
float sparkleHash = Hlsl.Frac(Hlsl.Sin(sparkleAngle * 43758.5453f + Hlsl.Floor(depth * 20f) * 12.9898f) * 43758.5453f);
float sparkle = Hlsl.Step(0.93f, sparkleHash) * Hlsl.Exp(-depth * 0.04f) * 0.6f;
sparkle *= 0.4f + energy * 0.8f;
color = new float3(
color.X + sparkle * 0.7f,
color.Y + sparkle * 0.4f,
color.Z + sparkle * 1f);
// === FEEDBACK TRAIL — ghost copy zoomed out ===
float2 fbUV = new float2(uv.X * 0.92f, uv.Y * 0.92f);
float fbCos = Hlsl.Cos(time * 0.03f);
float fbSin = Hlsl.Sin(time * 0.03f);
float2 fbRot = new float2(
fbUV.X * fbCos - fbUV.Y * fbSin,
fbUV.X * fbSin + fbUV.Y * fbCos);
float fbR = Hlsl.Sqrt(fbRot.X * fbRot.X + fbRot.Y * fbRot.Y);
fbR = Hlsl.Max(fbR, 0.001f);
float fbAngle = Hlsl.Atan2(fbRot.Y, fbRot.X);
float fbDepth = 1f / fbR;
float fbZ = fbDepth + time * 2f - 0.3f;
float fbWave1 = Hlsl.Sin(fbZ * 0.8f * 3f + fbAngle * 2f + (time - 0.3f) * 0.3f) * 0.3f;
float fbWaveDist = Hlsl.Abs(fbRot.Y - fbWave1 * (1f - fbR * 0.5f));
float fbWaveInt = Hlsl.Exp(-fbWaveDist * fbWaveDist * 60f * fbDepth) * Hlsl.Exp(-fbDepth * 0.06f) * 0.12f;
float3 fbCol = PurplePalette(Hlsl.Frac(0.82f + time * 0.01f), 0.5f);
color = new float3(
color.X + fbCol.X * fbWaveInt,
color.Y + fbCol.Y * fbWaveInt,
color.Z + fbCol.Z * fbWaveInt);
// === POST: Energy boost — cranked up ===
float boost = 1.2f + energy * 1.2f;
color = new float3(color.X * boost, color.Y * boost, color.Z * boost);
// === POST: Center glow — stronger ===
float centerGlow = Hlsl.Exp(-radius * radius * 2f) * (0.25f + bass * 0.5f);
color = new float3(
color.X + 0.6f * centerGlow,
color.Y + 0.2f * centerGlow,
color.Z + 0.9f * centerGlow);
// === POST: Vignette ===
float edgeDist = Hlsl.Max(Hlsl.Abs(uv.X), Hlsl.Abs(uv.Y));
float vig = 1f - Hlsl.Saturate((edgeDist - 0.7f) * 1.5f);
vig = vig * vig;
color = new float3(color.X * vig, color.Y * vig, color.Z * vig);
float cr = Hlsl.Saturate(color.X);
float cg = Hlsl.Saturate(color.Y);
float cb = Hlsl.Saturate(color.Z);
float ca = Hlsl.Max(cr, Hlsl.Max(cg, cb));
// Boost with audio: baseline visible, audio intensifies
float intensity = 0.35f + 1.5f * energy;
return new float4(Hlsl.Saturate(cr * intensity), Hlsl.Saturate(cg * intensity), Hlsl.Saturate(cb * intensity), Hlsl.Saturate(ca * intensity));
}
/// <summary>
/// Deep purple/magenta neon palette. hue shifts within the purple range,
/// energy brightens and adds pink/white highlights.
/// </summary>
private static float3 PurplePalette(float t, float energy)
{
// Base: cycle through deep purple → magenta → blue-purple
float phase = t * 6.28318f;
float r = 0.4f + 0.3f * Hlsl.Sin(phase + 0f);
float g = 0.1f + 0.15f * Hlsl.Sin(phase + 2.5f);
float b = 0.5f + 0.35f * Hlsl.Sin(phase + 1.2f);
// Energy pushes toward bright pink/white
r = Hlsl.Lerp(r, 1f, energy * 0.3f);
g = Hlsl.Lerp(g, 0.5f, energy * 0.2f);
b = Hlsl.Lerp(b, 1f, energy * 0.25f);
return new float3(
Hlsl.Saturate(r),
Hlsl.Saturate(g),
Hlsl.Saturate(b));
}
}

View File

@@ -0,0 +1,237 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ComputeSharp;
using ComputeSharp.D2D1;
namespace Microsoft.CmdPal.UI.Controls.ShaderEffects.Shaders;
/// <summary>
/// Audio-reactive waveform tunnel: the camera flies through a glowing
/// corridor whose walls oscillate with audio. Bass drives forward movement,
/// mids shape the wall geometry, and highs tint the neon highlights.
/// </summary>
[D2DInputCount(0)]
[D2DRequiresScenePosition]
[D2DShaderProfile(D2D1ShaderProfile.PixelShader50)]
[D2DGeneratedPixelShaderDescriptor]
public readonly partial struct WaveformTunnelShader(
float time,
int width,
int height,
float4 bands0,
float4 bands1,
float4 bands2) : ID2D1PixelShader
{
public float4 Execute()
{
float2 pos = D2D.GetScenePosition().XY;
float w = (float)width;
float h = (float)height;
float minDim = Hlsl.Min(w, h);
// Centered UV in [-1, 1] with aspect correction
// Stretch to fill the window
float2 uv = new float2(
(pos.X / w - 0.5f) * 2f,
(pos.Y / h - 0.5f) * 2f);
// Audio
float bass = (bands0.X + bands0.Y + bands0.Z) * 0.333f;
float mid = (bands0.W + bands1.X + bands1.Y + bands1.Z + bands1.W) * 0.2f;
float high = (bands2.X + bands2.Y + bands2.Z + bands2.W) * 0.25f;
float energy = (bass + mid + high) * 0.333f;
// Gentle continuous camera rotation — purely time-driven, no audio jitter
float rotAngle = time * 0.15f;
float cosR = Hlsl.Cos(rotAngle);
float sinR = Hlsl.Sin(rotAngle);
float2 ruv = new float2(
uv.X * cosR - uv.Y * sinR,
uv.X * sinR + uv.Y * cosR);
// Polar coords of rotated UV
float angle = Hlsl.Atan2(ruv.Y, ruv.X);
float radius = Hlsl.Sqrt(ruv.X * ruv.X + ruv.Y * ruv.Y);
// Prevent division by zero at exact center
radius = Hlsl.Max(radius, 0.001f);
// === TUNNEL MAPPING ===
// Inverse radius gives depth (z); angle gives position around the tunnel
float depth = 1f / radius;
// Constant forward speed — audio drives visuals, not camera position
float speed = time * 3f;
float tunnelZ = depth + speed;
float tunnelAngle = angle;
// === TUNNEL WALL WAVEFORMS ===
// Main wall oscillation — driven by mid frequencies
float wallWave1 = Hlsl.Sin(tunnelZ * 4f + tunnelAngle * 3f + time * 0.8f) * (0.3f + mid * 0.7f);
float wallWave2 = Hlsl.Sin(tunnelZ * 7f - tunnelAngle * 2f + time * 1.2f) * (0.15f + mid * 0.4f);
float wallWave3 = Hlsl.Sin(tunnelZ * 13f + time * 2f) * (0.08f + high * 0.3f);
float wallPattern = wallWave1 + wallWave2 + wallWave3;
// Bass-reactive tunnel pulsing — walls breathe inward on hits
float tunnelPulse = 1f + 0.15f * Hlsl.Sin(tunnelZ * 2f + time) * bass;
// === RING LINES (depth markers) ===
float ringFreq = 8f;
float ringZ = Hlsl.Frac(tunnelZ * ringFreq * 0.1f);
float ring = Hlsl.Exp(-ringZ * ringZ * 200f) + Hlsl.Exp(-(1f - ringZ) * (1f - ringZ) * 200f);
ring *= 0.6f + 0.4f * energy;
// === LONGITUDINAL LINES (ribs along tunnel) ===
float ribCount = 12f;
float ribAngle = Hlsl.Frac(tunnelAngle * ribCount * 0.159f + 0.5f);
float rib = Hlsl.Exp(-(ribAngle - 0.5f) * (ribAngle - 0.5f) * 80f);
rib *= 0.3f + 0.7f * mid;
// === WALL SURFACE PATTERN (waveform texture) ===
float surface = wallPattern * 0.5f + 0.5f;
surface = surface * surface;
// === EDGE GLOW (brighter at tunnel edges, simulates lighting) ===
float edgeGlow = Hlsl.Exp(-radius * 1.2f);
float wallGlow = (1f - edgeGlow) * 0.8f;
// === NEON STRIPE EFFECT ===
float stripe = Hlsl.Sin(tunnelZ * 20f + tunnelAngle * 4f) * 0.5f + 0.5f;
stripe = Hlsl.Step(0.92f, stripe) * (0.5f + bass);
// === COMPOSITE INTENSITY ===
float intensity = 0f;
intensity += surface * wallGlow * 0.5f;
intensity += ring * 0.6f;
intensity += rib * wallGlow * 0.3f;
intensity += stripe * 0.4f;
// Center light shaft
intensity += edgeGlow * 0.2f * (0.5f + energy);
// === DEPTH FOG (fade distant parts) ===
float fog = Hlsl.Exp(-depth * 0.08f);
intensity *= Hlsl.Lerp(0.2f, 1f, fog);
// === COLORING ===
// Base hue shifts with depth and angle — creates flowing neon ribbons
float hue1 = Hlsl.Frac(tunnelZ * 0.05f + time * 0.02f);
float hue2 = Hlsl.Frac(tunnelAngle * 0.159f + time * 0.05f + 0.3f);
float hue3 = Hlsl.Frac(time * 0.03f + depth * 0.02f + 0.6f);
float3 col1 = HueToRGB(hue1);
float3 col2 = HueToRGB(hue2);
float3 col3 = HueToRGB(hue3);
// Blend colors based on what element is dominant
float3 wallCol = new float3(
col1.X * surface + col2.X * rib * 0.5f,
col1.Y * surface + col2.Y * rib * 0.5f,
col1.Z * surface + col2.Z * rib * 0.5f);
float3 ringCol = new float3(col3.X * 1.2f, col3.Y * 1.2f, col3.Z * 1.2f);
float3 stripeCol = new float3(
Hlsl.Lerp(col2.X, 1f, 0.5f),
Hlsl.Lerp(col2.Y, 1f, 0.5f),
Hlsl.Lerp(col2.Z, 1f, 0.5f));
float3 color = new float3(
wallCol.X * wallGlow * 0.5f + ringCol.X * ring * 0.6f + stripeCol.X * stripe * 0.4f,
wallCol.Y * wallGlow * 0.5f + ringCol.Y * ring * 0.6f + stripeCol.Y * stripe * 0.4f,
wallCol.Z * wallGlow * 0.5f + ringCol.Z * ring * 0.6f + stripeCol.Z * stripe * 0.4f);
// Central light shaft — white/cyan glow at the center (light at end of tunnel)
float3 shaftCol = HueToRGB(Hlsl.Frac(time * 0.04f + 0.5f));
float shaftIntensity = edgeGlow * edgeGlow * (0.3f + energy * 0.7f);
color = new float3(
color.X + shaftCol.X * shaftIntensity,
color.Y + shaftCol.Y * shaftIntensity,
color.Z + shaftCol.Z * shaftIntensity);
// Apply depth fog
color = new float3(color.X * fog, color.Y * fog, color.Z * fog);
// Energy flash — bass hits cause brief bright flash
float flash = bass * bass * 0.3f;
float3 flashCol = HueToRGB(Hlsl.Frac(time * 0.08f));
color = new float3(
color.X + flashCol.X * flash,
color.Y + flashCol.Y * flash,
color.Z + flashCol.Z * flash);
// === SECOND TUNNEL LAYER (ghostly inner tube) ===
float innerRadius = radius * 1.5f + 0.05f;
float innerDepth = 1f / Hlsl.Max(innerRadius, 0.001f);
float innerZ = innerDepth + speed * 0.7f;
float innerWave = Hlsl.Sin(innerZ * 6f + tunnelAngle * 5f + time) * 0.5f + 0.5f;
float innerRing = Hlsl.Exp(-Hlsl.Frac(innerZ * 0.8f) * Hlsl.Frac(innerZ * 0.8f) * 150f);
float innerFog = Hlsl.Exp(-innerDepth * 0.1f);
float innerIntensity = (innerWave * 0.3f + innerRing * 0.4f) * innerFog * 0.25f;
float3 innerCol = HueToRGB(Hlsl.Frac(hue1 + 0.5f));
color = new float3(
color.X + innerCol.X * innerIntensity,
color.Y + innerCol.Y * innerIntensity,
color.Z + innerCol.Z * innerIntensity);
// === AUDIO WAVEFORM ON WALLS ===
// Project band levels as glowing bumps along the tunnel
float bandPos = Hlsl.Frac(tunnelAngle * 1.91f);
float bandIdx = bandPos * 11f;
float bFloor = Hlsl.Floor(bandIdx);
float bFrac = bandIdx - bFloor;
float bSmooth = bFrac * bFrac * (3f - 2f * bFrac);
float bVal0 = GetBand((int)bFloor);
float bVal1 = GetBand(Hlsl.Min((int)bFloor + 1, 11));
float bandLevel = Hlsl.Lerp(bVal0, bVal1, bSmooth);
float bandGlow = bandLevel * Hlsl.Exp(-radius * 2f) * fog * 0.5f;
float3 bandCol = HueToRGB(Hlsl.Frac(bandPos + time * 0.06f));
color = new float3(
color.X + bandCol.X * bandGlow,
color.Y + bandCol.Y * bandGlow,
color.Z + bandCol.Z * bandGlow);
// === VIGNETTE ===
float vignette = 1f - radius * 0.25f;
vignette = Hlsl.Saturate(vignette * vignette);
color = new float3(color.X * vignette, color.Y * vignette, color.Z * vignette);
float cr = Hlsl.Saturate(color.X);
float cg = Hlsl.Saturate(color.Y);
float cb = Hlsl.Saturate(color.Z);
float ca = Hlsl.Max(cr, Hlsl.Max(cg, cb));
// Boost with audio: baseline visible, audio intensifies
float audioBoost = 0.35f + 1.5f * energy;
return new float4(Hlsl.Saturate(cr * audioBoost), Hlsl.Saturate(cg * audioBoost), Hlsl.Saturate(cb * audioBoost), Hlsl.Saturate(ca * audioBoost));
}
private float GetBand(int i)
{
float r = bands0.X;
r = (i >= 1) ? bands0.Y : r;
r = (i >= 2) ? bands0.Z : r;
r = (i >= 3) ? bands0.W : r;
r = (i >= 4) ? bands1.X : r;
r = (i >= 5) ? bands1.Y : r;
r = (i >= 6) ? bands1.Z : r;
r = (i >= 7) ? bands1.W : r;
r = (i >= 8) ? bands2.X : r;
r = (i >= 9) ? bands2.Y : r;
r = (i >= 10) ? bands2.Z : r;
r = (i >= 11) ? bands2.W : r;
return r;
}
private static float3 HueToRGB(float hue)
{
float r = Hlsl.Abs(Hlsl.Frac(hue) * 6f - 3f) - 1f;
float g = Hlsl.Abs(Hlsl.Frac(hue + 0.6667f) * 6f - 3f) - 1f;
float b = Hlsl.Abs(Hlsl.Frac(hue + 0.3333f) * 6f - 3f) - 1f;
return new float3(Hlsl.Saturate(r), Hlsl.Saturate(g), Hlsl.Saturate(b));
}
}

View File

@@ -0,0 +1,246 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ComputeSharp;
using ComputeSharp.D2D1;
namespace Microsoft.CmdPal.UI.Controls.ShaderEffects.Shaders;
/// <summary>
/// MilkDrop-inspired generative visualization: kaleidoscopic symmetry,
/// fractal-like domain warping, spiral vortices, audio-reactive ripples,
/// vibrant spectrum color cycling, and soft additive glow.
/// </summary>
[D2DInputCount(0)]
[D2DRequiresScenePosition]
[D2DShaderProfile(D2D1ShaderProfile.PixelShader50)]
[D2DGeneratedPixelShaderDescriptor]
public readonly partial struct WinampScopeShader(
float time,
int width,
int height,
float4 bands0,
float4 bands1,
float4 bands2) : ID2D1PixelShader
{
public float4 Execute()
{
float2 pos = D2D.GetScenePosition().XY;
float w = (float)width;
float h = (float)height;
float aspect = w / h;
float minDim = Hlsl.Min(w, h);
// Scale by height so effect fills the vertical space
// Stretch to fill the window
float2 uv = new float2(
(pos.X / w - 0.5f) * 2f,
(pos.Y / h - 0.5f) * 2f);
// Audio bands → energy zones
float bass = (bands0.X + bands0.Y + bands0.Z) * 0.333f;
float mid = (bands0.W + bands1.X + bands1.Y + bands1.Z + bands1.W) * 0.2f;
float high = (bands2.X + bands2.Y + bands2.Z + bands2.W) * 0.25f;
float energy = (bass + mid + high) * 0.333f;
// Polar coordinates
float r = Hlsl.Sqrt(uv.X * uv.X + uv.Y * uv.Y);
float theta = Hlsl.Atan2(uv.Y, uv.X);
// === KALEIDOSCOPE SYMMETRY ===
// Audio-reactive segment count: 6-12 fold
float segments = 6f + Hlsl.Floor(energy * 6f);
float segAngle = 6.28318f / segments;
float kAngle = theta + 628.318f;
kAngle = kAngle - Hlsl.Floor(kAngle / segAngle) * segAngle;
kAngle = Hlsl.Abs(kAngle - segAngle * 0.5f);
float2 kp = new float2(Hlsl.Cos(kAngle) * r, Hlsl.Sin(kAngle) * r);
// === DOMAIN WARPING (fractal-like distortion) ===
float warpIntensity = 0.3f + 0.4f * bass;
float2 wp = DomainWarp(kp, time, warpIntensity);
// === LAYER 1: Flowing fractal field ===
float fractal = FractalPattern(wp, time, bass);
// === LAYER 2: Spiral vortex ===
float spiralAngle = theta + r * (3f + 2f * bass) - time * (0.5f + 0.5f * mid);
float spiral = Hlsl.Sin(spiralAngle * 5f) * 0.5f + 0.5f;
spiral *= Hlsl.Exp(-r * 0.5f);
float spiral2Angle = theta - r * (2f + 3f * high) + time * 0.3f;
float spiral2 = Hlsl.Sin(spiral2Angle * 3f) * 0.5f + 0.5f;
spiral2 *= Hlsl.Exp(-r * 0.7f);
// === LAYER 3: Audio-reactive ripples ===
float ripple = Hlsl.Sin((r - time * 0.8f - bass * 2f) * 12f);
ripple *= Hlsl.Exp(-r * 1.5f);
float ripple2 = Hlsl.Sin((r * 8f - time * 1.5f) * (1f + energy * 3f));
ripple2 *= Hlsl.Exp(-r * 2f) * 0.5f;
// === LAYER 4: Radial burst on bass hits ===
float burstAngle = theta + time * 0.2f;
float rays = Hlsl.Abs(Hlsl.Sin(burstAngle * 8f));
float burst = rays * Hlsl.Exp(-r * (2f - bass * 1.5f)) * bass * 2f;
// === LAYER 5: Morphing blob/flower ===
float blobAngle = kAngle * 3f + time * 0.7f;
float blobR = 0.4f + 0.3f * Hlsl.Sin(blobAngle) * (0.5f + bass);
float blob = 1f - Hlsl.SmoothStep(blobR - 0.1f, blobR + 0.1f, r);
// === COMPOSITE INTENSITY ===
float intensity = 0f;
intensity += fractal * 0.35f;
intensity += spiral * 0.25f * (0.5f + mid);
intensity += spiral2 * 0.15f;
intensity += (ripple * 0.5f + 0.5f) * 0.2f * (0.3f + bass);
intensity += (ripple2 * 0.5f + 0.5f) * 0.1f;
intensity += burst;
intensity += blob * 0.3f;
// === VIBRANT SPECTRUM COLORING ===
// Hue from angle + radius + time (creates rainbow kaleidoscope)
float hue1 = Hlsl.Frac(theta * 0.159f + time * 0.05f + r * 0.3f);
float hue2 = Hlsl.Frac(theta * 0.159f - time * 0.08f + fractal * 0.2f);
float hue3 = Hlsl.Frac(time * 0.03f + r * 0.5f - theta * 0.1f);
float3 col1 = HueToRGB(hue1) * fractal;
float3 col2 = HueToRGB(hue2) * (spiral + spiral2);
float3 col3 = HueToRGB(hue3) * (ripple * 0.5f + 0.5f);
float3 colBurst = HueToRGB(Hlsl.Frac(time * 0.1f)) * burst;
float3 colBlob = HueToRGB(Hlsl.Frac(time * 0.06f + 0.5f)) * blob;
// Additive blending of colored layers
float3 color = new float3(0f, 0f, 0f);
color = new float3(
color.X + col1.X * 0.4f, color.Y + col1.Y * 0.4f, color.Z + col1.Z * 0.4f);
color = new float3(
color.X + col2.X * 0.35f, color.Y + col2.Y * 0.35f, color.Z + col2.Z * 0.35f);
color = new float3(
color.X + col3.X * 0.2f * (0.3f + bass),
color.Y + col3.Y * 0.2f * (0.3f + bass),
color.Z + col3.Z * 0.2f * (0.3f + bass));
color = new float3(
color.X + colBurst.X, color.Y + colBurst.Y, color.Z + colBurst.Z);
color = new float3(
color.X + colBlob.X * 0.3f, color.Y + colBlob.Y * 0.3f, color.Z + colBlob.Z * 0.3f);
// Energy-reactive brightness boost
float boost = 1f + energy * 1.5f;
color = new float3(color.X * boost, color.Y * boost, color.Z * boost);
// === SOFT GLOW (simulated bloom) ===
// Add a warm glow concentrated at the center
float glowR = Hlsl.Exp(-r * r * 0.5f) * energy * 0.4f;
float3 glowColor = HueToRGB(Hlsl.Frac(time * 0.04f));
color = new float3(
color.X + glowColor.X * glowR,
color.Y + glowColor.Y * glowR,
color.Z + glowColor.Z * glowR);
// === FEEDBACK SIMULATION ===
// Multiple warped copies at different scales create depth
float2 fb1 = new float2(uv.X * 0.8f, uv.Y * 0.8f);
float fbAngle = time * 0.02f;
float2 fbRot = new float2(
fb1.X * Hlsl.Cos(fbAngle) - fb1.Y * Hlsl.Sin(fbAngle),
fb1.X * Hlsl.Sin(fbAngle) + fb1.Y * Hlsl.Cos(fbAngle));
float fbR = Hlsl.Sqrt(fbRot.X * fbRot.X + fbRot.Y * fbRot.Y);
float fbTheta = Hlsl.Atan2(fbRot.Y, fbRot.X) + 628.318f;
float fbKA = fbTheta - Hlsl.Floor(fbTheta / segAngle) * segAngle;
fbKA = Hlsl.Abs(fbKA - segAngle * 0.5f);
float2 fbKP = new float2(Hlsl.Cos(fbKA) * fbR, Hlsl.Sin(fbKA) * fbR);
float fbPattern = FractalPattern(DomainWarp(fbKP, time - 0.5f, warpIntensity * 0.7f), time - 0.5f, bass * 0.7f);
float3 fbCol = HueToRGB(Hlsl.Frac(hue1 + 0.3f));
color = new float3(
color.X + fbCol.X * fbPattern * 0.15f,
color.Y + fbCol.Y * fbPattern * 0.15f,
color.Z + fbCol.Z * fbPattern * 0.15f);
// === VIGNETTE ===
float vignette = 1f - Hlsl.Saturate(r * 0.4f);
vignette = vignette * vignette;
color = new float3(color.X * vignette, color.Y * vignette, color.Z * vignette);
float cr = Hlsl.Saturate(color.X);
float cg = Hlsl.Saturate(color.Y);
float cb = Hlsl.Saturate(color.Z);
float ca = Hlsl.Max(cr, Hlsl.Max(cg, cb));
// Boost with audio: baseline visible, audio intensifies
float audioBoost = 0.35f + 1.5f * energy;
return new float4(Hlsl.Saturate(cr * audioBoost), Hlsl.Saturate(cg * audioBoost), Hlsl.Saturate(cb * audioBoost), Hlsl.Saturate(ca * audioBoost));
}
private static float2 DomainWarp(float2 p, float t, float intensity)
{
float px = p.X + intensity * Hlsl.Sin(p.Y * 2.1f + t * 0.7f);
float py = p.Y + intensity * Hlsl.Cos(p.X * 1.9f + t * 0.6f);
px += intensity * 0.5f * Hlsl.Sin(py * 3.7f + t * 1.1f);
py += intensity * 0.5f * Hlsl.Cos(px * 3.3f + t * 0.9f);
px += intensity * 0.25f * Hlsl.Sin(py * 7.1f + t * 1.7f);
py += intensity * 0.25f * Hlsl.Cos(px * 6.3f + t * 1.3f);
return new float2(px, py);
}
/// <summary>
/// Layered sine-based fractal pattern with rotation per octave.
/// Creates flowing organic shapes similar to MilkDrop presets.
/// </summary>
private static float FractalPattern(float2 p, float t, float bassEnergy)
{
float val = 0f;
float amp = 1f;
float px = p.X;
float py = p.Y;
float rotSpeed = 0.5f + bassEnergy * 0.3f;
// Octave 1
val += amp * (Hlsl.Sin(px * 1.5f + t * 0.4f * rotSpeed) *
Hlsl.Cos(py * 1.3f + t * 0.3f) * 0.5f + 0.5f);
float nx = px * 0.866f - py * 0.5f;
float ny = px * 0.5f + py * 0.866f;
px = nx * 1.8f;
py = ny * 1.8f;
amp *= 0.6f;
// Octave 2
val += amp * (Hlsl.Sin(px * 1.2f + t * 0.5f * rotSpeed + 1f) *
Hlsl.Cos(py * 1.4f - t * 0.35f) * 0.5f + 0.5f);
nx = px * 0.707f - py * 0.707f;
ny = px * 0.707f + py * 0.707f;
px = nx * 1.7f;
py = ny * 1.7f;
amp *= 0.55f;
// Octave 3
val += amp * (Hlsl.Sin(px * 1.1f - t * 0.6f * rotSpeed + 2f) *
Hlsl.Cos(py * 0.9f + t * 0.4f) * 0.5f + 0.5f);
nx = px * 0.94f - py * 0.342f;
ny = px * 0.342f + py * 0.94f;
px = nx * 1.6f;
py = ny * 1.6f;
amp *= 0.5f;
// Octave 4
val += amp * (Hlsl.Sin(px + t * 0.7f * rotSpeed) *
Hlsl.Cos(py - t * 0.5f) * 0.5f + 0.5f);
return val;
}
/// <summary>
/// Branchless HSV hue (S=1, V=1) to RGB.
/// </summary>
private static float3 HueToRGB(float hue)
{
float r = Hlsl.Abs(Hlsl.Frac(hue) * 6f - 3f) - 1f;
float g = Hlsl.Abs(Hlsl.Frac(hue + 0.6667f) * 6f - 3f) - 1f;
float b = Hlsl.Abs(Hlsl.Frac(hue + 0.3333f) * 6f - 3f) - 1f;
return new float3(Hlsl.Saturate(r), Hlsl.Saturate(g), Hlsl.Saturate(b));
}
}

View File

@@ -32,6 +32,12 @@
</Border.Background>
</Border>
<cpcontrols:ShaderEffectControl
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
EffectType="{x:Bind WindowViewModel.ShaderEffect, Mode=OneWay}"
IsHitTestVisible="False" />
<cpcontrols:BlurImageControl
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"

View File

@@ -83,7 +83,7 @@ public sealed partial class DockWindow : WindowEx,
_themeService = serviceProvider.GetRequiredService<IThemeService>();
_themeService.ThemeChanged += ThemeService_ThemeChanged;
InitializeBackdropSupport();
_windowViewModel = new DockWindowViewModel(_themeService);
_windowViewModel = new DockWindowViewModel(_themeService, _settingsService);
_dock = new DockControl(viewModel);
InitializeComponent();

View File

@@ -17,6 +17,12 @@
mc:Ignorable="d">
<Grid x:Name="RootElement">
<controls:ShaderEffectControl
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
EffectType="{x:Bind ViewModel.ShaderEffect, Mode=OneWay}"
IsHitTestVisible="False" />
<controls:BlurImageControl
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"

View File

@@ -27,6 +27,7 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<UseWinRT>true</UseWinRT>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<!-- Defines: to quickly add project-wide define constants / feature flags -->
@@ -117,6 +118,7 @@
<PackageReference Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Graphics.Win2D" />
<PackageReference Include="ComputeSharp.D2D1.WinUI" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" />

View File

@@ -81,6 +81,11 @@
<InfoBadge Style="{StaticResource NewInfoBadge}" />
</NavigationViewItem.InfoBadge>
</NavigationViewItem>
<NavigationViewItem
x:Name="ShaderEffectsPageNavItem"
Content="Shader Effects"
Icon="{ui:FontIcon Glyph=&#xE2B1;}"
Tag="ShaderEffects" />
<!-- "Internal Tools" page item is added dynamically from code -->
</NavigationView.MenuItems>
<Grid>

View File

@@ -127,6 +127,9 @@ public sealed partial class SettingsWindow : WindowEx,
case "Dock":
pageType = typeof(DockSettingsPage);
break;
case "ShaderEffects":
pageType = typeof(ShaderEffectsPage);
break;
case "Internal":
pageType = typeof(InternalPage);
break;
@@ -311,6 +314,11 @@ public sealed partial class SettingsWindow : WindowEx,
var pageType = RS_.GetString("Settings_PageTitles_DockPage");
BreadCrumbs.Add(new(pageType, pageType));
}
else if (e.SourcePageType == typeof(ShaderEffectsPage))
{
NavView.SelectedItem = ShaderEffectsPageNavItem;
BreadCrumbs.Add(new("Shader Effects", "Shader Effects"));
}
else if (e.SourcePageType == typeof(ExtensionPage) && e.Parameter is ProviderSettingsViewModel vm)
{
NavView.SelectedItem = ExtensionPageNavItem;

View File

@@ -0,0 +1,59 @@
<!-- Copyright (c) Microsoft Corporation -->
<!-- The Microsoft Corporation licenses this file to you under the MIT license. -->
<!-- See the LICENSE file in the project root for more information. -->
<Page
x:Class="Microsoft.CmdPal.UI.Settings.ShaderEffectsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<ScrollViewer>
<StackPanel Margin="16" Spacing="{StaticResource SettingsCardSpacing}">
<!-- Command Palette Shader -->
<controls:SettingsCard
Header="Command Palette"
Description="Shader effect for the main Command Palette window"
HeaderIcon="{ui:FontIcon Glyph=&#xE2B1;}">
<ComboBox
MinWidth="{StaticResource SettingActionControlMinWidth}"
SelectedIndex="{x:Bind ViewModel.Appearance.ShaderEffectIndex, Mode=TwoWay}">
<ComboBoxItem Content="Off" />
<ComboBoxItem Content="Audio Glow" />
<ComboBoxItem Content="Alchemy" />
<ComboBoxItem Content="MilkDrop" />
<ComboBoxItem Content="Waveform Tunnel" />
<ComboBoxItem Content="Radial Spectrum" />
<ComboBoxItem Content="Kaleidoscope" />
<ComboBoxItem Content="Kaleido Tunnel" />
<ComboBoxItem Content="Waveform Space" />
</ComboBox>
</controls:SettingsCard>
<!-- Dock Shader -->
<controls:SettingsCard
Header="Dock"
Description="Shader effect for the Dock bar"
HeaderIcon="{ui:FontIcon Glyph=&#xF596;}">
<ComboBox
MinWidth="{StaticResource SettingActionControlMinWidth}"
SelectedIndex="{x:Bind ViewModel.DockAppearance.ShaderEffectIndex, Mode=TwoWay}">
<ComboBoxItem Content="Off" />
<ComboBoxItem Content="Audio Glow" />
<ComboBoxItem Content="Alchemy" />
<ComboBoxItem Content="MilkDrop" />
<ComboBoxItem Content="Waveform Tunnel" />
<ComboBoxItem Content="Radial Spectrum" />
<ComboBoxItem Content="Kaleidoscope" />
<ComboBoxItem Content="Kaleido Tunnel" />
<ComboBoxItem Content="Waveform Space" />
</ComboBox>
</controls:SettingsCard>
</StackPanel>
</ScrollViewer>
</Page>

View File

@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.CmdPal.UI.Settings;
public sealed partial class ShaderEffectsPage : Page
{
internal SettingsViewModel ViewModel { get; }
public ShaderEffectsPage()
{
this.InitializeComponent();
var themeService = App.Current.Services.GetService<IThemeService>()!;
var topLevelCommandManager = App.Current.Services.GetService<TopLevelCommandManager>()!;
var settingsService = App.Current.Services.GetRequiredService<ISettingsService>();
ViewModel = new SettingsViewModel(topLevelCommandManager, TaskScheduler.FromCurrentSynchronizationContext(), themeService, settingsService);
}
}