refactor(12.A): rename Mix* visualizer engine to Waveform* abstraction

This commit is contained in:
daniel-c-harvey
2026-06-17 10:16:44 -04:00
parent ad94354632
commit 3839948eeb
12 changed files with 58 additions and 58 deletions
@@ -29,7 +29,7 @@ public partial class AudioPlayerBar : ComponentBase, IAsyncDisposable
// error banner.
//
// _miniDock is the minimized FAB container. We observe it in minimized state so
// --player-height stays non-zero (the FAB's actual height) and the MixWaveformVisualizer
// --player-height stays non-zero (the FAB's actual height) and the WaveformVisualizer
// clips to the top of the FAB rather than extending to the viewport bottom (fix §1).
// The player-spacer's .minimized class uses a hardcoded 60px and ignores the var,
// so publishing the FAB height here does not regress the spacer.
@@ -125,7 +125,7 @@ public partial class AudioPlayerBar : ComponentBase, IAsyncDisposable
// The Fixed embed is already in normal flow — no spacer/clip needed.
// For the docked player: we observe in BOTH expanded and minimized states
// so --player-height always reflects the live height of whichever element
// is visible. This keeps the MixWaveformVisualizer clipped to the top of
// is visible. This keeps the WaveformVisualizer clipped to the top of
// the footer in both states (fix §1).
// expanded → observe _playerRoot (full player bar, reflows across breakpoints)
// minimized → observe _miniDock (floating FAB container, ~5660px)
@@ -1,16 +1,16 @@
@namespace DeepDrftPublic.Client.Controls
@* Full-page scrolling Mix waveform background (Phase 9, 8.K). A windowed slice of the mix's loudness
@* Full-page scrolling waveform background (Phase 9, 8.K). A windowed slice of the track's loudness
datum scrolls bottom-to-top, coupled to playback; a zoom slider controls the visible time-span (and
so the apparent scroll speed, Guitar-Hero style). Strictly read-only: it self-fetches its datum from
ReleaseEntryKey, takes playback as one-way input only, and never seeks or writes back. The rAF loop and all
scroll/zoom/compositing math live in the MixVisualizer.ts interop module; this component is a thin
scroll/zoom/compositing math live in the WaveformVisualizer.ts interop module; this component is a thin
bridge that feeds it datum + playback + zoom + theme. Deliberately NOT the player-bar peak-bar idiom. *@
<div class="mix-waveform-bg">
<canvas @ref="_canvas" class="mix-waveform-canvas"></canvas>
</div>
@* The viewing controls (resolution + the three Wave 2 controls) live in MixVisualizerControls,
@* The viewing controls (resolution + the three Wave 2 controls) live in WaveformVisualizerControls,
rendered in the mix-detail foreground row below the back button — NOT here. This component is now a
pure backdrop bridge; it pushes uniforms in response to the shared MixVisualizerControlState. *@
pure backdrop bridge; it pushes uniforms in response to the shared WaveformVisualizerControlState. *@
@@ -8,10 +8,10 @@ using Microsoft.JSInterop;
namespace DeepDrftPublic.Client.Controls;
/// <summary>
/// Full-page scrolling Mix waveform background. Standalone and reusable: give it a
/// Full-page scrolling waveform background. Standalone and reusable: give it a
/// <see cref="ReleaseEntryKey"/> and it fetches its own loudness datum. The rendering itself — a windowed,
/// bottom-to-top, playback-coupled scroll with a glassy theme-aware gradient — lives in the
/// MixVisualizer.ts interop module; this component is the bridge that feeds it datum, playback
/// WaveformVisualizer.ts interop module; this component is the bridge that feeds it datum, playback
/// position, zoom, and theme, and owns the module lifecycle.
///
/// Strictly read-only (spec §D): no seek, no two-way write-back. <see cref="PlaybackPosition"/> is a
@@ -20,12 +20,12 @@ namespace DeepDrftPublic.Client.Controls;
/// <see cref="PlaybackPosition"/> parameter is the composability fallback for hosts that have no
/// player cascade (e.g. an embed) and want to drive position themselves.
/// </summary>
public partial class MixWaveformVisualizer : ComponentBase, IAsyncDisposable
public partial class WaveformVisualizer : ComponentBase, IAsyncDisposable
{
[Inject] public required IReleaseDataService ReleaseData { get; set; }
[Inject] public required IJSRuntime JS { get; set; }
[Inject] public required MixVisualizerControlState ControlState { get; set; }
[Inject] public required ILogger<MixWaveformVisualizer> Logger { get; set; }
[Inject] public required WaveformVisualizerControlState ControlState { get; set; }
[Inject] public required ILogger<WaveformVisualizer> Logger { get; set; }
// Live playback + the mix duration come from the cascaded streaming player when present. The
// cascade is IsFixed, so we subscribe to its multicast StateChanged side-channel to learn about
@@ -54,12 +54,12 @@ public partial class MixWaveformVisualizer : ComponentBase, IAsyncDisposable
/// </summary>
[Parameter] public double PlaybackPosition { get; set; }
// Bridge-level diagnostics. Mirrors the JS-side DEBUG flag in MixVisualizer.ts: when true the
// Bridge-level diagnostics. Mirrors the JS-side DEBUG flag in WaveformVisualizer.ts: when true the
// datum-fetch / subscription / playback-coupling seams log to the browser console (prefixed
// `[MixVisualizer]`, same as the JS logs so the two interleave into one timeline). These pinpoint
// `[WaveformVisualizer]`, same as the JS logs so the two interleave into one timeline). These pinpoint
// which upstream link is broken when the ribbon stays blank — set true temporarily to diagnose.
private static readonly bool Debug = false;
private const string Tag = "[MixVisualizer]";
private const string Tag = "[WaveformVisualizer]";
private static void DebugLog(string message)
{
@@ -164,12 +164,12 @@ public partial class MixWaveformVisualizer : ComponentBase, IAsyncDisposable
try
{
_module = await JS.InvokeAsync<IJSObjectReference>(
"import", "./js/visualizer/MixVisualizer.js");
"import", "./js/visualizer/WaveformVisualizer.js");
_handle = await _module.InvokeAsync<IJSObjectReference>("create", _canvas);
}
catch (JSException ex)
{
Logger.LogWarning(ex, "MixWaveformVisualizer: failed to load the visualizer module; rendering a plain backdrop.");
Logger.LogWarning(ex, "WaveformVisualizer: failed to load the visualizer module; rendering a plain backdrop.");
return;
}
@@ -203,7 +203,7 @@ public partial class MixWaveformVisualizer : ComponentBase, IAsyncDisposable
/// and to re-push when the controls bar signals a change. Each value is its own dedicated dial:
/// <list type="bullet">
/// <item>scroll speed [0,1] is mapped onto the useful zoom band via
/// <see cref="MixZoomMapping.ScrollKnobToSeconds"/> and pushed through <c>setScrollSpeed</c>
/// <see cref="WaveformZoomMapping.ScrollKnobToSeconds"/> and pushed through <c>setScrollSpeed</c>
/// (higher speed → tighter window → faster scroll);</item>
/// <item>gradient rotation speed → <c>setGradientRotationSpeed</c> (live OKLab anchor rotation);</item>
/// <item>gravity / heat / collision strength → their dedicated lava-physics dials;</item>
@@ -218,7 +218,7 @@ public partial class MixWaveformVisualizer : ComponentBase, IAsyncDisposable
if (_handle is null) return;
// Scroll speed is a normalized [0,1] axis; map it onto the useful zoom band (Phase 10 retune —
// the knob's full travel now covers the 60%100% zoom range, dropping the dead slow/wide end).
var visibleSeconds = MixZoomMapping.ScrollKnobToSeconds(ControlState.ScrollSpeed);
var visibleSeconds = WaveformZoomMapping.ScrollKnobToSeconds(ControlState.ScrollSpeed);
await _handle.InvokeVoidAsync("setScrollSpeed", visibleSeconds);
await _handle.InvokeVoidAsync("setGradientRotationSpeed", ControlState.GradientRotationSpeed);
await _handle.InvokeVoidAsync("setLavaGravity", ControlState.LavaGravity);
@@ -1,8 +1,8 @@
@namespace DeepDrftPublic.Client.Controls
@using DeepDrftPublic.Client.Services
@inject MixVisualizerControlState ControlState
@inject WaveformVisualizerControlState ControlState
@* The Mix visualizer controls. EIGHT continuous RadialKnobs — scroll speed, gradient rotation speed,
@* The waveform 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).
@@ -14,8 +14,8 @@
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
It owns NO JS interop: it mutates the shared, session-scoped WaveformVisualizerControlState and raises its
Changed event. The backdrop bridge (WaveformVisualizer) subscribes to that event and pushes the
affected dial to the WebGL module. That keeps the JS module handle single-owned by the bridge and
this component purely presentational. None of these is a seek surface (read-only contract §D).
@@ -1,13 +1,13 @@
namespace DeepDrftPublic.Client.Controls;
/// <summary>
/// Pure mapping between the Mix visualizer's zoom slider position [0, 1] and the visible time-span in
/// Pure mapping between the waveform visualizer's zoom slider position [0, 1] and the visible time-span in
/// seconds. The span range is wide (0.333 s … 30 s, ~90×), so the mapping is logarithmic — equal
/// slider travel changes the span by an equal *ratio*, which feels even to the hand. Slider
/// orientation: fraction 0 = most zoomed-out (longest span), fraction 1 = most zoomed-in (the
/// 0.333 s quarter-note-@-180-BPM anchor). Extracted from the component so the math is unit-testable.
/// </summary>
public static class MixZoomMapping
public static class WaveformZoomMapping
{
/// <summary>Shortest span (max zoom): one quarter note at 180 BPM = 60/180 s. Hard anchor.</summary>
public const double MinVisibleSeconds = 60.0 / 180.0;