feat(mix-visualizer): Phase 10 tuning — smooth waveform, bouncy collision, 8 knobs
Smooth the loudness contour (~50 ms envelope at preprocessing + decode-time, plus smootherstep render reconstruction); retune wax↔waveform collision to bouncy/sub-unity (no explosion/stuck/jitter); split the bubbles knob into fluid-amount + fluid-viscosity (cohesion via uniform-only smin/wobble); retune scroll/gravity/heat/width ranges; make the colour rotation visible and boost OKLab chroma; the controls bar now holds its layout and hides only its knobs via a Visible parameter.
This commit is contained in:
@@ -2,12 +2,17 @@
|
||||
@using DeepDrftPublic.Client.Services
|
||||
@inject MixVisualizerControlState ControlState
|
||||
|
||||
@* The Mix visualizer controls. SEVEN continuous RadialKnobs — scroll speed, gradient rotation speed,
|
||||
lava gravity, lava heat, blob density, collision strength, waveform width — each its own dedicated
|
||||
control with a Material-icon caption. Visibility is controlled by Blazor, not CSS: the host page
|
||||
renders this component only while the lava-lamp toggle is on (@if-guarded), so when off it is not in
|
||||
the DOM at all. There is no collapse/expand animation and no glass surface — the knobs simply appear
|
||||
in their own transparent band and disappear when un-rendered.
|
||||
@* The Mix visualizer controls. EIGHT continuous RadialKnobs — scroll speed, gradient rotation speed,
|
||||
lava gravity, lava heat, fluid amount, fluid viscosity, collision strength, waveform width — each its
|
||||
own dedicated control with a Material-icon caption. The single "bubbles" knob is split into
|
||||
fluid-amount + fluid-viscosity (Phase 10 §5).
|
||||
|
||||
Visibility (Phase 10 §4): the host ALWAYS renders this component now and feeds the lava-lamp toggle
|
||||
into the @Visible parameter. THIS component decides knob visibility — it @if-gates the knobs but keeps
|
||||
the container's reserved size, so the content below the controls bar never pops when the lamp toggles.
|
||||
The gating is Blazor @if (matching the established "@if-gated knob band, no CSS hide/glass/animation"
|
||||
convention) — the knobs are simply not rendered when hidden, while a min-height container holds the
|
||||
layout. No collapse animation, no glass surface, no CSS visibility-hiding of populated knobs.
|
||||
|
||||
It owns NO JS interop: it mutates the shared, session-scoped MixVisualizerControlState and raises its
|
||||
Changed event. The backdrop bridge (MixWaveformVisualizer) subscribes to that event and pushes the
|
||||
@@ -21,72 +26,92 @@
|
||||
|
||||
<div class="mix-visualizer-controls-bar">
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Waveform scroll speed">
|
||||
<RadialKnob Value="@ControlState.ScrollSpeed"
|
||||
ValueChanged="@OnScrollSpeedChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.Speed" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
@if (Visible)
|
||||
{
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Waveform scroll speed">
|
||||
<RadialKnob Value="@ControlState.ScrollSpeed"
|
||||
ValueChanged="@OnScrollSpeedChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.Speed" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Color gradient rotation speed">
|
||||
<RadialKnob Value="@ControlState.GradientRotationSpeed"
|
||||
ValueChanged="@OnGradientRotationSpeedChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.Palette" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Color gradient rotation speed">
|
||||
<RadialKnob Value="@ControlState.GradientRotationSpeed"
|
||||
ValueChanged="@OnGradientRotationSpeedChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.Palette" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Lava gravity">
|
||||
<RadialKnob Value="@ControlState.LavaGravity"
|
||||
ValueChanged="@OnLavaGravityChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowDownward" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Lava gravity">
|
||||
<RadialKnob Value="@ControlState.LavaGravity"
|
||||
ValueChanged="@OnLavaGravityChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowDownward" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Lava heat">
|
||||
<RadialKnob Value="@ControlState.LavaHeat"
|
||||
ValueChanged="@OnLavaHeatChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.LocalFireDepartment" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Lava heat">
|
||||
<RadialKnob Value="@ControlState.LavaHeat"
|
||||
ValueChanged="@OnLavaHeatChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.LocalFireDepartment" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Blob density and size">
|
||||
<RadialKnob Value="@ControlState.BlobDensity"
|
||||
ValueChanged="@OnBlobDensityChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.BubbleChart" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Fluid amount">
|
||||
<RadialKnob Value="@ControlState.FluidAmount"
|
||||
ValueChanged="@OnFluidAmountChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.BubbleChart" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Collision strength">
|
||||
<RadialKnob Value="@ControlState.CollisionStrength"
|
||||
ValueChanged="@OnCollisionStrengthChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.Compress" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Fluid viscosity">
|
||||
<RadialKnob Value="@ControlState.FluidViscosity"
|
||||
ValueChanged="@OnFluidViscosityChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.Opacity" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Waveform width">
|
||||
<RadialKnob Value="@ControlState.WaveformWidth"
|
||||
ValueChanged="@OnWaveformWidthChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.SettingsEthernet" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Collision strength">
|
||||
<RadialKnob Value="@ControlState.CollisionStrength"
|
||||
ValueChanged="@OnCollisionStrengthChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.Compress" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Waveform width">
|
||||
<RadialKnob Value="@ControlState.WaveformWidth"
|
||||
ValueChanged="@OnWaveformWidthChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.SettingsEthernet" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// Whether the knob band is shown. The host wires its lava-lamp toggle straight into this — the host
|
||||
/// always renders this component, and THIS component decides knob visibility (Phase 10 §4). When
|
||||
/// false the knobs are @if-gated out but the container holds its reserved height (CSS min-height), so
|
||||
/// content below the bar never pops as the lamp toggles.
|
||||
/// </summary>
|
||||
[Parameter] public bool Visible { get; set; }
|
||||
|
||||
// Each handler mutates its own dedicated property then raises Changed — the bridge re-reads and
|
||||
// pushes the affected dial. All values are already normalized [0,1]; the bridge maps scroll speed
|
||||
// to a visible time-span and routes the rest straight to the lava/colour dials.
|
||||
@@ -115,9 +140,15 @@
|
||||
ControlState.NotifyChanged();
|
||||
}
|
||||
|
||||
private void OnBlobDensityChanged(double value)
|
||||
private void OnFluidAmountChanged(double value)
|
||||
{
|
||||
ControlState.BlobDensity = value;
|
||||
ControlState.FluidAmount = value;
|
||||
ControlState.NotifyChanged();
|
||||
}
|
||||
|
||||
private void OnFluidViscosityChanged(double value)
|
||||
{
|
||||
ControlState.FluidViscosity = value;
|
||||
ControlState.NotifyChanged();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user