Demos/BeatPad/KnobMath.cs
using System;
namespace Sandbox.BeatPad;
// Pure mapping math for the knob row. Angle convention: -135deg (min, 7 o'clock) ..
// 0deg (mid) .. +135deg (max, 5 o'clock), a 270deg sweep.
public static class KnobMath
{
public const float MinAngle = -135f;
public const float MaxAngle = 135f;
public const float Sweep = MaxAngle - MinAngle; // 270
public static float Clamp01(float v) => v < 0f ? 0f : (v > 1f ? 1f : v);
public static float ValueToAngle(float value) => MinAngle + Clamp01(value) * Sweep;
public static float AngleToValue(float angle) => Clamp01((angle - MinAngle) / Sweep);
// Vertical drag: down (+y) lowers value, up (-y) raises it. pixelsPerDegree ~ 2.
public static float ApplyDrag(float current, float deltaPixelsY, float pixelsPerDegree)
{
float degrees = -deltaPixelsY / pixelsPerDegree;
return Clamp01(current + degrees / Sweep);
}
// PCH knob: value 0..1 -> pitch multiplier, +/-1 octave (0.5..2.0), 1.0 at mid.
public static float ValueToPitch(float value) => MathF.Pow(2f, (Clamp01(value) - 0.5f) * 2f);
// Most-closed one-pole Cutoff at full filter deflection (audible, never silent).
public const float FilterFloor = 0.05f;
// Max wet reverb send so full-right REV still keeps the dry transient.
public const float MaxReverbMix = 0.6f;
// amount 0 (centered) .. 1 (full) -> processor Cutoff (1 = transparent .. FilterFloor),
// curved so the onset near center is gentle and the extreme is dramatic.
static float Deflect(float amount) => 1f - (1f - FilterFloor) * (amount * amount);
// FLT is bipolar: center 0.5 leaves both filters open. Left of center drives the
// low-pass down (darker); right of center leaves the low-pass open.
public static float ValueToLowPassCutoff(float value)
{
float v = Clamp01(value);
return v >= 0.5f ? 1f : Deflect((0.5f - v) / 0.5f);
}
// Right of center drives the high-pass down (thinner); left of center leaves it open.
public static float ValueToHighPassCutoff(float value)
{
float v = Clamp01(value);
return v <= 0.5f ? 1f : Deflect((v - 0.5f) / 0.5f);
}
// REV knob -> DspProcessor.Mix (wet/dry); linear, capped at MaxReverbMix.
public static float ValueToReverbMix(float value) => Clamp01(value) * MaxReverbMix;
}